From 1c23049ec475c1be33739051242c9c1a08ec09df Mon Sep 17 00:00:00 2001 From: wyuer <1148547900@q qqq.com> Date: Tue, 9 Apr 2024 22:06:24 +0800 Subject: [PATCH] adslder --- src/app/[slug]/page.jsx | 2 +- src/app/api/collection/[slug]/route.js | 49 +++++ src/app/api/collection/route.js | 33 ++++ src/app/api/folder/route.js | 6 +- src/app/collectiondetail/[slug]/page.jsx | 79 ++++++++ src/app/detail/[slug]/page.jsx | 12 +- src/app/page.jsx | 13 +- src/app/providers.jsx | 7 +- src/components/AddCollection.jsx | 177 ++++++++++++++++++ src/components/Cards.jsx | 4 +- src/components/Collections.jsx | 41 ++++ .../{AddFileInfo.jsx => DeleteCollection.jsx} | 57 ++---- src/components/EditCollection.jsx | 177 ++++++++++++++++++ 13 files changed, 604 insertions(+), 53 deletions(-) create mode 100644 src/app/api/collection/[slug]/route.js create mode 100644 src/app/api/collection/route.js create mode 100644 src/app/collectiondetail/[slug]/page.jsx create mode 100644 src/components/AddCollection.jsx create mode 100644 src/components/Collections.jsx rename src/components/{AddFileInfo.jsx => DeleteCollection.jsx} (52%) create mode 100644 src/components/EditCollection.jsx diff --git a/src/app/[slug]/page.jsx b/src/app/[slug]/page.jsx index 22d18c7..7d9588e 100644 --- a/src/app/[slug]/page.jsx +++ b/src/app/[slug]/page.jsx @@ -2,7 +2,7 @@ import React from "react"; import AddFolder from "@/components/AddFolder"; -import AddFileInfo from "@/components/AddFileInfo"; +import AddFileInfo from "@/components/AddCollection"; import Cards from "@/components/Cards"; import { useRequest } from "ahooks"; import { getFetch } from "@/lib/doFetch"; diff --git a/src/app/api/collection/[slug]/route.js b/src/app/api/collection/[slug]/route.js new file mode 100644 index 0000000..1c4acdd --- /dev/null +++ b/src/app/api/collection/[slug]/route.js @@ -0,0 +1,49 @@ +// RESTFUl api 匹é…å¸¦è·¯ç”±å‚æ•° +import response from "@/lib/res"; +import prisma from "@/lib/prisma"; + +// R -> Read One +export async function GET(request, { params }) { + const slug = params.slug; + try { + const collection = await prisma.collection.findUnique({ + where: { + id: parseInt(slug), + }, + include: { + folder: true, + }, + }); + return response(collection); + } catch (error) { + return response(error, "err"); + } +} + +//U -> Update +export async function PUT(request, { params }) { + const body = await request.json(); + const slug = params.slug; + try { + const res = await prisma.collection.update({ + where: { id: parseInt(slug) }, + data: { ...body }, + }); + return response({ ...res },"æ›´æ–°æˆåŠŸ"); + } catch (error) { + return response(error, "err"); + } +} + +//D -> Delete +export async function DELETE(request, { params }) { + const slug = params.slug; + try { + const res = await prisma.collection.delete({ + where: { id: parseInt(slug) }, + }); + return response({ ...res },'åˆ é™¤æˆåŠŸ'); + } catch (error) { + return response(error, "err"); + } +} diff --git a/src/app/api/collection/route.js b/src/app/api/collection/route.js new file mode 100644 index 0000000..0fb7987 --- /dev/null +++ b/src/app/api/collection/route.js @@ -0,0 +1,33 @@ +// RESTFUl api åŒ¹é…æ— è·¯ç”±å‚æ•° + +import prisma from "@/lib/prisma"; +import response from "@/lib/res"; + +// C -> Create +export async function POST(request) { + const body = await request.json(); + //获å–body傿•° + const res = await prisma.collection.create({ + data: { ...body }, + }); + return response({ ...res }, "新建æˆåŠŸ"); +} + +// R -> Read +export async function GET(request, { params }) { + // query is "hello" for /api/search?query=hello + // const slug = params.slug; // è·¯ç”±å‚æ•° + try { + const collections = await prisma.collection.findMany({ + where: { + parentId: null, // æ ¹ç›®å½• + }, + include: { + folder: true, + }, + }); + return response(collections); + } catch (error) { + return response(error, "err"); + } +} diff --git a/src/app/api/folder/route.js b/src/app/api/folder/route.js index 5d8d3a4..71e0eae 100644 --- a/src/app/api/folder/route.js +++ b/src/app/api/folder/route.js @@ -26,9 +26,11 @@ export async function GET(request, { params }) { }, include: { collections: true, - folders: true, - }, + folders: true + } }); + + return response(folders); } catch (error) { return response(error, "err"); diff --git a/src/app/collectiondetail/[slug]/page.jsx b/src/app/collectiondetail/[slug]/page.jsx new file mode 100644 index 0000000..2dd90dc --- /dev/null +++ b/src/app/collectiondetail/[slug]/page.jsx @@ -0,0 +1,79 @@ +"use client"; + + +import { getFetch } from "@/lib/doFetch"; +import { useRequest } from "ahooks"; +import { Button } from "@nextui-org/react"; +import { ArrowLeftOutlined } from "@ant-design/icons"; +import EditFolder from "@/components/EditFolder"; +import DeleteFolder from "@/components/DeleteFolder"; + +import { useRouter } from "next/navigation"; + + + +const columns = [ + { name: "code", label: "ç¼–ç " }, + { name: "faultType", label: "故障类型" }, + { name: "fault", label: "æ•…éšœ" }, + { name: "faultJudge", label: "åˆ¤æ–æ¡ä»¶" }, + { name: "faultReason", label: "æ•…éšœåŽŸå› " }, + { name: "faultFn", label: "解决方案" }, + { name: "faultMessage", label: "æ•…éšœæè¿°" }, +]; + +export default function Detail({ params }) { + //params.slug è·¯ç”±ä¼ å‚èŽ·å– + const router = useRouter(); + + const { data, refreshAsync } = useRequest( + async () => { + let res = await getFetch({ + url: "/api/collection/" + params.slug, + params: {}, + }); + return res?.data; + }, + { + refreshDeps: [params.slug], + } + ); + + return ( + <div className="flex w-full flex-col"> + <div className="flex gap-4"> + + <Button + isIconOnly + color="primary" + variant="faded" + aria-label="Take a add" + onPress={() => { + router.back(); + }} + > + <ArrowLeftOutlined></ArrowLeftOutlined> + </Button> + <Button color="primary" variant="flat"> + {data?.name} + </Button> +<EditFolder data={data} refresh={refreshAsync}/> +<DeleteFolder data={data} refresh={refreshAsync}/> + + </div> + + <ul class="divide-y divide-gray-200"> + {columns?.map((it) => { + return ( + <li class="flex items-center py-4" key={it?.name}> + <div class="ml-4"> + <div class="text-sm font-medium text-gray-900">{it?.label}</div> + <div class="text-sm text-gray-500">{data?.[it?.name]}</div> + </div> + </li> + ); + })} + </ul> + </div> + ); +} diff --git a/src/app/detail/[slug]/page.jsx b/src/app/detail/[slug]/page.jsx index a78627b..b6d9510 100644 --- a/src/app/detail/[slug]/page.jsx +++ b/src/app/detail/[slug]/page.jsx @@ -8,7 +8,11 @@ import { ArrowLeftOutlined } from "@ant-design/icons"; import EditFolder from "@/components/EditFolder"; import DeleteFolder from "@/components/DeleteFolder"; import AddFolder from "@/components/AddFolder"; +import AddCollection from '@/components/AddCollection'; import Cards from "@/components/Cards"; +import Collections from "@/components/Collections"; + + import { useRouter } from "next/navigation"; export default function Detail({ folder, params }) { @@ -72,10 +76,16 @@ export default function Detail({ folder, params }) { type={"icon"} ></AddFolder> <Divider orientation="vertical" /> + <AddCollection + refresh={refreshAsync} + parentId={params.slug} + type={"icon"} + /> </div> - <div className="mt-8"> + <div className="mt-8 flex flex-wrap gap-2"> <Cards list={data?.folders ?? []}></Cards> + <Collections list={data?.collections ?? []}></Collections> </div> </div> </div> diff --git a/src/app/page.jsx b/src/app/page.jsx index 2d71f99..3a8fbb1 100644 --- a/src/app/page.jsx +++ b/src/app/page.jsx @@ -2,8 +2,10 @@ import React from "react"; import AddFolder from "@/components/AddFolder"; -import AddFileInfo from "@/components/AddFileInfo"; +import AddCollection from "@/components/AddCollection"; import Cards from "@/components/Cards"; +import Collections from "@/components/Collections"; + import { useRequest } from "ahooks"; import { getFetch } from "@/lib/doFetch"; @@ -14,13 +16,20 @@ export default function Home(props) { return res?.data ?? []; }); + const collection = useRequest(async () => { + const res = await getFetch({ url: "/api/collection", params: {} }); + return res?.data ?? []; + }); return ( <div> <div className="flex gap-4"> <AddFolder refresh={refreshAsync} /> - <AddFileInfo refresh={refreshAsync} /> + <AddCollection refresh={collection?.refreshAsync} /> </div> + <div className="flex gap-4"> <Cards list={data ?? []} /> + <Collections list={collection?.data ?? []} /> + </div> </div> ); } diff --git a/src/app/providers.jsx b/src/app/providers.jsx index 8731de5..34bdece 100644 --- a/src/app/providers.jsx +++ b/src/app/providers.jsx @@ -17,7 +17,7 @@ export function Providers({ children }) { return ( <NextUIProvider navigate={router.push}> - <div className="flex w-full flex-col mb-4"> + {/* <div className="flex w-full flex-col mb-4"> <Tabs aria-label="Options" radius={"full"} @@ -30,11 +30,8 @@ export function Providers({ children }) { <Tab key="/" title={"首页"}></Tab> <Tab key="/login" title={"login"}></Tab> <Tab key="/detail" title={"detail"}></Tab> - </Tabs> - </div> - {/* <Link href="/">首页</Link> - <Link href="/detail">DOC</Link> */} + </div> */} {children} </NextUIProvider> ); diff --git a/src/components/AddCollection.jsx b/src/components/AddCollection.jsx new file mode 100644 index 0000000..7be7189 --- /dev/null +++ b/src/components/AddCollection.jsx @@ -0,0 +1,177 @@ +import React from "react"; +import { + Modal, + ModalContent, + ModalHeader, + ModalBody, + ModalFooter, + Button, + useDisclosure, + Input, + Avatar, + Textarea, +} from "@nextui-org/react"; +import { AiFillPlusSquare } from "react-icons/ai"; +import { useForm } from "react-hook-form"; +import { doFetch } from "@/lib/doFetch"; +import UploadImage from "./UploadImage"; + +export default function AddCollection({ refresh, parentId, type }) { + const { isOpen, onOpen, onOpenChange } = useDisclosure(); + const { register, handleSubmit, control, setValue } = useForm(); + + const handleButtonClick = async (close) => { + // 手动触å‘è¡¨å•æäº¤ + handleSubmit(onSubmit)(); + await refresh(); + close(); + }; + + const onSubmit = async (data) => { + return await doFetch({ + url: "/api/collection", + params: { ...data, parentId:parentId ?parseInt(parentId): null }, + }); + }; + + return ( + <> + {type === "icon" ? ( + <Button + isIconOnly + color="primary" + variant="faded" + aria-label="Take a add" + onPress={onOpen} + > + <AiFillPlusSquare style={{ fontSize: "20px" }} /> + </Button> + ) : ( + <Button + radius="full" + className="bg-gradient-to-tr from-green-500 to-blue-500 text-white shadow-lg mb-4" + onPress={onOpen} + > + <AiFillPlusSquare /> + æ·»åŠ çŸ¥è¯†åº“ + </Button> + )} + <Modal + isOpen={isOpen} + backdrop={"blur"} + onOpenChange={onOpenChange} + motionProps={{ + variants: { + enter: { + y: 0, + opacity: 1, + transition: { + duration: 0.3, + ease: "easeOut", + }, + }, + exit: { + y: -20, + opacity: 0, + transition: { + duration: 0.2, + ease: "easeIn", + }, + }, + }, + }} + > + <ModalContent> + {(onClose) => ( + <> + <ModalHeader className="flex flex-col gap-1"> + æ·»åŠ çŸ¥è¯†åº“ + </ModalHeader> + <ModalBody> + <form action="" className="flex flex-col gap-4"> + <Input + type="text" + label="知识库åç§°" + name="name" + isRequired + {...register("name", { required: true, maxLength: 20 })} + /> + <Input + type="text" + label="ç¼–ç " + name="code" + {...register("code", { required: true, maxLength: 20 })} + /> + + <Input + type="text" + label="故障类型" + name="faultType" + {...register("faultType", { + required: true, + maxLength: 20, + })} + /> + + <Input + type="text" + label="æ•…éšœ" + name="fault" + {...register("fault", { required: true, maxLength: 20 })} + /> + <Textarea + type="text" + label="åˆ¤æ–æ¡ä»¶" + name="faultJudge" + {...register("faultJudge", { + required: true, + maxLength: 20, + })} + /> + <Textarea + type="text" + label="æ•…éšœåŽŸå› " + name="faultReason" + {...register("faultReason", { + required: true, + maxLength: 20, + })} + /> + + <Textarea + type="text" + label="解决方案" + name="faultFn" + {...register("faultFn", { required: true, maxLength: 20 })} + /> + <Textarea + type="text" + label="æ•…éšœæè¿°" + name="faultMessage" + {...register("faultMessage", { + required: true, + maxLength: 20, + })} + /> + </form> + </ModalBody> + <ModalFooter> + <Button color="danger" variant="light" onPress={onClose}> + å–æ¶ˆ + </Button> + <Button + color="primary" + onPress={() => { + handleButtonClick(onClose); + }} + > + æäº¤ + </Button> + </ModalFooter> + </> + )} + </ModalContent> + </Modal> + </> + ); +} diff --git a/src/components/Cards.jsx b/src/components/Cards.jsx index f1e3e89..6cff2fd 100644 --- a/src/components/Cards.jsx +++ b/src/components/Cards.jsx @@ -16,7 +16,7 @@ export default memo(({ list }) => { {list?.map?.((item, index) => ( <Card shadow="sm" - className="w-[170px]" + className="w-[220px]" key={index} isPressable onPress={() => { @@ -30,7 +30,7 @@ export default memo(({ list }) => { radius="lg" width="100%" alt={item.name} - className="w-full object-cover h-[140px]" + className="w-full object-cover h-[180px]" src={item.poster} fallbackSrc="/folder.png" /> diff --git a/src/components/Collections.jsx b/src/components/Collections.jsx new file mode 100644 index 0000000..17ff9f5 --- /dev/null +++ b/src/components/Collections.jsx @@ -0,0 +1,41 @@ +/* eslint-disable import/no-anonymous-default-export */ +/* eslint-disable react/display-name */ + +import React, { memo } from "react"; +import { Card, CardBody, CardFooter, Image, Button } from "@nextui-org/react"; +import { difftime } from "@/lib/time"; +import { useRouter } from "next/navigation"; + +export default memo(({ list }) => { + const router = useRouter(); + + return ( + <div className="gap-2 flex flex-wrap"> + {list?.map?.((item, index) => ( + <Card + shadow="sm" + className="w-[220px]" + key={index} + isPressable + onPress={() => { + router.push("/collectiondetail/" + item.id); + }} + > + <CardBody className="overflow-visible p-0"> + <div + className="w-full object-cover h-[180px] flex justify-center items-center bg-gradient-to-r from-cyan-500 to-blue-500" + > + <span className="bg-clip-text text-transparent text-white text-lg"> + {item?.name?.substring(0,4).toUpperCase()} + </span> + </div> + </CardBody> + <CardFooter className="text-small justify-between"> + <b>{item.name}</b> + <p className="text-default-500">{difftime(item.createdAt)}</p> + </CardFooter> + </Card> + ))} + </div> + ); +}); diff --git a/src/components/AddFileInfo.jsx b/src/components/DeleteCollection.jsx similarity index 52% rename from src/components/AddFileInfo.jsx rename to src/components/DeleteCollection.jsx index 2593a24..d2cb2fd 100644 --- a/src/components/AddFileInfo.jsx +++ b/src/components/DeleteCollection.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect } from "react"; import { Modal, ModalContent, @@ -8,38 +8,27 @@ import { Button, useDisclosure, Input, - Avatar, } from "@nextui-org/react"; -import { AiFillPlusSquare } from "react-icons/ai"; -import { useForm } from "react-hook-form"; -import { doFetch } from "@/lib/doFetch"; +import { useForm, Controller } from "react-hook-form"; +import { delFetch, doFetch, putFetch } from "@/lib/doFetch"; import UploadImage from "./UploadImage"; +import { BsTrash3Fill } from "react-icons/bs"; +import { useRouter } from "next/navigation"; -export default function AddFileInfo({refresh}) { +export default function DeleteFolder({ refresh, data }) { const { isOpen, onOpen, onOpenChange } = useDisclosure(); - const { register, handleSubmit, control, setValue } = useForm(); + const router = useRouter(); const handleButtonClick = async (close) => { - // 手动触å‘è¡¨å•æäº¤ - handleSubmit(onSubmit)(); - await refresh(); - - close(); + await delFetch({url:"/api/folder/"+data.id}) + router.back(); // 返回上一页 }; - const onSubmit = async (data) => { - return await doFetch({ url: "/api/folder", params: data }); - }; return ( <> - <Button - radius="full" - className="bg-gradient-to-tr from-green-500 to-blue-500 text-white shadow-lg mb-4" - onPress={onOpen} - > - <AiFillPlusSquare /> - æ·»åŠ æ–‡ä»¶ + <Button isIconOnly color="danger" aria-label="Like" onPress={onOpen}> + <BsTrash3Fill style={{ fontSize: "18px" }} /> </Button> <Modal isOpen={isOpen} @@ -70,37 +59,25 @@ export default function AddFileInfo({refresh}) { {(onClose) => ( <> <ModalHeader className="flex flex-col gap-1"> - æ·»åŠ æ–‡ä»¶ + 是å¦åˆ 除该文件夹 </ModalHeader> <ModalBody> - <form action=""> - <input type="hidden" {...register("poster")} /> - <div className="flex justify-center items-center flex-col mb-8"> - <UploadImage - control={control} - setValue={setValue} - ></UploadImage> - </div> + <p> + ä»…å¯åˆ 除空文件夹 + </p> - <Input - type="text" - label="文件夹" - name="name" - {...register("name", { required: true, maxLength: 20 })} - /> - </form> </ModalBody> <ModalFooter> <Button color="danger" variant="light" onPress={onClose}> å–æ¶ˆ </Button> <Button - color="primary" + color="danger" onPress={() => { handleButtonClick(onClose); }} > - æäº¤ + åˆ é™¤ </Button> </ModalFooter> </> diff --git a/src/components/EditCollection.jsx b/src/components/EditCollection.jsx new file mode 100644 index 0000000..7be7189 --- /dev/null +++ b/src/components/EditCollection.jsx @@ -0,0 +1,177 @@ +import React from "react"; +import { + Modal, + ModalContent, + ModalHeader, + ModalBody, + ModalFooter, + Button, + useDisclosure, + Input, + Avatar, + Textarea, +} from "@nextui-org/react"; +import { AiFillPlusSquare } from "react-icons/ai"; +import { useForm } from "react-hook-form"; +import { doFetch } from "@/lib/doFetch"; +import UploadImage from "./UploadImage"; + +export default function AddCollection({ refresh, parentId, type }) { + const { isOpen, onOpen, onOpenChange } = useDisclosure(); + const { register, handleSubmit, control, setValue } = useForm(); + + const handleButtonClick = async (close) => { + // 手动触å‘è¡¨å•æäº¤ + handleSubmit(onSubmit)(); + await refresh(); + close(); + }; + + const onSubmit = async (data) => { + return await doFetch({ + url: "/api/collection", + params: { ...data, parentId:parentId ?parseInt(parentId): null }, + }); + }; + + return ( + <> + {type === "icon" ? ( + <Button + isIconOnly + color="primary" + variant="faded" + aria-label="Take a add" + onPress={onOpen} + > + <AiFillPlusSquare style={{ fontSize: "20px" }} /> + </Button> + ) : ( + <Button + radius="full" + className="bg-gradient-to-tr from-green-500 to-blue-500 text-white shadow-lg mb-4" + onPress={onOpen} + > + <AiFillPlusSquare /> + æ·»åŠ çŸ¥è¯†åº“ + </Button> + )} + <Modal + isOpen={isOpen} + backdrop={"blur"} + onOpenChange={onOpenChange} + motionProps={{ + variants: { + enter: { + y: 0, + opacity: 1, + transition: { + duration: 0.3, + ease: "easeOut", + }, + }, + exit: { + y: -20, + opacity: 0, + transition: { + duration: 0.2, + ease: "easeIn", + }, + }, + }, + }} + > + <ModalContent> + {(onClose) => ( + <> + <ModalHeader className="flex flex-col gap-1"> + æ·»åŠ çŸ¥è¯†åº“ + </ModalHeader> + <ModalBody> + <form action="" className="flex flex-col gap-4"> + <Input + type="text" + label="知识库åç§°" + name="name" + isRequired + {...register("name", { required: true, maxLength: 20 })} + /> + <Input + type="text" + label="ç¼–ç " + name="code" + {...register("code", { required: true, maxLength: 20 })} + /> + + <Input + type="text" + label="故障类型" + name="faultType" + {...register("faultType", { + required: true, + maxLength: 20, + })} + /> + + <Input + type="text" + label="æ•…éšœ" + name="fault" + {...register("fault", { required: true, maxLength: 20 })} + /> + <Textarea + type="text" + label="åˆ¤æ–æ¡ä»¶" + name="faultJudge" + {...register("faultJudge", { + required: true, + maxLength: 20, + })} + /> + <Textarea + type="text" + label="æ•…éšœåŽŸå› " + name="faultReason" + {...register("faultReason", { + required: true, + maxLength: 20, + })} + /> + + <Textarea + type="text" + label="解决方案" + name="faultFn" + {...register("faultFn", { required: true, maxLength: 20 })} + /> + <Textarea + type="text" + label="æ•…éšœæè¿°" + name="faultMessage" + {...register("faultMessage", { + required: true, + maxLength: 20, + })} + /> + </form> + </ModalBody> + <ModalFooter> + <Button color="danger" variant="light" onPress={onClose}> + å–æ¶ˆ + </Button> + <Button + color="primary" + onPress={() => { + handleButtonClick(onClose); + }} + > + æäº¤ + </Button> + </ModalFooter> + </> + )} + </ModalContent> + </Modal> + </> + ); +} -- 2.21.0