From ceceee6ba7b29c0e05e51c8adc2e18407c8fba21 Mon Sep 17 00:00:00 2001 From: zrll_ <zhengruilin0@126.com> Date: Thu, 16 Nov 2023 23:47:07 +0800 Subject: [PATCH] add picture show and pay --- app/authenticate/page.tsx | 11 ++--- app/buy/layout.tsx | 13 +++++ app/buy/page.tsx | 97 ++++++++++++++++++++++++++++++++++++++ app/dashboard/page.tsx | 22 +++++++-- components/navbar.tsx | 2 +- components/picture.tsx | 18 +++++-- components/price.tsx | 20 +++++++- config/prices.tsx | 4 ++ interface/hooks.ts | 2 +- interface/model/picture.ts | 24 ++++++++++ interface/pictureAPI.ts | 11 ++++- package.json | 1 + yarn.lock | 12 +++++ 13 files changed, 213 insertions(+), 24 deletions(-) create mode 100644 app/buy/layout.tsx create mode 100644 app/buy/page.tsx create mode 100644 interface/model/picture.ts diff --git a/app/authenticate/page.tsx b/app/authenticate/page.tsx index cdf5385..acf3308 100644 --- a/app/authenticate/page.tsx +++ b/app/authenticate/page.tsx @@ -1,6 +1,6 @@ 'use client' -import {Card, CardBody, CardFooter, Spinner} from "@nextui-org/react"; +import {Card, CardBody, CardFooter} from "@nextui-org/react"; import {Button} from "@nextui-org/button"; import React, {ReactNode, useState} from "react"; import {Input} from "@nextui-org/input"; @@ -118,7 +118,7 @@ export default function Page() { </div> </CardBody> <CardFooter> - <Button style={{position: "relative", left: 7}} color={buttonColor} disabled={buttonDisable} + <Button style={{position: "relative", left: 7}} disabled={buttonDisable} color={buttonColor} isLoading={isLoading} onClick={ () => { if (state == "email") { @@ -127,10 +127,8 @@ export default function Page() { return; } setLoading(true); - setButtonDisable(true); UserAPI.checkEmail(email).then(r => { setState(r ? "login" : "register") - setButtonDisable(false); setLoading(false); if (!r) { Message.message("验证码已发送") @@ -139,7 +137,6 @@ export default function Page() { } else if (state == "register") { setLoading(true); - setButtonDisable(true); if (password != confirmPassword) { Message.error("密码输入不一致") } @@ -155,11 +152,9 @@ export default function Page() { Message.error(message); } setLoading(false); - setButtonDisable(false); }) } else {//login setLoading(true); - setButtonDisable(true); UserAPI.login(email, password).then((r) => { let [state, text] = r; @@ -178,7 +173,7 @@ export default function Page() { } }> {isLoading ? - <div className="justify-center flex space-x-3"><Spinner color="default" size="sm"/><p>加载中...</p></div> : state == "email" ? "下一步" : state == "login" ? "登录" : "注册"} + "加载中..." : state == "email" ? "下一步" : state == "login" ? "登录" : "注册"} </Button> </CardFooter> </Card> diff --git a/app/buy/layout.tsx b/app/buy/layout.tsx new file mode 100644 index 0000000..7f94cd4 --- /dev/null +++ b/app/buy/layout.tsx @@ -0,0 +1,13 @@ +export default function BuyLayout({ + children, + }: { + children: React.ReactNode; +}) { + return ( + <section className="flex flex-col items-center justify-center gap-4 py-8 md:py-10"> + <div className="inline-block max-w-lg text-center justify-center"> + {children} + </div> + </section> + ); +} diff --git a/app/buy/page.tsx b/app/buy/page.tsx new file mode 100644 index 0000000..27eb943 --- /dev/null +++ b/app/buy/page.tsx @@ -0,0 +1,97 @@ +'use client' + +import {title} from "@/components/primitives"; +import {IsLoggedIn} from "@/interface/hooks"; +import {useRouter} from "next/navigation"; +import {useState} from "react"; +import {Card, CardBody, CardFooter, CardHeader, Divider, Select, SelectItem} from "@nextui-org/react"; +import {Button, ButtonGroup} from "@nextui-org/button"; + +export default function BuyPage() { + let router = useRouter(); + let [group, setGroup] = useState(""); + let [state, setState] = useState(0); + let [time, setTime] = useState("year"); + + let pages = [ + <> + <Card> + <CardHeader>确认支付信息</CardHeader> + <CardBody> + <div className={"space-y-10"}> + <p>请确认下方信息是否正确,如信息错误导致损失,我们不承担任何责任。</p> + <div className={"flex space-x-3"}> + <Select + label="请选择套餐" + className="max-w-xs" + labelPlacement={"outside"} + > + <SelectItem key="started" value="入门"> + 入门 + </SelectItem> + <SelectItem key="advanced" value="进阶"> + 进阶 + </SelectItem> + <SelectItem key="professional" value="专业"> + 专业 + </SelectItem> + </Select> + <ButtonGroup> + <Button color={time == "month" ? "primary" : "default"} onClick={() => { + setTime("month") + }}>月度</Button> + <Button color={time == "quarter" ? "primary" : "default"} onClick={() => { + setTime("quarter") + }}>季度</Button> + <Button color={time == "half" ? "primary" : "default"} onClick={() => { + setTime("half") + }}>半年</Button> + <Button color={time == "year" ? "primary" : "default"} onClick={() => { + setTime("year") + }}>年度</Button> + </ButtonGroup> + </div> + <div> + <p>支付方式</p> + <ButtonGroup> + <Button>微信支付</Button> + <Button>支付宝</Button> + </ButtonGroup> + </div> + </div> + </CardBody> + <Divider/> + <CardFooter> + <Button color={"primary"} onClick={() => { + setState(1) + }}>下一步</Button> + </CardFooter> + </Card> + </>, + <> + <Card> + <CardHeader>支付</CardHeader> + <CardBody> + <p>请扫描下方的二维码进行支付,支付成功后请点击下方的刷新按钮或直接前往用户中心。</p> + <p>将立即进行支付,恕不退款。</p> + </CardBody> + <Divider/> + <CardFooter className={"space-x-3"}> + <Button color={"secondary"}>刷新</Button> + <Button variant={"light"} color={"danger"} onClick={() => { + setState(0); + }}>上一步</Button> + </CardFooter> + </Card></> + ] + + if (!IsLoggedIn) { + router.push("/authenticate"); + } + return ( + <div className="max-w-7xl space-y-10"> + <h1 className={title()}>购买</h1> + {pages[state]} + </div> + ); +} diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index d0f17c2..1846f42 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -12,6 +12,9 @@ import {PictureAPI} from "@/interface/pictureAPI"; import {useState} from "react"; import {UserAPI} from "@/interface/userAPI"; import {getGroupPrice} from "@/config/prices"; +import {PictureList} from "@/interface/model/picture"; +import {SERVER_URL} from "@/interface/api"; +import cookie from "react-cookies"; export default function Page() { let [used, setUsed] = useState(0); @@ -19,9 +22,9 @@ export default function Page() { let [timeLeft, setTimeLeft] = useState(100); let [timeDescription, setTimeDescription] = useState("无限时间"); + let [pictures, setPictures] = useState<PictureList>(); - - if (used == 0 && total == 1) { + function updateInfo() { UserAPI.getExtendedInformation().then((r) => { if (r == undefined) { router.push("/authenticate"); @@ -43,7 +46,13 @@ export default function Page() { } } }) - PictureAPI.getPicturesList().then(); + PictureAPI.getPicturesList().then((r) => { + setPictures(r); + }); + } + + if (used == 0 && total == 1) { + updateInfo(); } @@ -78,6 +87,7 @@ export default function Page() { let files = e.target.files!; let file = files[0]; PictureAPI.uploadFile(file).then(() => { + updateInfo(); }); }}></Uploader> @@ -105,8 +115,10 @@ export default function Page() { </a> </CardFooter> </Card> - - <Picture url="https://t7.baidu.com/it/u=2961459243,2146986594&fm=193&f=GIF" name="雪景.png"/> + {pictures?.records == null ? <a>请上传</a> : pictures!.records.map(picture => <Picture + url={SERVER_URL + "/picture/preview?shareMode=2&id=" + picture.id.toString() + "&token=" + cookie.load("token")} + name={picture.fileName}/>)} + {/*<Picture url="https://t7.baidu.com/it/u=2961459243,2146986594&fm=193&f=GIF" name="雪景.png"/>*/} </div> ) } \ No newline at end of file diff --git a/components/navbar.tsx b/components/navbar.tsx index f64e13f..1c7e12a 100644 --- a/components/navbar.tsx +++ b/components/navbar.tsx @@ -19,7 +19,7 @@ import NextLink from "next/link"; import clsx from "clsx"; import {ThemeSwitch} from "@/components/theme-switch"; -import {GithubIcon, Logo, LogoText,} from "@/components/icons"; +import {GithubIcon, LogoText,} from "@/components/icons"; import {useRouter} from "next/navigation"; import {useState} from "react"; import cookie from "react-cookies"; diff --git a/components/picture.tsx b/components/picture.tsx index 19dfb54..3bd8ef6 100644 --- a/components/picture.tsx +++ b/components/picture.tsx @@ -7,6 +7,7 @@ import {DropdownMenu, DropdownTrigger} from "@nextui-org/dropdown"; import {Message} from "@/components/message"; import {FiClipboard, FiShare} from "react-icons/fi"; import {Input} from "@nextui-org/input"; +import cookie from "react-cookies"; export default function Picture(props: PictureProps) { let [isChecked, setIsChecked] = useState(false); @@ -14,7 +15,7 @@ export default function Picture(props: PictureProps) { const {isOpen, onOpen, onOpenChange} = useDisclosure(); const descriptionOpen = useDisclosure(); let [name, setName] = useState(props.name); - let [shareMode, setShareMode] = useState(props.pubicMode); + let [shareMode, setShareMode] = useState(props.pubicMode == null ? "none" : props.pubicMode!); let timeout: NodeJS.Timeout | null = null; let modelTimeout: NodeJS.Timeout | null = null; let [link, setShareLink] = useState(""); @@ -24,6 +25,8 @@ export default function Picture(props: PictureProps) { Message.success("已生成链接") } + console.log(cookie.load("token")); + // @ts-ignore return ( <> @@ -35,17 +38,19 @@ export default function Picture(props: PictureProps) { onPress={() => { descriptionOpen.onOpen(); }} + style={{width: 450}} > <Image alt={props.name} className="object-cover" + width={450} height={200} src={props.url} isZoomed /> <CardFooter className="justify-between before:bg-white/10 border-white/20 border-1 overflow-hidden py-1 absolute before:rounded-xl rounded-large bottom-1 w-auto shadow-small ml-1 z-10 space-x-2"> - <p className="text-tiny font-mono text-white/80">{props.name}</p> + <p className="text-tiny font-mono ">{props.name}</p> <Button isIconOnly className="text-white bg-black/20 justify-center" variant="light" size="sm" onClick={() => { copy(props.url); @@ -81,6 +86,7 @@ export default function Picture(props: PictureProps) { <Image alt={props.name} className="object-cover" + width={450} height={200} src={props.url} /> @@ -95,7 +101,7 @@ export default function Picture(props: PictureProps) { variant="bordered" className="capitalize" > - {shareMode == "watermark" ? "水印" : shareMode == "compressed" ? "压缩" : "原图"} + {shareMode == "watermark" ? "水印" : shareMode == "compressed" ? "压缩" : shareMode == "none" ? "不公开" : "原图"} </Button> </DropdownTrigger> <DropdownMenu @@ -107,10 +113,12 @@ export default function Picture(props: PictureProps) { disabledKeys={["original"]} onAction={(key) => { setShareMode(key.toString()); - let description = key == "watermark" ? "水印" : key == "compressed" ? "压缩" : "原图"; + console.log(key); + let description = key == "watermark" ? "水印" : key == "compressed" ? "压缩" : key == "none" ? "不公开" : "原图"; Message.success("已经设置公开等级为:" + description); }} > + <DropdownItem key="none">不公开</DropdownItem> <DropdownItem key="watermark">公开水印版本</DropdownItem> <DropdownItem key="compressed">公开压缩版本</DropdownItem> <DropdownItem key="original" @@ -119,7 +127,7 @@ export default function Picture(props: PictureProps) { </Dropdown> <div> {link == "" ? - <Button onClick={generateShareLink}>生成分享链接</Button> : + <Button disabled={shareMode == "none"} color={shareMode == "none" ? "default" : "primary"} onClick={generateShareLink}>生成分享链接</Button> : <Input value={link} style={{width: 260}} variant={"underlined"} endContent={ <Button isIconOnly style={{position: "relative", left: 7}} size="sm" onClick={() => { diff --git a/components/price.tsx b/components/price.tsx index ea55c6c..cae1ad3 100644 --- a/components/price.tsx +++ b/components/price.tsx @@ -4,8 +4,23 @@ import {Card, CardBody, CardFooter, CardHeader, Divider} from "@nextui-org/react import {Button} from "@nextui-org/button"; import {Spacer} from "@nextui-org/spacer"; import {ReactNode} from "react"; +import {IsLoggedIn} from "@/interface/hooks"; +import {useRouter} from "next/navigation"; export const Price = (price: PriceProps) => { + const router = useRouter(); + function onStart() { + if (IsLoggedIn) { + if (price.price.price == 0) { + router.push("/dashboard"); + } else { + router.push("/buy"); + } + } else { + router.push("/authenticate"); + } + } + return ( <> @@ -22,7 +37,7 @@ export const Price = (price: PriceProps) => { </CardBody> <Divider/> <CardFooter> - <Button variant="ghost" color="primary">开始使用</Button> + <Button variant="ghost" color="primary" onClick={onStart}>开始使用</Button> <Spacer style={{width: 20}}/> <p>{price.price.price} 元/月</p> <Spacer style={{width: 20}}/> @@ -38,7 +53,8 @@ export type PriceInfo = { name: ReactNode, singleFile: string, allSpace: string, - price: number + price: number, + plainName: string } type PriceProps = { diff --git a/config/prices.tsx b/config/prices.tsx index 42563f1..fe8d74f 100644 --- a/config/prices.tsx +++ b/config/prices.tsx @@ -3,6 +3,7 @@ import {PiFireDuotone} from "react-icons/pi"; export const priceFree: PriceInfo = { name: "免费", + plainName: "免费", singleFile: "50 MB", allSpace: "2 GB", price: 0, @@ -10,6 +11,7 @@ export const priceFree: PriceInfo = { export const priceStarted: PriceInfo = { name: "入门", + plainName: "入门", singleFile: "50 MB", allSpace: "10 GB", price: 30, // 0.9 320 @@ -20,6 +22,7 @@ export const priceAdvanced: PriceInfo = { <span>进阶</span> <PiFireDuotone style={{color: "red"}}/> </div>, + plainName: "进阶", singleFile: "100 MB", allSpace: "50 GB", price: 50, // 0.8 480 @@ -27,6 +30,7 @@ export const priceAdvanced: PriceInfo = { export const priceProfessional: PriceInfo = { name: "专业", + plainName: "专业", singleFile: "不限", allSpace: "200 GB", price: 150, // 0.7 1250 diff --git a/interface/hooks.ts b/interface/hooks.ts index ee10e5b..b45e7a2 100644 --- a/interface/hooks.ts +++ b/interface/hooks.ts @@ -3,7 +3,7 @@ import cookie from "react-cookies"; export var SetLoggedInState: any = null; -export var IsLoggedIn: boolean = !cookie.load("token") == undefined; +export var IsLoggedIn: boolean = cookie.load("token") != undefined; export function UpdateSetLoggedInHook(hook: any) { SetLoggedInState = (loggedIn: boolean) => { diff --git a/interface/model/picture.ts b/interface/model/picture.ts new file mode 100644 index 0000000..b10f316 --- /dev/null +++ b/interface/model/picture.ts @@ -0,0 +1,24 @@ +export interface Picture { + id: number; + uid: number; + pid: string; + fileName: string; + size: number; + downloads: number; + createTime: string; + updateTime: string; +} + +export interface PictureList { + records: Picture[]; + total: number; + size: number; + current: number; + orders: any[]; + optimizeCountSql: boolean; + searchCount: boolean; + optimizeJoinOfCountSql: boolean; + maxLimit?: number; + countId?: string; + pages: number; +} \ No newline at end of file diff --git a/interface/pictureAPI.ts b/interface/pictureAPI.ts index 9671e69..6465fc7 100644 --- a/interface/pictureAPI.ts +++ b/interface/pictureAPI.ts @@ -1,7 +1,7 @@ import {SERVER_URL} from "@/interface/api"; -import {base} from "next/dist/build/webpack/config/blocks/base"; import {Message} from "@/components/message"; import cookie from "react-cookies"; +import {PictureList} from "@/interface/model/picture"; export class PictureAPI { static async uploadFile(file: File) { @@ -40,6 +40,13 @@ export class PictureAPI { }; let result = await fetch(SERVER_URL + "/picture", requestOptions); - console.log(result); + if (!result.ok) { + Message.error("登录状态失效"); + cookie.remove('token'); + return; + } + let list: PictureList = JSON.parse(await result.text()); + console.log(list); + return list; } } \ No newline at end of file diff --git a/package.json b/package.json index ef946ec..cc3a79a 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "postcss": "8.4.29", "react": "18.2.0", "react-cookies": "^0.1.1", + "react-device-detect": "^2.2.3", "react-dom": "18.2.0", "react-hot-toast": "^2.4.1", "react-icons": "^4.11.0", diff --git a/yarn.lock b/yarn.lock index 55a75e7..2eca46e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5545,6 +5545,13 @@ react-cookies@^0.1.1: cookie "^0.3.1" object-assign "^4.1.1" +react-device-detect@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/react-device-detect/-/react-device-detect-2.2.3.tgz#97a7ae767cdd004e7c3578260f48cf70c036e7ca" + integrity sha512-buYY3qrCnQVlIFHrC5UcUoAj7iANs/+srdkwsnNjI7anr3Tt7UY6MqNxtMLlr0tMBied0O49UZVK8XKs3ZIiPw== + dependencies: + ua-parser-js "^1.0.33" + react-dom@18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" @@ -6164,6 +6171,11 @@ typescript@5.0.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b" integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw== +ua-parser-js@^1.0.33: + version "1.0.37" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.37.tgz#b5dc7b163a5c1f0c510b08446aed4da92c46373f" + integrity sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ== + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e"