This commit is contained in:
Eamon-meng
2025-06-07 11:28:36 +08:00
parent 1d0008fd4d
commit b3cd22e1cf
8 changed files with 418 additions and 327 deletions

View File

@@ -1,170 +1,191 @@
import Page from '@/components/page'
import Image from 'next/image'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { getProfile } from '@/actions/auth'
import { format } from 'date-fns'
import { CheckCircleIcon, CircleAlertIcon } from 'lucide-react'
import { Button, buttonVariants } from '@/components/ui/button'
import RechargeModal from '@/components/composites/recharge'
import { merge } from '@/lib/utils'
import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card'
import {format} from 'date-fns'
import {merge} from '@/lib/utils'
import banner from './_assets/banner.webp'
import actionBill from './_assets/action-bill.webp'
import actionBuy from './_assets/action-buy.webp'
import actionLogout from './_assets/action-logout.webp'
import Link from 'next/link'
import { listAnnouncements } from '@/actions/announcement'
import {listInitialization} from '@/actions/dashboard'
import Charts from './_client/charts'
import Pins from './_client/pins'
import UserCenter from './_client/userCenter'
import soon from './_assets/coming-soon.svg'
import mask from './_assets/Mask group.webp'
export type DashboardPageProps = {}
export default function DashboardPage(props: DashboardPageProps) {
return (
<Page className={merge(
`flex-auto grid`,
`grid-cols-[minmax(0,1fr)_minmax(0,1fr)_minmax(0,1fr)_300px]`,
`grid-rows-[150px_200px_minmax(150px,1fr)_minmax(150px,1fr)]`,
)}>
{/* banner */}
<section className={`col-start-1 row-start-1 col-span-3 relative rounded-lg overflow-hidden`}>
<Image src={banner} alt={`banner image`} className={`w-full h-full inset-0 absolute object-cover`} />
<div className={`flex flex-col absolute inset-0 justify-center px-8 gap-1`}>
<h3 className={`text-2xl text-primary font-medium`}>IP资源</h3>
<p className={`text-primary font-medium`}>//IP代理</p>
</div>
</section>
{/* 磁贴集 */}
<Pins />
{/* 图表 */}
<Charts />
{/* 信息 */}
<UserCenter />
{/* 通知 */}
<Announcements />
</Page>
)
}
async function UserCenter() {
const resp = await getProfile()
if (!resp.success) {
return (
<div className={`col-start-4 row-start-1 row-span-2 flex justify-center items-center`}>
</div>
)
}
const profile = resp.data
return (
<Card className={`col-start-4 row-start-1 row-span-2`}>
<CardContent className={`flex-auto flex flex-col justify-between`}>
<div className={`flex flex-col gap-1`}>
<p>{profile.phone}</p>
<p className={`text-sm text-weak`}>{`最后登录:${format(profile.last_login, 'yyyy-MM-dd HH:mm')}`}</p>
</div>
<div className={merge(
`flex justify-between p-2 rounded-md`,
profile.id_token ? `bg-done-muted` : `bg-warn-muted`,
)}>
{profile.id_token
? <>
<div className={`flex gap-2 items-center`}>
<CheckCircleIcon size={20} className={`text-done`} />
<span></span>
</div>
<div className={`flex flex-col items-end`}>
<span className={`text-sm`}>{profile.name}</span>
<span className={`text-xs text-weak`}>{profile.id_no}</span>
</div>
</>
: <>
<span className={`flex gap-2 items-center`}>
<CircleAlertIcon className={`text-warn`} />
<span></span>
</span>
<Button className={`h-9`}></Button>
</>
}
</div>
<div className={`flex flex-col gap-1`}>
<h4 className={`text-sm text-weak`}></h4>
<div className={`flex justify-between items-baseline`}>
<p className={`text-xl text-accent`}>{profile.balance}</p>
<RechargeModal />
</div>
</div>
<div className={`flex flex-col gap-3`}>
<h4 className={`text-sm text-weak`}></h4>
<div className={`flex justify-around gap-2`}>
<Link href="/admin/bills" className={merge(buttonVariants({ variant: `ghost` }), `flex flex-col gap-2 py-2 px-3 h-auto`)}>
<Image alt={`bill icon`} src={actionBill} height={48} />
<span className={`text-sm text-weak`}></span>
</Link>
<Link href="/admin/purchase" className={merge(buttonVariants({ variant: `ghost` }), `flex flex-col gap-2 py-2 px-3 h-auto`)}>
<Image alt={`buy icon`} src={actionBuy} height={48} />
<span className={`text-sm text-weak`}></span>
</Link>
<Link href="/admin/profile" className={merge(buttonVariants({ variant: `ghost` }), `flex flex-col gap-2 py-2 px-3 h-auto`)}>
<Image alt={`logout icon`} src={actionLogout} height={48} />
<span className={`text-sm text-weak`}></span>
</Link>
</div>
</div>
</CardContent>
</Card>
)
}
async function Announcements() {
const resp = await listAnnouncements({
export default async function DashboardPage(props: DashboardPageProps) {
const resp = await listInitialization({
page: 1,
size: 5,
})
if (!resp.success) {
return (
<div className={`col-start-4 row-start-3 row-span-2 flex justify-center items-center`}>
<div className="col-start-4 row-start-3 row-span-2 flex justify-center items-center">
</div>
)
}
const announcements = resp.data.list
const initData = resp.data
return (
<Card className={`col-start-4 row-start-3 row-span-2`}>
<Page className={merge(
`flex-auto grid`,
`grid-cols-[minmax(0,1fr)_minmax(0,1fr)_minmax(0,1fr)_300px]`,
`grid-rows-[150px_200px_minmax(150px,1fr)_minmax(150px,1fr)]`,
)}
>
{/* banner */}
<section className="col-start-1 row-start-1 col-span-3 relative rounded-lg overflow-hidden">
<Image src={banner} alt="banner image" className="w-full h-full inset-0 absolute object-cover"/>
<div className="flex flex-col absolute inset-0 justify-center px-8 gap-1">
<h3 className="text-2xl text-primary font-medium">IP资源</h3>
<p className="text-primary font-medium">//IP代理</p>
</div>
</section>
{/* 磁贴集 */}
{initData && (
<Pins
short_term={String(initData.short_term)}
short_term_monthly={String(initData.short_term_monthly)}
long_term={String(initData.long_term)}
long_term_monthly={String(initData.long_term_monthly)}
/>
)}
{/* 图表 */}
<Charts/>
{/* 信息 */}
<UserCenter/>
{/* 通知 */}
{initData && (
<Announcements list={initData.list}/>
)}
</Page>
)
}
type DashboardChartProps = {
short_term: string
short_term_monthly: string
long_term: string
long_term_monthly: string
}
function Pins(props: DashboardChartProps) {
return (
<>
{/* 短效 */}
<Card className="col-start-1 row-start-2 py-4">
<CardHeader>
<CardTitle>
<Image src={mask} alt="Mask group" width={35} height={35} priority/>
{' '}
</CardTitle>
</CardHeader>
<CardContent className="flex-auto flex flex-col gap-2">
<div className="flex-1 flex items-center justify-between">
<h4></h4>
<p className="flex flex-col items-end">
<span className="text-sm text-weak"></span>
<span className="text-lg">{props.short_term ? props.short_term : '1'}</span>
</p>
</div>
<div className="border-b"></div>
<div className="flex-1 flex items-center justify-between">
<h4></h4>
<p className="flex flex-col items-end">
<span className="text-sm text-weak"></span>
<span className="text-lg">{props.short_term_monthly}</span>
</p>
</div>
</CardContent>
</Card>
{/* 长效 */}
<Card className="col-start-2 row-start-2">
<CardHeader>
<CardTitle>
<Image src={mask} alt="Mask group" width={35} height={35} priority/>
{' '}
</CardTitle>
</CardHeader>
<CardContent className="flex-auto flex flex-col gap-2">
{/* <Image alt={`coming soon`} src={soon} />
<p>敬请期待</p> */}
<div className="flex-1 flex items-center justify-between">
<h4></h4>
<p className="flex flex-col items-end">
<span className="text-sm text-weak"></span>
<span className="text-lg">{props.long_term}</span>
</p>
</div>
<div className="border-b"></div>
<div className="flex-1 flex items-center justify-between">
<h4></h4>
<p className="flex flex-col items-end">
<span className="text-sm text-weak"></span>
<span className="text-lg">{props.long_term_monthly}</span>
</p>
</div>
</CardContent>
</Card>
{/* 固定 */}
<Card className="col-start-3 row-start-2 py-4">
<CardHeader className="px-4">
<CardTitle>IP套餐</CardTitle>
</CardHeader>
<CardContent className="flex-auto flex flex-col gap-2 items-center justify-center">
<Image alt="coming soon" src={soon}/>
<p></p>
</CardContent>
</Card>
</>
)
}
type Props = {
list: {
id: number
title: string
created_at: Date
} []
}
function Announcements(props: Props) {
return (
<Card className="col-start-4 row-start-3 row-span-2">
<CardHeader>
<div className={`flex justify-between gap-2`}>
<div className="flex justify-between gap-2">
<CardTitle></CardTitle>
<span className={`text-sm text-weak`}></span>
<span className="text-sm text-weak"></span>
</div>
</CardHeader>
<CardContent className={`flex-auto p-0`}>
{announcements.length === 0
<CardContent className="flex-auto p-0">
{!props.list.length
? (
<div className={`flex flex-col items-center justify-center gap-2 h-full`}>
{/* <Image alt={`coming soon`} src={soon} />
<p>暂无公告</p> */}
</div>
)
: announcements.map(item => (
<div key={item.id} className={merge(
`transition-colors duration-150 ease-in-out`,
`flex flex-col gap-1 px-4 py-2`,
`hover:bg-muted cursor-pointer`,
)}>
<h4>{item.title}</h4>
<p className={`text-sm text-weak`}>{format(item.created_at, 'yyyy-MM-dd HH:mm')}</p>
</div>
))
}
<div className="flex flex-col items-center justify-center gap-2 h-full">
<Image alt="coming soon" src={soon}/>
<p></p>
</div>
)
: props.list.map(item => (
<div
key={item.id}
className={merge(
`transition-colors duration-150 ease-in-out`,
`flex flex-col gap-1 px-4 py-2`,
`hover:bg-muted cursor-pointer`,
)}
>
<h4>{item.title}</h4>
<p className="text-sm text-weak">{format(item.created_at, 'yyyy-MM-dd HH:mm')}</p>
</div>
))}
</CardContent>
</Card>
)