引入 husky,并全局重新格式化

This commit is contained in:
2025-06-07 11:49:57 +08:00
parent 05fce179c9
commit c7527177b0
89 changed files with 2140 additions and 1899 deletions

View File

@@ -15,7 +15,7 @@ export default function Page(props: PageProps) {
)
}
function Page1() {
function Page1() {
const params = useSearchParams()
const success = params.get('success') === 'true'
const id = params.get('id') || ''
@@ -27,7 +27,7 @@ export default function Page(props: PageProps) {
useEffect(() => {
if (success) {
IdentifyCallback({id}).then(resp => {
IdentifyCallback({id}).then((resp) => {
if (!resp.success) {
setResult({
status: 'fail',
@@ -55,28 +55,34 @@ export default function Page(props: PageProps) {
}, [])
return (
<div className={`w-full min-h-screen flex justify-center items-center bg-blue-50`}>
<div className="w-full min-h-screen flex justify-center items-center bg-blue-50">
<Card className="w-full max-w-xs border-none shadow-none bg-white -translate-y-1/3">
<CardContent className="flex flex-col items-center gap-4 py-6">
{result.status === 'load' ? (<>
<Loader2 className="w-16 h-16 text-primary animate-spin"/>
<p className={`text-primary text-xl`}>{result.message}</p>
<p className={`text-weak text-sm`}>
</p>
</>) : result.status === 'done' ? (<>
<CheckCircle className="w-16 h-16 text-done"/>
<p className={`text-done text-xl`}>{result.message}</p>
<p className={`text-weak text-sm`}>
</p>
</>) : (<>
<AlertCircle className="w-16 h-16 text-fail"/>
<p className={`text-fail text-xl`}>{result.message}</p>
<p className={`text-weak text-sm`}>
</p>
</>)}
{result.status === 'load' ? (
<>
<Loader2 className="w-16 h-16 text-primary animate-spin"/>
<p className="text-primary text-xl">{result.message}</p>
<p className="text-weak text-sm">
</p>
</>
) : result.status === 'done' ? (
<>
<CheckCircle className="w-16 h-16 text-done"/>
<p className="text-done text-xl">{result.message}</p>
<p className="text-weak text-sm">
</p>
</>
) : (
<>
<AlertCircle className="w-16 h-16 text-fail"/>
<p className="text-fail text-xl">{result.message}</p>
<p className="text-weak text-sm">
</p>
</>
)}
</CardContent>
</Card>
</div>

View File

@@ -50,7 +50,7 @@ export async function GET(req: NextRequest) {
const body = JSON.stringify(params)
return NextResponse.json(body)
case 'text':
const text = result.data.map(item => {
const text = result.data.map((item) => {
const list = [item.host, item.port]
if (item.username && item.password) {
list.push(item.username)

View File

@@ -14,7 +14,7 @@ function generateCaptchaText(length: number = 4): string {
}
// 哈希验证码文本并使用随机盐值
function hashCaptcha(text: string): { hash: string, salt: string } {
function hashCaptcha(text: string): {hash: string, salt: string} {
const salt = crypto.randomBytes(16).toString('hex')
const hash = crypto
.createHmac('sha256', salt)

View File

@@ -63,7 +63,7 @@ export default function Captcha(props: CaptchaProps) {
<Input
placeholder="请输入图形验证码"
value={captchaCode}
onChange={(e) => setCaptchaCode(e.target.value)}
onChange={e => setCaptchaCode(e.target.value)}
className="w-full"
/>
</div>

View File

@@ -1,6 +1,5 @@
import { ReactNode } from 'react'
import { Metadata } from 'next'
import {ReactNode} from 'react'
import {Metadata} from 'next'
export async function generateMetadata(): Promise<Metadata> {
return {
@@ -9,9 +8,9 @@ export async function generateMetadata(): Promise<Metadata> {
}
export type LoginLayoutProps = {
children: ReactNode
children: ReactNode
}
export default async function LoginLayout(props: LoginLayoutProps) {
return props.children
}
}

View File

@@ -193,7 +193,7 @@ export default function LoginPage(props: LoginPageProps) {
const params = useSearchParams()
const redirect = params.get('redirect')
const refreshProfile = useProfileStore(store=>store.refreshProfile)
const refreshProfile = useProfileStore(store => store.refreshProfile)
// ======================
// render
@@ -205,9 +205,9 @@ export default function LoginPage(props: LoginPageProps) {
`h-screen w-screen xl:pr-64 bg-cover bg-left`,
`flex justify-center xl:justify-end items-center`,
)}>
<Image src={bg} alt={`背景图`} fill priority className={`absolute -z-10 object-cover`}/>
<Image src={bg} alt="背景图" fill priority className="absolute -z-10 object-cover"/>
<Image src={logo} alt={`logo`} priority height={64} className={`absolute top-8 left-8`}/>
<Image src={logo} alt="logo" priority height={64} className="absolute top-8 left-8"/>
{/* 登录表单 */}
<Card className="w-96 mx-4 shadow-lg">
@@ -215,9 +215,9 @@ export default function LoginPage(props: LoginPageProps) {
<CardTitle className="text-2xl">/</CardTitle>
</CardHeader>
<CardContent className={`px-8`}>
<CardContent className="px-8">
<Form<FormValues> className="space-y-6" onSubmit={onSubmit} form={form}>
<FormField name="username" label={`手机号码`}>
<FormField name="username" label="手机号码">
{({id, field}) => (
<Input
{...field}
@@ -229,7 +229,7 @@ export default function LoginPage(props: LoginPageProps) {
)}
</FormField>
<FormField name="password" label={`验证码`}>
<FormField name="password" label="验证码">
{({id, field}) => (
<div className="flex space-x-4">
<Input
@@ -277,7 +277,10 @@ export default function LoginPage(props: LoginPageProps) {
</Button>
<p className="text-xs text-center text-gray-500">
<a href="#" className="text-blue-600 hover:text-blue-500"></a><a href="#" className="text-blue-600 hover:text-blue-500"></a>
<a href="#" className="text-blue-600 hover:text-blue-500"></a>
<a href="#" className="text-blue-600 hover:text-blue-500"></a>
</p>
</div>
</Form>

View File

@@ -6,24 +6,24 @@ export default function Footer(props: FooterProps) {
return (
<footer className="bg-gray-900 text-white overflow-hidden">
<Wrap className="flex flex-col px-4 py-8 lg:p-12">
<div className={`flex-auto overflow-hidden flex flex-wrap justify-between`}>
<div className="flex-auto overflow-hidden flex flex-wrap justify-between">
<div className="flex flex-col lg:items-center gap-2 lg:gap-6 max-lg:w-1/2">
<img src="/qrcode.svg" alt="logo" className="flex-none w-20 h-20 sm:w-44 sm:h-44 bg-gray-100"/>
<span className="text-sm "></span>
</div>
<div className={`flex flex-col gap-2 lg:gap-4 max-lg:w-1/2`}>
<div className="flex flex-col gap-2 lg:gap-4 max-lg:w-1/2">
<h3></h3>
<p className={`text-sm text-gray-500 `}>大客户经理:张经理</p>
<p className={`text-sm text-gray-500 `}>/微信:18751847847</p>
<p className={`text-sm text-gray-500 `}>QQ号:800180559</p>
<h3 className={`hidden sm:block`}></h3>
<p className={`text-sm text-gray-500 hidden sm:block`}></p>
<p className={`text-sm text-gray-500 hidden sm:block`}></p>
<h3 className="hidden sm:block"></h3>
<p className="text-sm text-gray-500 hidden sm:block"></p>
<p className="text-sm text-gray-500 hidden sm:block"></p>
</div>
<SiteNavList
title={`站点导航`}
title="站点导航"
items={[
{name: `产品订购`, href: `#`},
{name: `获取代理`, href: `#`},
@@ -33,7 +33,7 @@ export default function Footer(props: FooterProps) {
]}
/>
<SiteNavList
title={`国内代理`}
title="国内代理"
items={[
{name: `短效代理`, href: `#`},
{name: `长效代理`, href: `#`},
@@ -41,14 +41,14 @@ export default function Footer(props: FooterProps) {
]}
/>
<SiteNavList
title={`全球代理`}
title="全球代理"
items={[
{name: `动态代理`, href: `#`},
{name: `静态代理`, href: `#`},
]}
/>
<SiteNavList
title={`使用案例`}
title="使用案例"
items={[
{name: `数据抓取`, href: `#`},
{name: `媒体矩阵`, href: `#`},
@@ -61,7 +61,7 @@ export default function Footer(props: FooterProps) {
]}
/>
<SiteNavList
title={`获取代理`}
title="获取代理"
items={[
{name: `API提取`, href: `#`},
{name: `代码示例`, href: `#`},
@@ -69,7 +69,7 @@ export default function Footer(props: FooterProps) {
]}
/>
<SiteNavList
title={`代理资讯`}
title="代理资讯"
items={[
{name: `产品功能`, href: `#`},
{name: `使用教程`, href: `#`},
@@ -78,14 +78,15 @@ export default function Footer(props: FooterProps) {
/>
</div>
<div className={`flex-none mt-6 pt-6 border-t border-gray-300 flex flex-col items-center`}>
<div className="flex-none mt-6 pt-6 border-t border-gray-300 flex flex-col items-center">
<p className={`text-sm `}>
HTTP仅提供代理IP服务使HTTP从事任何违法犯罪行为HTTP不承担任何法律责任
<a href="#"></a>
</p>
<p className={`text-sm mt-3 `}>
577404-405</p>
577404-405
</p>
<p className={`text-sm mt-3 `}>
B1-11111111
ICP备111111111号-1
@@ -105,7 +106,7 @@ function SiteNavList(props: {
}[]
}) {
return (
<div className={`max-lg:mt-4 w-full lg:w-auto`}>
<div className="max-lg:mt-4 w-full lg:w-auto">
<h3>{props.title}</h3>
<ul
className={[
@@ -114,7 +115,7 @@ function SiteNavList(props: {
].join(' ')}>
{props.items.map((item, index) => (
<li key={index}>
<a href={item.href} className={`text-sm text-gray-500`}>{item.name}</a>
<a href={item.href} className="text-sm text-gray-500">{item.name}</a>
</li>
))}
</ul>

View File

@@ -1,44 +1,41 @@
import Link from "next/link"
import Link from 'next/link'
import Image, {StaticImageData} from 'next/image'
import Wrap from "@/components/wrap"
import Wrap from '@/components/wrap'
import h01 from '@/assets/header/help/01.svg'
import h02 from '@/assets/header/help/02.svg'
import h03 from '@/assets/header/help/03.svg'
import banner from '@/assets/header/help/banner.webp'
export default function HelpMenu() {
return (
<Wrap className="w-full grid grid-cols-4 gap-4 justify-items-center">
<Column
icon={h01}
title="提取IP"
items={[
{ lead: '短效动态IP提取', href: '#' },
{ lead: '长效静态IP提取', href: '#' },
{lead: '短效动态IP提取', href: '#'},
{lead: '长效静态IP提取', href: '#'},
]}
/>
<Column
icon={h02}
title="使用教程"
items={[
{ lead: '快速入手', href: '#' },
{ lead: '代码下载', href: '#' },
{ lead: 'API文档', href: '#' },
{lead: '快速入手', href: '#'},
{lead: '代码下载', href: '#'},
{lead: 'API文档', href: '#'},
]}
/>
<Column
icon={h03}
title="产品功能"
items={[
{ lead: '常见问题', href: '#' },
{ lead: '产品介绍', href: '#' },
{ lead: '行业资讯', href: '#' },
{lead: '常见问题', href: '#'},
{lead: '产品介绍', href: '#'},
{lead: '行业资讯', href: '#'},
]}
/>
<Image src={banner} alt={`banner`} className={``} />
<Image src={banner} alt="banner" className=""/>
</Wrap>
)
}
@@ -54,7 +51,7 @@ function Column(props: {
return (
<div className="flex flex-col gap-4">
<h3 className="font-bold flex gap-3 items-center">
<Image src={props.icon} alt={props.title} className="w-10 h-10" />
<Image src={props.icon} alt={props.title} className="w-10 h-10"/>
<span>{props.title}</span>
</h3>
<ul className=" text-gray-500 text-sm flex flex-col items-end gap-2">

View File

@@ -1,13 +1,13 @@
import Image from "next/image"
import Link from "next/link"
import down from "@/assets/header/down.svg"
import Image from 'next/image'
import Link from 'next/link'
import down from '@/assets/header/down.svg'
export function LinkItem(props: {
text: string
href: string
}) {
return (
<li className={`group relative`}>
<li className="group relative">
<Link
href={props.href}
className={[
@@ -35,7 +35,7 @@ export function MenuItem(props: {
onLeave: () => void
}) {
return (
<li className={`group relative`}>
<li className="group relative">
<button
onPointerEnter={props.onEnter}
onPointerLeave={props.onLeave}
@@ -50,7 +50,7 @@ export function MenuItem(props: {
<span>{props.text}</span>
<Image
src={down}
alt={`drop_menu`}
alt="drop_menu"
className={[
`transition-transform duration-200 ease-in-out`,
props.active
@@ -66,7 +66,7 @@ export function MenuItem(props: {
props.active
? `bg-blue-500`
: 'bg-transparent',
].join(' ')} />
].join(' ')}/>
</li>
)
}
}

View File

@@ -35,26 +35,26 @@ export function Domestic(props: {}) {
<section role="tabpanel" className="flex gap-16 mr-16">
<div className="w-64 flex flex-col">
<h3 className="mb-6 font-bold flex items-center gap-3">
<Image src={prod} alt={`产品`} className={`w-10 h-=10`}/>
<Image src={prod} alt="产品" className="w-10 h-=10"/>
<span></span>
</h3>
<DomesticLink
label={`动态IP`}
desc={`全国300+城市级定位节点`}
href={`/product?type=dynamic`}
label="动态IP"
desc="全国300+城市级定位节点"
href="/product?type=dynamic"
discount={45}
/>
<DomesticLink
label={`长效静态IP`}
desc={`IP 资源覆盖全国`}
href={`/product?type=dynamic`}
label="长效静态IP"
desc="IP 资源覆盖全国"
href="/product?type=dynamic"
discount={45}
/>
<DomesticLink
label={`固定IP`}
desc={`全国300+城市级定位节点`}
href={`/product?type=static`}
label="固定IP"
desc="全国300+城市级定位节点"
href="/product?type=static"
discount={45}
/>
</div>
@@ -85,7 +85,6 @@ export function Oversea(props: {}) {
}
export default function ProductMenu() {
const [type, setType] = useState<TabType>('domestic')
return (
@@ -105,7 +104,7 @@ export default function ProductMenu() {
</div>
<aside className="w-64">
<h3 className="flex gap-3 items-center mb-4">
<Image src={anno} alt={`公告`} className={`w-10 h-10`}/>
<Image src={anno} alt="公告" className="w-10 h-10"/>
<span></span>
</h3>
<div className="flex flex-col gap-2">
@@ -140,14 +139,19 @@ export function DomesticLink(props: {
}
return (
<Link href={props.href} className={merge(
`transition-colors duration-150 ease-in-out`,
`p-4 rounded-lg flex flex-col gap-2 hover:bg-blue-50`,
)} onClick={onClick}>
<Link
href={props.href}
className={merge(
`transition-colors duration-150 ease-in-out`,
`p-4 rounded-lg flex flex-col gap-2 hover:bg-blue-50`,
)}
onClick={onClick}>
<p className="flex gap-2">
<span>{props.label}</span>
<span className="text-orange-500 text-xs text-light px-2 py-1 bg-orange-50 rounded-full">
{props.discount}%
{props.discount}
%
</span>
</p>
<p className="text-gray-400 text-sm">

View File

@@ -1,17 +1,16 @@
import Image from "next/image"
import Wrap from "@/components/wrap"
import s01 from "@/assets/header/solution/01.svg"
import s02 from "@/assets/header/solution/02.svg"
import s03 from "@/assets/header/solution/03.svg"
import s04 from "@/assets/header/solution/04.svg"
import s05 from "@/assets/header/solution/05.svg"
import s06 from "@/assets/header/solution/06.svg"
import s07 from "@/assets/header/solution/07.svg"
import s08 from "@/assets/header/solution/08.svg"
import Image from 'next/image'
import Wrap from '@/components/wrap'
import s01 from '@/assets/header/solution/01.svg'
import s02 from '@/assets/header/solution/02.svg'
import s03 from '@/assets/header/solution/03.svg'
import s04 from '@/assets/header/solution/04.svg'
import s05 from '@/assets/header/solution/05.svg'
import s06 from '@/assets/header/solution/06.svg'
import s07 from '@/assets/header/solution/07.svg'
import s08 from '@/assets/header/solution/08.svg'
import {StaticImageData} from 'next/image'
export default function SolutionMenu() {
return (
<Wrap className="grid grid-cols-4 auto-rows-fr gap-4">
<SolutionItem
@@ -70,7 +69,7 @@ function SolutionItem(props: {
`transition-colors duration-200 hover:bg-blue-50`,
].join(' ')}
>
<Image src={props.icon} alt={props.title} className="w-10 h-10" />
<Image src={props.icon} alt={props.title} className="w-10 h-10"/>
<div className="flex flex-col gap-1">
<h3 className="font-bold">{props.title}</h3>
<p className="text-gray-400 text-sm">{props.desc}</p>

View File

@@ -4,7 +4,7 @@ export type HeaderProps = {}
export default async function Header(props: HeaderProps) {
return (
<header className={`fixed top-0 w-full z-10`}>
<header className="fixed top-0 w-full z-10">
<Provider/>
</header>
)

View File

@@ -6,7 +6,7 @@ export type CollectPageProps = {}
export default function CollectPage(props: CollectPageProps) {
return (
<main className={`mt-20 flex flex-col gap-4`}>
<main className="mt-20 flex flex-col gap-4">
<Wrap className="flex flex-col py-8 gap-8">
<BreadCrumb items={[
{label: 'IP 提取', href: '/collect'},

View File

@@ -8,7 +8,7 @@ export type HomeLayoutProps = {
export default function HomeLayout(props: HomeLayoutProps) {
return (
<div className={`overflow-auto bg-blue-50 flex flex-col items-stretch relative`}>
<div className="overflow-auto bg-blue-50 flex flex-col items-stretch relative">
{/* 页头 */}
<Header/>

View File

@@ -4,25 +4,25 @@ import Image from 'next/image'
export default function Home() {
return (
<main className={`flex flex-col gap-16 lg:gap-32 mb-16 lg:mb-32`}>
<main className="flex flex-col gap-16 lg:gap-32 mb-16 lg:mb-32">
{/* banner */}
<section className={`w-full bg-[url('/banner.webp')] bg-cover bg-[center_right_40%]`}>
<Wrap className={`pt-64 pb-48 max-md:pt-32 max-md:pb-24`}>
<h1 className={`text-4xl`}></h1>
<p className={`mt-10 text-gray-500`}>IP代理服务</p>
<Wrap className="pt-64 pb-48 max-md:pt-32 max-md:pb-24">
<h1 className="text-4xl"></h1>
<p className="mt-10 text-gray-500">IP代理服务</p>
<div className={`mt-24 max-md:mt-14 flex gap-8 max-md:flex-col`}>
<p className={`flex gap-4 items-center`}>
<Image src={`/check.svg`} alt={`checkbox`} width={24} height={24}/>
<div className="mt-24 max-md:mt-14 flex gap-8 max-md:flex-col">
<p className="flex gap-4 items-center">
<Image src="/check.svg" alt="checkbox" width={24} height={24}/>
<span className={`lg:text-lg `}>200+</span>
</p>
<p className={`flex gap-4 items-center`}>
<Image src={`/check.svg`} alt={`checkbox`} width={24} height={24}/>
<p className="flex gap-4 items-center">
<Image src="/check.svg" alt="checkbox" width={24} height={24}/>
<span className={`lg:text-lg `}>300+</span>
</p>
<p className={`flex gap-4 items-center`}>
<Image src={`/check.svg`} alt={`checkbox`} width={24} height={24}/>
<p className="flex gap-4 items-center">
<Image src="/check.svg" alt="checkbox" width={24} height={24}/>
<span className={`lg:text-lg `}>&</span>
</p>
</div>
@@ -38,55 +38,62 @@ export default function Home() {
</section>
{/* 数据展示 */}
<Section title={`覆盖全国的IP资源及超大的带宽线路`}>
<ul className={`shadow-[0_0_20px_4px] shadow-blue-50 p-8 flex max-lg:flex-col`}>
<li className={`flex-1 flex flex-col items-center justify-center lg:border-r max-lg:mb-4 border-gray-200`}>
<p className={`text-xl`}>线</p>
<p className={`mt-9 max-lg:mt-2 text-5xl bg-gradient-to-t from-blue-500 to-cyan-400 bg-clip-text text-transparent font-bold pb-2 -mb-2`}>350+</p>
<div className={`lg:hidden w-24 border-b mt-4 border-gray-200`}></div>
<Section title="覆盖全国的IP资源及超大的带宽线路">
<ul className="shadow-[0_0_20px_4px] shadow-blue-50 p-8 flex max-lg:flex-col">
<li className="flex-1 flex flex-col items-center justify-center lg:border-r max-lg:mb-4 border-gray-200">
<p className="text-xl">线</p>
<p className="mt-9 max-lg:mt-2 text-5xl bg-gradient-to-t from-blue-500 to-cyan-400 bg-clip-text text-transparent font-bold pb-2 -mb-2">350+</p>
<div className="lg:hidden w-24 border-b mt-4 border-gray-200"></div>
</li>
<li className={`flex-1 flex flex-col items-center justify-center lg:border-r max-lg:mb-4 border-gray-200`}>
<p className={`text-xl`}>IP数量</p>
<p className={`mt-9 max-lg:mt-2 text-5xl bg-gradient-to-t from-blue-500 to-cyan-400 bg-clip-text text-transparent font-bold pb-2 -mb-2`}>1,350,129</p>
<div className={`lg:hidden w-24 border-b mt-4 border-gray-200`}></div>
<li className="flex-1 flex flex-col items-center justify-center lg:border-r max-lg:mb-4 border-gray-200">
<p className="text-xl">IP数量</p>
<p className="mt-9 max-lg:mt-2 text-5xl bg-gradient-to-t from-blue-500 to-cyan-400 bg-clip-text text-transparent font-bold pb-2 -mb-2">1,350,129</p>
<div className="lg:hidden w-24 border-b mt-4 border-gray-200"></div>
</li>
<li className={`flex-1 flex flex-col items-center justify-center lg:border-r max-lg:mb-4 border-gray-200`}>
<p className={`text-xl`}></p>
<p className={`mt-9 max-lg:mt-2 text-5xl bg-gradient-to-t from-blue-500 to-cyan-400 bg-clip-text text-transparent font-bold pb-2 -mb-2`}>26,578</p>
<div className={`lg:hidden w-24 border-b mt-4 border-gray-200`}></div>
<li className="flex-1 flex flex-col items-center justify-center lg:border-r max-lg:mb-4 border-gray-200">
<p className="text-xl"></p>
<p className="mt-9 max-lg:mt-2 text-5xl bg-gradient-to-t from-blue-500 to-cyan-400 bg-clip-text text-transparent font-bold pb-2 -mb-2">26,578</p>
<div className="lg:hidden w-24 border-b mt-4 border-gray-200"></div>
</li>
<li className={`flex-1 flex flex-col items-center justify-center`}>
<p className={`text-xl`}>IP可用率</p>
<p className={`mt-9 max-lg:mt-2 text-5xl bg-gradient-to-t from-blue-500 to-cyan-400 bg-clip-text text-transparent font-bold pb-2 -mb-2`}>99%</p>
<li className="flex-1 flex flex-col items-center justify-center">
<p className="text-xl">IP可用率</p>
<p className="mt-9 max-lg:mt-2 text-5xl bg-gradient-to-t from-blue-500 to-cyan-400 bg-clip-text text-transparent font-bold pb-2 -mb-2">99%</p>
</li>
</ul>
<img src={`/map.webp`} alt={`map`} className="w-[1200px]"/>
<img src="/map.webp" alt="map" className="w-[1200px]"/>
</Section>
{/* 优势 1 */}
<Section title={`HTTP安全合规的代理IP资源池`}>
<Section title="HTTP安全合规的代理IP资源池">
<ul
className={[
`grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8`,
].join(' ')}>
<Sec3Item
icon={`s1-1`} title={`短期动态IP池`} terms={[
icon="s1-1"
title="短期动态IP池"
terms={[
{icon: `s1-check`, text: `IP时效3-30分钟(可定制)`},
{icon: `s1-check`, text: `支持高并发提取`},
]}/>
<Sec3Item
icon={`s1-2`} title={`长期静态IP池`} terms={[
icon="s1-2"
title="长期静态IP池"
terms={[
{icon: `s1-check`, text: `IP覆盖全国各地`},
{icon: `s1-check`, text: `平均响应时长0.03s`},
]}/>
<Sec3Item
icon={`s1-3`} title={`固定IP池`} terms={[
icon="s1-3"
title="固定IP池"
terms={[
{icon: `s1-check`, text: `稳定长输不掉线`},
{icon: `s1-check`, text: `全国热门静态IP线路`},
]}/>
<Sec3Item
icon={`s1-4`} title={`企业级定制池`} terms={[
icon="s1-4"
title="企业级定制池"
terms={[
{icon: `s1-check`, text: `可视化监控设计`},
{icon: `s1-check`, text: `技术团队现场支持`},
]}/>
@@ -94,31 +101,31 @@ export default function Home() {
</Section>
{/* 优势 2 */}
<Section title={`HTTP 产品优势`}>
<div className={`flex gap-36`}>
<ul className={`flex-1 flex flex-col gap-6`}>
<Sec4Item icon={`s4-1-1`} title={`安全合规`} description={`国内三大运营商支持`}/>
<Sec4Item icon={`s4-1-2`} title={`稳定链接`} description={`IP纯净度高达99.9%`}/>
<Sec4Item icon={`s4-1-3`} title={`超匿名性`} description={`稳定传输,保护隐私安全`}/>
<Section title="HTTP 产品优势">
<div className="flex gap-36">
<ul className="flex-1 flex flex-col gap-6">
<Sec4Item icon="s4-1-1" title="安全合规" description="国内三大运营商支持"/>
<Sec4Item icon="s4-1-2" title="稳定链接" description="IP纯净度高达99.9%"/>
<Sec4Item icon="s4-1-3" title="超匿名性" description="稳定传输,保护隐私安全"/>
</ul>
<img src={`/s4-1-main.webp`} alt={`s2-1-main`} className={`w-0 flex-1 object-contain max-lg:hidden`}/>
<img src="/s4-1-main.webp" alt="s2-1-main" className="w-0 flex-1 object-contain max-lg:hidden"/>
</div>
<div className={`flex gap-36`}>
<img src={`/s4-2-main.webp`} alt={`s2-1-main`} className={`w-0 flex-1 object-contain max-lg:hidden`}/>
<ul className={`flex-1 flex flex-col gap-6`}>
<Sec4Item icon={`s4-2-1`} title={`API接口文档`} description={`与第三方软件轻松集成`}/>
<Sec4Item icon={`s4-2-2`} title={`多种编程语言代码`} description={`C语言、GO语言、Python...`}/>
<Sec4Item icon={`s4-2-3`} title={`双重认证方式`} description={`API提取+账密认证`}/>
<div className="flex gap-36">
<img src="/s4-2-main.webp" alt="s2-1-main" className="w-0 flex-1 object-contain max-lg:hidden"/>
<ul className="flex-1 flex flex-col gap-6">
<Sec4Item icon="s4-2-1" title="API接口文档" description="与第三方软件轻松集成"/>
<Sec4Item icon="s4-2-2" title="多种编程语言代码" description="C语言、GO语言、Python..."/>
<Sec4Item icon="s4-2-3" title="双重认证方式" description="API提取+账密认证"/>
</ul>
</div>
</Section>
{/* 行业资讯 */}
<Section title={`行业资讯`}>
<div className={`flex gap-8 max-md:gap-4`}>
<button className={`px-4 max-md:-mx-4`}>
<img src={`/next.svg`} alt={`prev`} className={`rotate-180`}/>
<Section title="行业资讯">
<div className="flex gap-8 max-md:gap-4">
<button className="px-4 max-md:-mx-4">
<img src="/next.svg" alt="prev" className="rotate-180"/>
</button>
<div
@@ -126,26 +133,26 @@ export default function Home() {
`shadow-[4px_4px_20px_4px] shadow-blue-50 rounded-lg`,
`flex p-14 md:gap-14 max-md:flex-col max-md:p-4`,
].join(' ')}>
<img src="/s3-main.webp" alt="tumb" className={`w-2/3 md:flex-1 md:w-0 object-cover max-md:self-center`}/>
<div className={`flex-2 flex flex-col justify-between gap-4`}>
<h3 className={`flex justify-between`}>
<span className={`text-xl`}></span>
<sub className={`text-sm text-gray-500`}>2025-03-04</sub>
<img src="/s3-main.webp" alt="tumb" className="w-2/3 md:flex-1 md:w-0 object-cover max-md:self-center"/>
<div className="flex-2 flex flex-col justify-between gap-4">
<h3 className="flex justify-between">
<span className="text-xl"></span>
<sub className="text-sm text-gray-500">2025-03-04</sub>
</h3>
<p className={`text-gray-400 md:leading-12`}>
<p className="text-gray-400 md:leading-12">
...
</p>
<div className={`flex justify-end`}>
<a href="#" className={`text-sm text-gray-500 flex items-center gap-4`}>
<div className="flex justify-end">
<a href="#" className="text-sm text-gray-500 flex items-center gap-4">
<img src={`/next.svg`} alt={`more`} className={`h-4 fill-gray-400`}/>
<img src="/next.svg" alt="more" className="h-4 fill-gray-400"/>
</a>
</div>
</div>
</div>
<button className={`px-4 max-md:-mx-4`}>
<img src={`/next.svg`} alt={`prev`}/>
<button className="px-4 max-md:-mx-4">
<img src="/next.svg" alt="prev"/>
</button>
</div>
</Section>
@@ -157,11 +164,10 @@ function Section(props: {
title: string
children: ReactNode
}) {
return (
<section>
<div className={`max-w-[1232px] mx-auto px-4 flex flex-col items-stretch`}>
<h2 className={`text-center text-3xl mb-8 lg:mb-24`}>{props.title}</h2>
<div className="max-w-[1232px] mx-auto px-4 flex flex-col items-stretch">
<h2 className="text-center text-3xl mb-8 lg:mb-24">{props.title}</h2>
{props.children}
</div>
</section>
@@ -182,13 +188,13 @@ function Sec3Item(props: {
`p-8 flex flex-col gap-5 shadow-[4px_4px_20px_4px] shadow-blue-50 bg-white rounded-lg`,
`max-md:items-center`,
].join(' ')}>
<img src={`/${props.icon}.webp`} alt={`s1-1`} aria-hidden className="w-44 h-44 object-cover"/>
<h3 className={`text-xl`}>{props.title}</h3>
<div className={`flex flex-col gap-3`}>
<img src={`/${props.icon}.webp`} alt="s1-1" aria-hidden className="w-44 h-44 object-cover"/>
<h3 className="text-xl">{props.title}</h3>
<div className="flex flex-col gap-3">
{props.terms.map((item, index) => {
return (
<p key={index} className={`text-sm text-gray-500 flex gap-3 items-center`}>
<img src={`/${item.icon}.svg`} alt={`check`} aria-hidden className={`w-5 h-5`}/>
<p key={index} className="text-sm text-gray-500 flex gap-3 items-center">
<img src={`/${item.icon}.svg`} alt="check" aria-hidden className="w-5 h-5"/>
<span>{item.text}</span>
</p>
)
@@ -204,10 +210,10 @@ function Sec4Item(props: {
description: string
}) {
return (
<li className={`flex gap-8 items-center p-4 lg:p-8 shadow-[4px_4px_20px_4px] shadow-blue-50 rounded-lg`}>
<img src={`/${props.icon}.webp`} alt={`s2-1-1`} aria-hidden className="w-24 h-24 object-contain"/>
<div className={`flex flex-col gap-3`}>
<h3 className={`text-xl`}>{props.title}</h3>
<li className="flex gap-8 items-center p-4 lg:p-8 shadow-[4px_4px_20px_4px] shadow-blue-50 rounded-lg">
<img src={`/${props.icon}.webp`} alt="s2-1-1" aria-hidden className="w-24 h-24 object-contain"/>
<div className="flex flex-col gap-3">
<h3 className="text-xl">{props.title}</h3>
<p>{props.description}</p>
</div>
</li>

View File

@@ -6,7 +6,7 @@ export type ProductPageProps = {}
export default function ProductPage(props: ProductPageProps) {
return (
<main className={`mt-20`}>
<main className="mt-20">
<Wrap className="flex flex-col py-8 gap-4">
<BreadCrumb items={[
{label: '产品中心', href: '/product'},
@@ -16,4 +16,3 @@ export default function ProductPage(props: ProductPageProps) {
</main>
)
}

View File

@@ -36,26 +36,26 @@ async function UserCenter() {
>
{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>
</>
)
<>
<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>
</>
)}
<>
<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>

View File

@@ -168,24 +168,24 @@ function Announcements(props: Props) {
<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>
)
<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>
))}
<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>
)

View File

@@ -7,46 +7,50 @@ import Image from 'next/image'
import logoAvatar from '../_assets/logo-avatar.svg'
import logoText from '../_assets/logo-text.svg'
import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from '@/components/ui/tooltip'
import { UserRound } from 'lucide-react'
import { UserRoundPen } from 'lucide-react'
import { IdCard } from 'lucide-react'
import { LockKeyhole } from 'lucide-react'
import { Wallet } from 'lucide-react'
import { ShoppingCart } from 'lucide-react'
import { Package } from 'lucide-react'
import { HardDriveUpload } from 'lucide-react'
import { Eye } from 'lucide-react'
import { Archive } from 'lucide-react'
import { ArchiveRestore } from 'lucide-react'
import {UserRound} from 'lucide-react'
import {UserRoundPen} from 'lucide-react'
import {IdCard} from 'lucide-react'
import {LockKeyhole} from 'lucide-react'
import {Wallet} from 'lucide-react'
import {ShoppingCart} from 'lucide-react'
import {Package} from 'lucide-react'
import {HardDriveUpload} from 'lucide-react'
import {Eye} from 'lucide-react'
import {Archive} from 'lucide-react'
import {ArchiveRestore} from 'lucide-react'
export type NavbarProps = {}
export default function Navbar(props: NavbarProps) {
const navbar = useLayoutStore(store => store.navbar)
return (
<nav data-expand={navbar} className={merge(
`transition-[flex-basis] duration-300 ease-in-out`,
`flex-none`,
`flex flex-col overflow-hidden group`,
`data-[expand=true]:basis-52 data-[expand=false]:basis-16`,
` `,
)}>
{/* logo */}
<Link href={'/'} className={merge(
`flex-none h-[64px] flex items-center justify-center`,
<nav
data-expand={navbar}
className={merge(
`transition-[flex-basis] duration-300 ease-in-out`,
`flex-none`,
`flex flex-col overflow-hidden group`,
`data-[expand=true]:basis-52 data-[expand=false]:basis-16`,
` `,
)}>
<Image src={logoAvatar} alt={`logo`} className={`w-10 h-10 object-contain`}/>
<Image src={logoText} alt={`logo`} className={merge(
`h-10 translate-1 object-cover object-left`,
`transition-[opacity,width] duration-[200ms,300ms] ease-in-out`,
`group-data-[expand=true]:delay-[100ms,0ms]`,
`group-data-[expand=true]:opacity-100 group-data-[expand=false]:opacity-0`,
`group-data-[expand=true]:w-[85px] group-data-[expand=false]:w-0`,
)}/>
{/* logo */}
<Link
href="/"
className={merge(
`flex-none h-[64px] flex items-center justify-center`,
)}>
<Image src={logoAvatar} alt="logo" className="w-10 h-10 object-contain"/>
<Image
src={logoText}
alt="logo"
className={merge(
`h-10 translate-1 object-cover object-left`,
`transition-[opacity,width] duration-[200ms,300ms] ease-in-out`,
`group-data-[expand=true]:delay-[100ms,0ms]`,
`group-data-[expand=true]:opacity-100 group-data-[expand=false]:opacity-0`,
`group-data-[expand=true]:w-[85px] group-data-[expand=false]:w-0`,
)}/>
</Link>
{/* routes */}
@@ -56,20 +60,20 @@ export default function Navbar(props: NavbarProps) {
`group-data-[expand=true]:px-4 group-data-[expand=false]:px-3`,
)}>
<TooltipProvider>
<NavItem href={'/admin'} icon={<UserRound size={20}/>} label={`账户总览`} expand={navbar}/>
<NavTitle label={`个人信息`}/>
<NavItem href={`/admin/profile`} icon={<UserRoundPen size={20}/>} label={`个人中心`} expand={navbar}/>
<NavItem href={`/admin/identify`} icon={<IdCard size={20}/>} label={`实名认证`} expand={navbar}/>
<NavItem href={`/admin/whitelist`} icon={<LockKeyhole size={20}/>} label={`白名单`} expand={navbar}/>
<NavItem href={`/admin/bills`} icon={<Wallet size={20}/>} label={`我的账单`} expand={navbar}/>
<NavTitle label={`套餐管理`}/>
<NavItem href={`/admin/purchase`} icon={<ShoppingCart size={20}/>} label={`购买套餐`} expand={navbar}/>
<NavItem href={`/admin/resources`} icon={<Package size={20}/>} label={`套餐管理`} expand={navbar}/>
<NavTitle label={`IP 管理`}/>
<NavItem href={`/admin/extract`} icon={<HardDriveUpload size={20}/>} label={`提取 IP`} expand={navbar}/>
<NavItem href={`/admin/channels`} icon={<Eye size={20}/>} label={`IP 管理`} expand={navbar}/>
<NavItem href={`/admin`} icon={<Archive size={20}/>} label={`提取记录`} expand={navbar}/>
<NavItem href={`/admin`} icon={<ArchiveRestore size={20}/>} label={`使用记录`} expand={navbar}/>
<NavItem href="/admin" icon={<UserRound size={20}/>} label="账户总览" expand={navbar}/>
<NavTitle label="个人信息"/>
<NavItem href="/admin/profile" icon={<UserRoundPen size={20}/>} label="个人中心" expand={navbar}/>
<NavItem href="/admin/identify" icon={<IdCard size={20}/>} label="实名认证" expand={navbar}/>
<NavItem href="/admin/whitelist" icon={<LockKeyhole size={20}/>} label="白名单" expand={navbar}/>
<NavItem href="/admin/bills" icon={<Wallet size={20}/>} label="我的账单" expand={navbar}/>
<NavTitle label="套餐管理"/>
<NavItem href="/admin/purchase" icon={<ShoppingCart size={20}/>} label="购买套餐" expand={navbar}/>
<NavItem href="/admin/resources" icon={<Package size={20}/>} label="套餐管理" expand={navbar}/>
<NavTitle label="IP 管理"/>
<NavItem href="/admin/extract" icon={<HardDriveUpload size={20}/>} label="提取 IP" expand={navbar}/>
<NavItem href="/admin/channels" icon={<Eye size={20}/>} label="IP 管理" expand={navbar}/>
<NavItem href="/admin" icon={<Archive size={20}/>} label="提取记录" expand={navbar}/>
<NavItem href="/admin" icon={<ArchiveRestore size={20}/>} label="使用记录" expand={navbar}/>
</TooltipProvider>
</section>
</nav>
@@ -89,11 +93,14 @@ function NavTitle(props: {
<span className={merge(
`transition-[opacity] duration-200 ease-in-out absolute mx-4`,
`group-data-[expand=true]:delay-100 group-data-[expand=true]:opacity-100 group-data-[expand=false]:opacity-0`,
)}>{props.label}</span>
)}>
{props.label}
</span>
<span className={merge(
`transition-[opacity] duration-200 ease-in-out absolute w-full border-b block`,
`group-data-[expand=false]:delay-100 group-data-[expand=false]:opacity-100 group-data-[expand=true]:opacity-0`,
)}></span>
)}>
</span>
</p>
)
}
@@ -104,7 +111,6 @@ function NavItem(props: {
label: string
expand?: boolean
}) {
const [open, setOpen] = useState(false)
const handleOpenChange = (open: boolean) => {
@@ -116,22 +122,26 @@ function NavItem(props: {
return (
<Tooltip open={open} onOpenChange={handleOpenChange}>
<TooltipTrigger asChild>
<Link className={merge(
`transition-[padding] duration-300 ease-in-out`,
`flex items-center rounded-md gap-2 whitespace-nowrap`,
`hover:bg-gray-100`,
`group-data-[expand=true]:px-4`,
)} href={props.href}>
<span className={`flex-none w-10 h-10 flex items-center justify-center`}>{props.icon}</span>
<Link
className={merge(
`transition-[padding] duration-300 ease-in-out`,
`flex items-center rounded-md gap-2 whitespace-nowrap`,
`hover:bg-gray-100`,
`group-data-[expand=true]:px-4`,
)}
href={props.href}>
<span className="flex-none w-10 h-10 flex items-center justify-center">{props.icon}</span>
<span className={merge(
`flex-auto`,
`transition-[width,opacity] duration-300 ease-in-out`,
`group-data-[expand=true]:w-auto group-data-[expand=true]:opacity-100`,
`group-data-[expand=false]:w-0 group-data-[expand=false]:opacity-0`,
)}>{props.label}</span>
)}>
{props.label}
</span>
</Link>
</TooltipTrigger>
<TooltipContent side={`right`} sideOffset={16}>
<TooltipContent side="right" sideOffset={16}>
<p>{props.label}</p>
</TooltipContent>
</Tooltip>

View File

@@ -1,6 +1,5 @@
import { ReactNode } from 'react'
import { Metadata } from 'next'
import {ReactNode} from 'react'
import {Metadata} from 'next'
export async function generateMetadata(): Promise<Metadata> {
return {
@@ -9,9 +8,9 @@ export async function generateMetadata(): Promise<Metadata> {
}
export type BillsLayoutProps = {
children: ReactNode
children: ReactNode
}
export default async function BillsLayout(props: BillsLayoutProps) {
return props.children
}
}

View File

@@ -29,7 +29,6 @@ type FilterSchema = zod.infer<typeof filterSchema>
export type BillsPageProps = {}
export default function BillsPage(props: BillsPageProps) {
// ======================
// 查询
// ======================
@@ -95,58 +94,58 @@ export default function BillsPage(props: BillsPageProps) {
<Page>
{/* 操作区 */}
<section className={`flex justify-between flex-wrap`}>
<section className="flex justify-between flex-wrap">
<div>
<h1 className="text-xl font-bold"></h1>
</div>
<Form form={form} onSubmit={onSubmit} className={`flex items-end gap-4`}>
<FormField name={`type`} label={<span className={`text-sm`}></span>}>
<Form form={form} onSubmit={onSubmit} className="flex items-end gap-4">
<FormField name="type" label={<span className="text-sm"></span>}>
{({id, field}) => (
<Select value={field.value} onValueChange={field.onChange}>
<SelectTrigger className={`w-24 h-9`}>
<SelectValue placeholder={`选择类型`}/>
<SelectTrigger className="w-24 h-9">
<SelectValue placeholder="选择类型"/>
</SelectTrigger>
<SelectContent>
<SelectItem value={`all`}></SelectItem>
<SelectItem value={`3`}></SelectItem>
<SelectItem value={`1`}></SelectItem>
<SelectItem value={`2`}>退</SelectItem>
<SelectItem value="all"></SelectItem>
<SelectItem value="3"></SelectItem>
<SelectItem value="1"></SelectItem>
<SelectItem value="2">退</SelectItem>
</SelectContent>
</Select>
)}
</FormField>
<div className={`flex flex-col gap-2`}>
<Label className={`text-sm`}></Label>
<div className={`flex items-center`}>
<FormField name={`create_after`}>
<div className="flex flex-col gap-2">
<Label className="text-sm"></Label>
<div className="flex items-center">
<FormField name="create_after">
{({field}) => (
<DatePicker
{...field}
className={`w-36`}
placeholder={`开始时间`}
format={`yyyy-MM-dd`}
className="w-36"
placeholder="开始时间"
format="yyyy-MM-dd"
/>
)}
</FormField>
<span className={`px-1`}>-</span>
<FormField name={`create_before`}>
<span className="px-1">-</span>
<FormField name="create_before">
{({field}) => (
<DatePicker
{...field}
className={`w-36`}
placeholder={`结束时间`}
format={`yyyy-MM-dd`}
className="w-36"
placeholder="结束时间"
format="yyyy-MM-dd"
/>
)}
</FormField>
</div>
</div>
<Button className={`h-9`} type="submit">
<Button className="h-9" type="submit">
<Search/>
<span></span>
</Button>
<Button theme={`outline`} className={`h-9`} type="button" onClick={() => form.reset()}>
<Button theme="outline" className="h-9" type="button" onClick={() => form.reset()}>
<Eraser/>
<span></span>
</Button>
@@ -173,21 +172,21 @@ export default function BillsPage(props: BillsPageProps) {
accessorKey: 'bill_no', header: `账单编号`,
}, {
accessorKey: 'type', header: `类型`, cell: ({row}) => (
<div className={`flex gap-2 items-center`}>
<div className="flex gap-2 items-center">
{row.original.type === 1 && (
<div className={`flex gap-2 items-center bg-orange-50 w-fit px-2 py-1 rounded-md`}>
<div className="flex gap-2 items-center bg-orange-50 w-fit px-2 py-1 rounded-md">
<CreditCard size={16}/>
<span></span>
</div>
)}
{row.original.type === 2 && (
<div className={`flex gap-2 items-center bg-green-50 w-fit px-2 py-1 rounded-md`}>
<div className="flex gap-2 items-center bg-green-50 w-fit px-2 py-1 rounded-md">
<CreditCard size={16}/>
<span>退</span>
</div>
)}
{row.original.type === 3 && (
<div className={`flex gap-2 items-center bg-blue-50 w-fit px-2 py-1 rounded-md`}>
<div className="flex gap-2 items-center bg-blue-50 w-fit px-2 py-1 rounded-md">
<CreditCard size={16}/>
<span></span>
</div>
@@ -203,28 +202,28 @@ export default function BillsPage(props: BillsPageProps) {
<>
{row.original.trade && (
row.original.trade.status === 0 ? (
<div className={`flex flex-col gap-1`}>
<div className={`flex gap-1 items-center text-warn`}>
<div className="flex flex-col gap-1">
<div className="flex gap-1 items-center text-warn">
<ClockIcon size={16}/>
<span></span>
<Link href={`/admin/bills`} className={`text-sm underline text-blue-500`}>
<Link href="/admin/bills" className="text-sm underline text-blue-500">
{row.original.trade.inner_no}
</Link>
</div>
</div>
) : row.original.trade.status === 1 ? (
<div className={`flex gap-1 items-center text-done`}>
<div className="flex gap-1 items-center text-done">
<CheckCircle size={16}/>
<span></span>
</div>
) : row.original.trade.status === 2 ? (
<div className={`flex gap-1 items-center text-weak`}>
<div className="flex gap-1 items-center text-weak">
<AlertCircle size={16}/>
<span></span>
</div>
) : row.original.trade.status === 3 ? (
<div className={`flex gap-1 items-center text-fail`}>
<div className="flex gap-1 items-center text-fail">
<AlertCircle size={16}/>
<span>退</span>
</div>
@@ -237,15 +236,18 @@ export default function BillsPage(props: BillsPageProps) {
},
{
accessorKey: 'amount', header: `支付信息`, cell: ({row}) => (
<div className={`flex gap-1`}>
<span className={`text-sm`}>
<div className="flex gap-1">
<span className="text-sm">
{!row.original.trade && '余额'}
{row.original.trade && row.original.trade.method === 1 && '支付宝'}
{row.original.trade && row.original.trade.method === 2 && '微信'}
</span>
<span className={
row.original.amount > 0 ? `text-green-400` : `text-orange-400`
}>{row.original.amount}</span>
}>
{row.original.amount}
</span>
</div>
),
},
@@ -255,8 +257,8 @@ export default function BillsPage(props: BillsPageProps) {
),
},
{
accessorKey: 'action', header: `操作`, cell: (item) => (
<div className={`flex gap-2`}>
accessorKey: 'action', header: `操作`, cell: item => (
<div className="flex gap-2">
-
</div>
),

View File

@@ -1,17 +1,16 @@
import { ReactNode } from 'react'
import { Metadata } from 'next'
import {ReactNode} from 'react'
import {Metadata} from 'next'
export async function generateMetadata(): Promise<Metadata> {
return {
title: 'IP管理 - 蓝狐代理',
}
return {
title: 'IP管理 - 蓝狐代理',
}
}
export type ChannelsLayoutProps = {
children: ReactNode
children: ReactNode
}
export default async function ChannelsLayout(props: ChannelsLayoutProps) {
return props.children
}
return props.children
}

View File

@@ -20,7 +20,6 @@ import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from '@/c
export type ChannelsPageProps = {}
export default function ChannelsPage(props: ChannelsPageProps) {
// ======================
// data
// ======================
@@ -88,7 +87,7 @@ export default function ChannelsPage(props: ChannelsPageProps) {
expire_before: undefined,
},
})
const filterHandler = filterForm.handleSubmit(async value => {
const filterHandler = filterForm.handleSubmit(async (value) => {
await refresh(data.page, data.size)
})
@@ -98,46 +97,48 @@ export default function ChannelsPage(props: ChannelsPageProps) {
return (
<Page>
<section className={`flex justify-between`}>
<section className="flex justify-between">
<div></div>
<Form form={filterForm} handler={filterHandler} className={`flex-none flex gap-4 items-end`}>
<FormField<FilterSchema, 'auth_type'> name={`auth_type`} label={<span className={`text-sm`}></span>}>
<Form form={filterForm} handler={filterHandler} className="flex-none flex gap-4 items-end">
<FormField<FilterSchema, 'auth_type'> name="auth_type" label={<span className="text-sm"></span>}>
{({field}) => (
<Select value={field.value} onValueChange={field.onChange}>
<SelectTrigger className={`h-9 w-36`}>
<SelectValue placeholder={`选择认证方式`}/>
<SelectTrigger className="h-9 w-36">
<SelectValue placeholder="选择认证方式"/>
</SelectTrigger>
<SelectContent>
<SelectItem value={'0'}></SelectItem>
<SelectItem value={'1'}>IP </SelectItem>
<SelectItem value={'2'}></SelectItem>
<SelectItem value="0"></SelectItem>
<SelectItem value="1">IP </SelectItem>
<SelectItem value="2"></SelectItem>
</SelectContent>
</Select>
)}
</FormField>
<fieldset className={`flex flex-col gap-2 items-start`}>
<fieldset className="flex flex-col gap-2 items-start">
<div>
<legend className={`block text-sm`}></legend>
<legend className="block text-sm"></legend>
</div>
<div className={`flex gap-1 items-center`}>
<FormField<FilterSchema, 'expire_after'> name={`expire_after`}>
<div className="flex gap-1 items-center">
<FormField<FilterSchema, 'expire_after'> name="expire_after">
{({field}) => (
<DatePicker placeholder={`选择开始时间`} {...field} format={`yyyy-MM-dd`}/>
<DatePicker placeholder="选择开始时间" {...field} format="yyyy-MM-dd"/>
)}
</FormField>
<span>-</span>
<FormField<FilterSchema, 'expire_before'> name={`expire_before`}>
<FormField<FilterSchema, 'expire_before'> name="expire_before">
{({field}) => (
<DatePicker placeholder={`选择结束时间`} {...field} format={`yyyy-MM-dd`}/>
<DatePicker placeholder="选择结束时间" {...field} format="yyyy-MM-dd"/>
)}
</FormField>
</div>
</fieldset>
<Button className={`h-9`}>
<SearchIcon/>
<Button className="h-9">
<SearchIcon/>
</Button>
<Button theme={`outline`} className={`h-9`} onClick={() => filterForm.reset()}>
<EraserIcon/>
<Button theme="outline" className="h-9" onClick={() => filterForm.reset()}>
<EraserIcon/>
</Button>
</Form>
</section>
@@ -149,8 +150,8 @@ export default function ChannelsPage(props: ChannelsPageProps) {
page: data.page,
size: data.size,
total: data.total,
onPageChange: (page) => refresh(page, data.size),
onSizeChange: (size) => refresh(1, size),
onPageChange: page => refresh(page, data.size),
onSizeChange: size => refresh(1, size),
}}
columns={[
{
@@ -158,16 +159,26 @@ export default function ChannelsPage(props: ChannelsPageProps) {
},
{
header: '认证方式', cell: ({row}) => {
return <div className={`flex flex-col gap-1`}>
{row.original.auth_ip && (<>
<span className={`text-weak`}>IP </span>
<span>{row.original.whitelists.replaceAll(",", ", ")}</span>
</>)}
{row.original.auth_pass && (<>
<span className={`text-weak`}></span>
<span>{row.original.username}:{row.original.password}</span>
</>)}
</div>
return (
<div className="flex flex-col gap-1">
{row.original.auth_ip && (
<>
<span className="text-weak">IP </span>
<span>{row.original.whitelists.replaceAll(',', ', ')}</span>
</>
)}
{row.original.auth_pass && (
<>
<span className="text-weak"></span>
<span>
{row.original.username}
:
{row.original.password}
</span>
</>
)}
</div>
)
},
},
{
@@ -181,4 +192,3 @@ export default function ChannelsPage(props: ChannelsPageProps) {
</Page>
)
}

View File

@@ -1,6 +1,5 @@
import { ReactNode } from 'react'
import { Metadata } from 'next'
import {ReactNode} from 'react'
import {Metadata} from 'next'
export async function generateMetadata(): Promise<Metadata> {
return {
@@ -9,9 +8,9 @@ export async function generateMetadata(): Promise<Metadata> {
}
export type ExtractLayoutProps = {
children: ReactNode
children: ReactNode
}
export default async function ExtractLayout(props: ExtractLayoutProps) {
return props.children
}
}

View File

@@ -6,7 +6,7 @@ export type ExtractPageProps = {}
export default async function ExtractPage(props: ExtractPageProps) {
return (
<Page>
<Extract className={`p-8`}/>
<Extract className="p-8"/>
</Page>
)
}

View File

@@ -1,5 +1,5 @@
import { ReactNode } from 'react'
import { Metadata } from 'next'
import {ReactNode} from 'react'
import {Metadata} from 'next'
export async function generateMetadata(): Promise<Metadata> {
return {
@@ -8,9 +8,9 @@ export async function generateMetadata(): Promise<Metadata> {
}
export type IdentifyLayoutProps = {
children: ReactNode
children: ReactNode
}
export default async function IdentifyLayout(props: IdentifyLayoutProps) {
return props.children
}
}

View File

@@ -24,7 +24,6 @@ import {CheckCircle, CheckCircleIcon, WorkflowIcon} from 'lucide-react'
export type IdentifyPageProps = {}
export default function IdentifyPage(props: IdentifyPageProps) {
// ======================
// 填写信息
// ======================
@@ -104,72 +103,72 @@ export default function IdentifyPage(props: IdentifyPageProps) {
// ======================
return (
<Page className={`flex-row`}>
<div className={`flex-3/4 flex flex-col bg-white rounded-lg overflow-hidden gap-16`}>
<Page className="flex-row">
<div className="flex-3/4 flex flex-col bg-white rounded-lg overflow-hidden gap-16">
{/* banner */}
<section className={`flex-none basis-40 relative flex flex-col gap-4 pl-8 justify-center`}>
<Image src={banner} alt={`背景图`} aria-hidden className={`absolute inset-0 w-full h-full object-cover`}/>
<h3 className={`text-lg font-bold z-10 relative`}>HTTP邀请您参与</h3>
<p className={`text-sm text-gray-600 z-10 relative`}></p>
<section className="flex-none basis-40 relative flex flex-col gap-4 pl-8 justify-center">
<Image src={banner} alt="背景图" aria-hidden className="absolute inset-0 w-full h-full object-cover"/>
<h3 className="text-lg font-bold z-10 relative">HTTP邀请您参与</h3>
<p className="text-sm text-gray-600 z-10 relative"></p>
</section>
<div className={`flex-auto flex justify-center items-start`}>
<div className="flex-auto flex justify-center items-start">
{/* 个人认证 */}
<section className={`w-96 bg-gray-50 p-8 rounded-md flex flex-col gap-4 items-center`}>
<Image src={personal} alt={`个人认证`}/>
<section className="w-96 bg-gray-50 p-8 rounded-md flex flex-col gap-4 items-center">
<Image src={personal} alt="个人认证"/>
<div>
<h3 className={`text-center text-lg font-bold`}></h3>
<p className={`text-sm text-gray-600`}>
<h3 className="text-center text-lg font-bold"></h3>
<p className="text-sm text-gray-600">
</p>
</div>
{profile?.id_token ? (
<p className={`flex gap-2 items-center`}>
<CheckCircleIcon className={`text-done`}/>
<p className="flex gap-2 items-center">
<CheckCircleIcon className="text-done"/>
<span></span>
</p>
) : (
<Dialog open={openDialog} onOpenChange={setOpenDialog}>
<DialogTrigger asChild>
<Button className={`w-full`}></Button>
<Button className="w-full"></Button>
</DialogTrigger>
<DialogContent>
<DialogTitle>
{step === 'form' ? `实名认证` : `扫码完成认证`}
</DialogTitle>
{step === 'form' && (
<Form form={form} handler={handler} className={`flex flex-col gap-4`}>
<FormField<Schema> name={`name`} label={`姓名`}>
<Form form={form} handler={handler} className="flex flex-col gap-4">
<FormField<Schema> name="name" label="姓名">
{({id, field}) => (
<input
{...field}
id={id}
placeholder={`请输入姓名`}
className={`border rounded p-2 w-full`}
autoComplete={`name`}
placeholder="请输入姓名"
className="border rounded p-2 w-full"
autoComplete="name"
/>
)}
</FormField>
<FormField<Schema> name={`iden_no`} label={`身份证号`}>
<FormField<Schema> name="iden_no" label="身份证号">
{({id, field}) => (
<input
{...field}
id={id}
placeholder={`请输入身份证号`}
className={`border rounded p-2 w-full`}
placeholder="请输入身份证号"
className="border rounded p-2 w-full"
/>
)}
</FormField>
<DialogFooter>
<Button type={`submit`} className={`w-full mt-4`}></Button>
<Button type="submit" className="w-full mt-4"></Button>
</DialogFooter>
</Form>
)}
{step === 'scan' && (
<div className={`flex flex-col gap-4 items-center`}>
<div className="flex flex-col gap-4 items-center">
<canvas ref={canvas} width={256} height={256}/>
<p className={`text-sm text-gray-600`}></p>
<p className="text-sm text-gray-600"></p>
<Button onClick={async () => {
await refreshProfile()
setOpenDialog(false)
@@ -185,48 +184,51 @@ export default function IdentifyPage(props: IdentifyPageProps) {
</div>
</div>
<Card className={`flex-none basis-80`}>
<Card className="flex-none basis-80">
<CardHeader>
<CardTitle>
<WorkflowIcon size={18}/>
<WorkflowIcon size={18}/>
</CardTitle>
</CardHeader>
<CardContent className={`flex flex-col px-4`}>
<p className={`text-sm text-weak mb-4`}>
<CardContent className="flex flex-col px-4">
<p className="text-sm text-weak mb-4">
使HTTP代理需完成实名认证
</p>
<p className={`flex gap-2 items-center justify-between w-56 self-center`}>
<span className={`flex gap-2`}>
<span className={`bg-primary/25 text-primary w-8 h-8 rounded-full flex items-center justify-center`}>01</span>
<span></span>
</span>
<Image alt={`步骤配图`} src={step1} aria-hidden/>
<p className="flex gap-2 items-center justify-between w-56 self-center">
<span className="flex gap-2">
<span className="bg-primary/25 text-primary w-8 h-8 rounded-full flex items-center justify-center">01</span>
<span></span>
</span>
<Image alt="步骤配图" src={step1} aria-hidden/>
</p>
<div className={`h-16 w-56 px-4 flex self-center`}>
<div className="h-16 w-56 px-4 flex self-center">
<div className={merge(
`w-0 h-full border-x border-primary border-dashed relative`,
`after:absolute after:-left-[3px] after:bottom-0 after:w-[6px] after:h-[6px] after:rounded-full after:bg-primary`,
)}></div>
)}>
</div>
</div>
<p className={`flex gap-2 items-center justify-between w-56 self-center`}>
<span className={`flex gap-2`}>
<span className={`bg-primary/25 text-primary w-8 h-8 rounded-full flex items-center justify-center`}>02</span>
<span></span>
</span>
<Image alt={`步骤配图`} src={step2} aria-hidden/>
<p className="flex gap-2 items-center justify-between w-56 self-center">
<span className="flex gap-2">
<span className="bg-primary/25 text-primary w-8 h-8 rounded-full flex items-center justify-center">02</span>
<span></span>
</span>
<Image alt="步骤配图" src={step2} aria-hidden/>
</p>
<div className={`h-16 w-56 px-4 flex self-center`}>
<div className="h-16 w-56 px-4 flex self-center">
<div className={merge(
`w-0 h-full border-x border-primary border-dashed relative`,
`after:absolute after:-left-[3px] after:bottom-0 after:w-[6px] after:h-[6px] after:rounded-full after:bg-primary`,
)}></div>
)}>
</div>
</div>
<p className={`flex gap-2 items-center justify-between w-56 self-center`}>
<span className={`flex gap-2`}>
<span className={`bg-primary/25 text-primary w-8 h-8 rounded-full flex items-center justify-center`}>03</span>
<span></span>
</span>
<Image alt={`步骤配图`} src={step3} aria-hidden/>
<p className="flex gap-2 items-center justify-between w-56 self-center">
<span className="flex gap-2">
<span className="bg-primary/25 text-primary w-8 h-8 rounded-full flex items-center justify-center">03</span>
<span></span>
</span>
<Image alt="步骤配图" src={step3} aria-hidden/>
</p>
</CardContent>
</Card>

View File

@@ -16,7 +16,7 @@ export default async function AdminLayout(props: AdminLayoutProps) {
<Navbar/>
<div className={`flex-auto flex flex-col items-stretch`}>
<div className="flex-auto flex flex-col items-stretch">
<Header/>
{props.children}
</div>

View File

@@ -1,6 +1,5 @@
import { ReactNode } from 'react'
import { Metadata } from 'next'
import {ReactNode} from 'react'
import {Metadata} from 'next'
export async function generateMetadata(): Promise<Metadata> {
return {
@@ -9,9 +8,9 @@ export async function generateMetadata(): Promise<Metadata> {
}
export type ProfileLayoutProps = {
children: ReactNode
children: ReactNode
}
export default async function ProfileLayout(props: ProfileLayoutProps) {
return props.children
}
}

View File

@@ -31,7 +31,6 @@ import {useRouter} from 'next/navigation'
export type ProfilePageProps = {}
export default function ProfilePage(props: ProfilePageProps) {
const router = useRouter()
const profile = useProfileStore(store => store.profile)
@@ -42,9 +41,9 @@ export default function ProfilePage(props: ProfilePageProps) {
if (!profile) {
return (
<Page>
<div className={`flex flex-col gap-4`}>
<h3 className={`text-lg font-bold`}>...</h3>
<p className={`text-sm text-gray-600`}></p>
<div className="flex flex-col gap-4">
<h3 className="text-lg font-bold">...</h3>
<p className="text-sm text-gray-600"></p>
</div>
</Page>
)
@@ -52,59 +51,63 @@ export default function ProfilePage(props: ProfilePageProps) {
return (
<Page className="flex-row items-start">
<div className={`flex-3/4 flex flex-col gap-4`}>
<div className="flex-3/4 flex flex-col gap-4">
{/* banner */}
<section className={`flex-none basis-40 relative rounded-lg overflow-hidden flex flex-col gap-4 pl-8 justify-center`}>
<Image src={banner} alt={`背景图`} aria-hidden className={`absolute inset-0 w-full h-full object-cover`}/>
<h3 className={`text-lg font-bold z-10 relative`}>HTTP邀请您参与</h3>
<p className={`text-sm text-gray-600 z-10 relative`}></p>
<section className="flex-none basis-40 relative rounded-lg overflow-hidden flex flex-col gap-4 pl-8 justify-center">
<Image src={banner} alt="背景图" aria-hidden className="absolute inset-0 w-full h-full object-cover"/>
<h3 className="text-lg font-bold z-10 relative">HTTP邀请您参与</h3>
<p className="text-sm text-gray-600 z-10 relative"></p>
</section>
{/* 块信息 */}
<div className={`flex-none basis-40 flex gap-4`}>
<div className="flex-none basis-40 flex gap-4">
<Card className={`flex-1`}>
<Card className="flex-1">
<CardHeader>
<CardTitle className={`font-normal`}></CardTitle>
<CardTitle className="font-normal"></CardTitle>
</CardHeader>
<CardContent className={`flex-auto flex justify-between items-center px-8`}>
<p className={`text-xl`}>{profile?.balance}</p>
<CardContent className="flex-auto flex justify-between items-center px-8">
<p className="text-xl">{profile?.balance}</p>
<RechargeModal classNames={{
trigger: `h-10 px-6`,
}}/>
</CardContent>
</Card>
<Card className={`flex-1`}>
<Card className="flex-1">
<CardHeader>
<CardTitle className={`font-normal`}></CardTitle>
<CardTitle className="font-normal"></CardTitle>
</CardHeader>
<CardContent className={`flex-auto flex justify-between items-center px-8`}>
<CardContent className="flex-auto flex justify-between items-center px-8">
{!profile?.id_token
? <>
<p className={`text-sm`}>使</p>
<Button theme={`outline`} className={`mx-16 w-24`} onClick={() => router.push('/admin/identify')}></Button>
</>
: <>
<p className={`flex flex-col gap-1`}>
<span>{profile.name}</span>
<span className={`text-sm`}>{profile.id_no}</span>
</p>
<p className={`flex gap-1 items-center`}>
<CheckCircle className={`text-done`} size={18}/>
<span></span>
</p>
</>}
? (
<>
<p className="text-sm">使</p>
<Button theme="outline" className="mx-16 w-24" onClick={() => router.push('/admin/identify')}></Button>
</>
)
: (
<>
<p className="flex flex-col gap-1">
<span>{profile.name}</span>
<span className="text-sm">{profile.id_no}</span>
</p>
<p className="flex gap-1 items-center">
<CheckCircle className="text-done" size={18}/>
<span></span>
</p>
</>
)}
</CardContent>
</Card>
</div>
<div className={`flex-auto shrink-0 basis-80 rounded-lg bg-white p-4 flex flex-col gap-8`}>
<div className="flex-auto shrink-0 basis-80 rounded-lg bg-white p-4 flex flex-col gap-8">
{/* 安全信息 */}
<div className={`flex flex-col gap-4`}>
<h3 className={`font-normal`}></h3>
<div className={`flex gap-4 items-center`}>
<div className="flex flex-col gap-4">
<h3 className="font-normal"></h3>
<div className="flex gap-4 items-center">
<p>{profile.phone}</p>
<PasswordForm profile={profile}/>
</div>
@@ -133,7 +136,7 @@ function Aftersale(props: {
qrcode.toCanvas(canvasRef.current, String(admin), {
width: 180,
margin: 0,
}).catch(err => {
}).catch((err) => {
console.error(err)
})
}
@@ -143,32 +146,36 @@ function Aftersale(props: {
<Card className="flex-none basis-80">
<CardHeader>
<CardTitle>
<QrCodeIcon size={18}/>
<QrCodeIcon size={18}/>
{' '}
</CardTitle>
</CardHeader>
<CardContent className={`flex flex-col gap-8`}>
<CardContent className="flex flex-col gap-8">
<div className={`flex flex-col gap-4`}>
<p className={`text-weak text-sm`}>
<div className="flex flex-col gap-4">
<p className="text-weak text-sm">
1.100+IP代理资源免费测试使
</p>
<p className={`text-weak text-sm`}>
<p className="text-weak text-sm">
2.线
</p>
<p className={`text-weak text-sm`}>
<p className="text-weak text-sm">
3.1V1专属售后答疑7*24线
</p>
</div>
<div className={`flex flex-col gap-4 items-center`}>
<div className="flex flex-col gap-4 items-center">
<p></p>
<div>
<canvas ref={canvasRef} width="180" height="180" className={`mx-auto bg-muted`}/>
<canvas ref={canvasRef} width="180" height="180" className="mx-auto bg-muted"/>
</div>
<p className="text-xs text-weak">
<br/>
<br/>
</p>
</div>
</CardContent>
@@ -179,7 +186,6 @@ function Aftersale(props: {
function BasicForm(props: {
profile: User
}) {
const schema = z.object({
username: z.string(),
email: z.string().email('请输入正确的邮箱'),
@@ -196,7 +202,7 @@ function BasicForm(props: {
contact_wechat: props.profile.contact_wechat || '',
},
})
const handler = form.handleSubmit(async value => {
const handler = form.handleSubmit(async (value) => {
try {
const resp = await update(value)
if (!resp.success) {
@@ -217,8 +223,8 @@ function BasicForm(props: {
const refreshProfile = useProfileStore(store => store.refreshProfile)
return (
<div className={`flex flex-col gap-4`}>
<h3 className={`font-normal`}></h3>
<div className="flex flex-col gap-4">
<h3 className="font-normal"></h3>
<Form
form={form}
handler={handler}
@@ -227,60 +233,65 @@ function BasicForm(props: {
)}
>
<FormField<Schema>
name={`username`}
label={<span className={`w-full flex justify-end`}></span>}
className={`grid grid-cols-[48px_1fr] grid-rows-[auto_auto] gap-x-4`}
name="username"
label={<span className="w-full flex justify-end"></span>}
className="grid grid-cols-[48px_1fr] grid-rows-[auto_auto] gap-x-4"
classNames={{
message: `col-start-2`,
}}
>
{({field}) => (
<Input {...field} placeholder={`请输入用户名`} className={`w-52`}/>
<Input {...field} placeholder="请输入用户名" className="w-52"/>
)}
</FormField>
<FormField<Schema>
name={`email`}
label={<span className={`w-full flex justify-end`}></span>}
className={`grid grid-cols-[48px_1fr] grid-rows-[auto_auto] gap-x-4`}
name="email"
label={<span className="w-full flex justify-end"></span>}
className="grid grid-cols-[48px_1fr] grid-rows-[auto_auto] gap-x-4"
classNames={{
message: `col-start-2`,
}}
>
{({field}) => (
<Input {...field} placeholder={`请输入邮箱`} className={`w-52`}/>
<Input {...field} placeholder="请输入邮箱" className="w-52"/>
)}
</FormField>
<FormField<Schema>
name={`contact_qq`}
label={<span className={`w-full flex justify-end`}>QQ</span>}
className={`grid grid-cols-[48px_1fr] grid-rows-[auto_auto] gap-x-4`}
name="contact_qq"
label={<span className="w-full flex justify-end">QQ</span>}
className="grid grid-cols-[48px_1fr] grid-rows-[auto_auto] gap-x-4"
classNames={{
message: `col-start-2`,
}}
>
{({field}) => (
<Input {...field} placeholder={`请输入QQ号`} className={`w-52`}/>
<Input {...field} placeholder="请输入QQ号" className="w-52"/>
)}
</FormField>
<FormField<Schema>
name={`contact_wechat`}
label={<span className={`w-full flex justify-end`}></span>}
className={`grid grid-cols-[48px_1fr] grid-rows-[auto_auto] gap-x-4`}
name="contact_wechat"
label={<span className="w-full flex justify-end"></span>}
className="grid grid-cols-[48px_1fr] grid-rows-[auto_auto] gap-x-4"
classNames={{
message: `col-start-2`,
}}
>
{({field}) => (
<Input {...field} placeholder={`请输入微信号`} className={`w-52`}/>
<Input {...field} placeholder="请输入微信号" className="w-52"/>
)}
</FormField>
<div className={`flex justify-end gap-4 col-span-2 justify-self-stretch`}>
<Button theme={`outline`} type={`button`} onClick={() => form.reset({
username: props.profile.username || '',
email: props.profile.email || '',
contact_qq: props.profile.contact_qq || '',
contact_wechat: props.profile.contact_wechat || '',
})}></Button>
<div className="flex justify-end gap-4 col-span-2 justify-self-stretch">
<Button
theme="outline"
type="button"
onClick={() => form.reset({
username: props.profile.username || '',
email: props.profile.email || '',
contact_qq: props.profile.contact_qq || '',
contact_wechat: props.profile.contact_wechat || '',
})}>
</Button>
<Button></Button>
</div>
</Form>
@@ -291,7 +302,6 @@ function BasicForm(props: {
function PasswordForm(props: {
profile: User
}) {
// ======================
// open
// ======================
@@ -324,7 +334,7 @@ function PasswordForm(props: {
confirm_password: '',
},
})
const handler = form.handleSubmit(async value => {
const handler = form.handleSubmit(async (value) => {
try {
const resp = await updatePassword({
phone: value.phone,
@@ -374,7 +384,7 @@ function PasswordForm(props: {
setCaptchaWait(60)
interval.current = setInterval(() => {
setCaptchaWait(wait => {
setCaptchaWait((wait) => {
if (wait <= 1) {
clearInterval(interval.current!)
return 0
@@ -393,38 +403,38 @@ function PasswordForm(props: {
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button theme={`outline`} className={`w-24 h-9`}></Button>
<Button theme="outline" className="w-24 h-9"></Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
<Form form={form} handler={handler} className={`flex flex-col gap-4 mt-4`}>
<Form form={form} handler={handler} className="flex flex-col gap-4 mt-4">
{/* 手机号 */}
<FormField<Schema> name={`phone`} label={`手机号`} className={`flex-auto`}>
<FormField<Schema> name="phone" label="手机号" className="flex-auto">
{({field}) => (
<Input {...field} placeholder={`请输入手机号`} autoComplete={`tel-national`}/>
<Input {...field} placeholder="请输入手机号" autoComplete="tel-national"/>
)}
</FormField>
<FormField<Schema> name={`captcha`} label={`验证码`}>
<FormField<Schema> name="captcha" label="验证码">
{({field}) => (
<div className={`flex gap-4`}>
<Input {...field} placeholder={`请输入验证码`} autoComplete={`one-time-code`}/>
<Button className={`p-0 bg-transparent`} onClick={refreshCaptcha} type={`button`}>
<img src={captchaUrl} alt={`验证码`} className={`h-10`}/>
<div className="flex gap-4">
<Input {...field} placeholder="请输入验证码" autoComplete="one-time-code"/>
<Button className="p-0 bg-transparent" onClick={refreshCaptcha} type="button">
<img src={captchaUrl} alt="验证码" className="h-10"/>
</Button>
</div>
)}
</FormField>
{/* 短信令牌 */}
<FormField<Schema> name={`code`} label={`短信令牌`} className={`flex-auto`}>
<FormField<Schema> name="code" label="短信令牌" className="flex-auto">
{({field}) => (
<div className={`flex gap-4`}>
<Input {...field} placeholder={`请输入验证码`} autoComplete={`one-time-code`}/>
<Button theme={`outline`} type={`button`} className={`w-36`} onClick={() => sendVerifier()}>
<div className="flex gap-4">
<Input {...field} placeholder="请输入验证码" autoComplete="one-time-code"/>
<Button theme="outline" type="button" className="w-36" onClick={() => sendVerifier()}>
{captchaWait > 0
? `重新发送(${captchaWait})`
: `获取短信令牌`
@@ -435,29 +445,35 @@ function PasswordForm(props: {
</FormField>
{/* 新密码 */}
<FormField<Schema> name={`password`} label={`新密码`} className={`flex-auto`}>
<FormField<Schema> name="password" label="新密码" className="flex-auto">
{({field}) => (
<Input {...field} placeholder={`请输入新密码`} type={`password`} autoComplete={`new-password`}/>
<Input {...field} placeholder="请输入新密码" type="password" autoComplete="new-password"/>
)}
</FormField>
{/* 确认密码 */}
<FormField<Schema> name={`confirm_password`} label={`确认密码`} className={`flex-auto`}>
<FormField<Schema> name="confirm_password" label="确认密码" className="flex-auto">
{({field}) => (
<Input {...field} placeholder={`请再次输入新密码`} type={`password`} autoComplete={`new-password`}/>
<Input {...field} placeholder="请再次输入新密码" type="password" autoComplete="new-password"/>
)}
</FormField>
</Form>
<DialogFooter>
<Button theme={`outline`} type={`button`} onClick={() => {
setOpen(false)
form.reset()
}}></Button>
<Button onClick={async e => {
<Button
theme="outline"
type="button"
onClick={() => {
setOpen(false)
form.reset()
}}>
</Button>
<Button onClick={async (e) => {
const result = await handler(e)
}}></Button>
}}>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>

View File

@@ -1,17 +1,16 @@
import { ReactNode } from 'react'
import { Metadata } from 'next'
import {ReactNode} from 'react'
import {Metadata} from 'next'
export async function generateMetadata(): Promise<Metadata> {
return {
title: '购买套餐 - 蓝狐代理',
}
return {
title: '购买套餐 - 蓝狐代理',
}
}
export type PurchaseLayoutProps = {
children: ReactNode
children: ReactNode
}
export default async function PurchaseLayout(props: PurchaseLayoutProps) {
return props.children
}
return props.children
}

View File

@@ -5,7 +5,7 @@ export type PurchasePageProps = {}
export default async function PurchasePage(props: PurchasePageProps) {
return (
<Page className={`flex-col`}>
<Page className="flex-col">
<Purchase/>
</Page>
)

View File

@@ -111,198 +111,221 @@ export default function LongResource(props: LongResourceProps) {
await refresh(1, data.size)
}
return <>
{/* 操作区 */}
<section className={`flex justify-between flex-wrap`}>
<div>
return (
<>
{/* 操作区 */}
<section className="flex justify-between flex-wrap">
<div>
</div>
</div>
<Form form={form} onSubmit={onSubmit} className={`flex items-end gap-4 flex-wrap`}>
<FormField name={`resource_no`} label={<span className={`text-sm`}></span>}>
{({id, field}) => (
<Input {...field} id={id} className={`h-9`}/>
)}
</FormField>
<FormField name={`type`} label={<span className={`text-sm`}></span>}>
{({field}) => (
<Select value={field.value} onValueChange={field.onChange}>
<SelectTrigger className={`w-24 h-9`}>
<SelectValue placeholder={`选择套餐类型`}/>
</SelectTrigger>
<SelectContent>
<SelectItem value={`all`}></SelectItem>
<SelectItem value={`expire`}></SelectItem>
<SelectItem value={`quota`}></SelectItem>
</SelectContent>
</Select>
)}
</FormField>
<div className={`flex flex-col gap-2`}>
<Label className={`text-sm`}></Label>
<div className={`flex items-center`}>
<FormField name={`create_after`}>
{({field}) => (
<DatePicker
{...field}
className={`w-36`}
placeholder={`开始时间`}
format={`yyyy-MM-dd`}
/>
)}
</FormField>
<span className={`px-1`}>-</span>
<FormField name={`create_before`}>
{({field}) => (
<DatePicker
{...field}
className={`w-36`}
placeholder={`结束时间`}
format={`yyyy-MM-dd`}
/>
)}
</FormField>
</div>
</div>
<div className={`flex flex-col gap-2`}>
<Label className={`text-sm`}>使</Label>
<div className={`flex items-center`}>
<FormField name={`expire_after`}>
{({field}) => (
<DatePicker
{...field}
className={`w-36`}
placeholder={`开始时间`}
format={`yyyy-MM-dd`}
/>
)}
</FormField>
<span className={`px-1`}>-</span>
<FormField name={`expire_before`}>
{({field}) => (
<DatePicker
{...field}
className={`w-36`}
placeholder={`结束时间`}
format={`yyyy-MM-dd`}
/>
)}
</FormField>
</div>
</div>
<div className={`flex gap-4`}>
<Button className={`h-9`}>
<Search/>
<span></span>
</Button>
<Button theme={`outline`} className={`h-9`} onClick={() => form.reset({
type: 'all',
resource_no: '',
create_after: undefined,
create_before: undefined,
expire_after: undefined,
expire_before: undefined,
})}>
<Eraser/>
<span></span>
</Button>
</div>
</Form>
</section>
{/* 数据表 */}
<DataTable
data={data.list}
status={status}
pagination={{
total: data.total,
page: data.page,
size: data.size,
onPageChange: async (page: number) => {
await refresh(page, data.size)
},
onSizeChange: async (size: number) => {
await refresh(data.page, size)
},
}}
columns={[
{
accessorKey: 'resource_no', header: `套餐编号`,
},
{
accessorKey: 'type', header: `类型`, cell: ({row}) => (
<div className={`flex gap-2 items-center`}>
{row.original.long.type === 1 && (
<div className={`flex gap-2 items-center bg-green-50 w-fit px-2 py-1 rounded-md`}>
<Timer size={20}/>
<span></span>
</div>
)}
{row.original.long.type === 2 && (
<div className={`flex gap-2 items-center bg-blue-50 w-fit px-2 py-1 rounded-md`}>
<Box size={20}/>
<span></span>
</div>
)}
<Form form={form} onSubmit={onSubmit} className="flex items-end gap-4 flex-wrap">
<FormField name="resource_no" label={<span className="text-sm"></span>}>
{({id, field}) => (
<Input {...field} id={id} className="h-9"/>
)}
</FormField>
<FormField name="type" label={<span className="text-sm"></span>}>
{({field}) => (
<Select value={field.value} onValueChange={field.onChange}>
<SelectTrigger className="w-24 h-9">
<SelectValue placeholder="选择套餐类型"/>
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
<SelectItem value="expire"></SelectItem>
<SelectItem value="quota"></SelectItem>
</SelectContent>
</Select>
)}
</FormField>
<div className="flex flex-col gap-2">
<Label className="text-sm"></Label>
<div className="flex items-center">
<FormField name="create_after">
{({field}) => (
<DatePicker
{...field}
className="w-36"
placeholder="开始时间"
format="yyyy-MM-dd"
/>
)}
</FormField>
<span className="px-1">-</span>
<FormField name="create_before">
{({field}) => (
<DatePicker
{...field}
className="w-36"
placeholder="结束时间"
format="yyyy-MM-dd"
/>
)}
</FormField>
</div>
),
},
{
accessorKey: 'live', header: `IP 时效`, cell: ({row}) => (
<span>
{row.original.long.live}
</span>
),
},
{
accessorKey: 'expire', header: `使用情况`, cell: ({row}) => (
<div className={`flex gap-1`}>
{row.original.long.type === 1 ? (
<div className={`flex gap-1`}>
{isAfter(row.original.long.expire, new Date())
? <span className={`text-green-500`}></span>
: <span className={`text-red-500`}></span>}
<span>|</span>
<span>{row.original.long.daily_used} / {row.original.long.daily_limit}</span>
<span>|</span>
<span>{intlFormatDistance(row.original.long.expire, new Date())} </span>
</div>
) : row.original.long.type === 2 ? (
<div className={`flex gap-1`}>
{row.original.long.used < row.original.long.quota
? <span className={`text-green-500`}></span>
: <span className={`text-red-500`}></span>}
<span>|</span>
<span>{row.original.long.used} / {row.original.long.quota}</span>
</div>
) : (
<span>-</span>
)}
</div>
<div className="flex flex-col gap-2">
<Label className="text-sm">使</Label>
<div className="flex items-center">
<FormField name="expire_after">
{({field}) => (
<DatePicker
{...field}
className="w-36"
placeholder="开始时间"
format="yyyy-MM-dd"
/>
)}
</FormField>
<span className="px-1">-</span>
<FormField name="expire_before">
{({field}) => (
<DatePicker
{...field}
className="w-36"
placeholder="结束时间"
format="yyyy-MM-dd"
/>
)}
</FormField>
</div>
),
},
{
accessorKey: 'daily_last', header: '最近使用时间', cell: ({row}) => {
return (
format(row.original.long.daily_last, 'yyyy-MM-dd') === '0001-01-01'
? '-'
: format(row.original.long.daily_last, 'yyyy-MM-dd HH:mm')
)
</div>
<div className="flex gap-4">
<Button className="h-9">
<Search/>
<span></span>
</Button>
<Button
theme="outline"
className="h-9"
onClick={() => form.reset({
type: 'all',
resource_no: '',
create_after: undefined,
create_before: undefined,
expire_after: undefined,
expire_before: undefined,
})}>
<Eraser/>
<span></span>
</Button>
</div>
</Form>
</section>
{/* 数据表 */}
<DataTable
data={data.list}
status={status}
pagination={{
total: data.total,
page: data.page,
size: data.size,
onPageChange: async (page: number) => {
await refresh(page, data.size)
},
},
{
accessorKey: 'created_at', header: '开通时间', cell: ({row}) => (
format(row.getValue('created_at'), 'yyyy-MM-dd HH:mm')
),
},
{
accessorKey: 'action', header: `操作`, cell: (item) => (
<div className={`flex gap-2`}>
-
</div>
),
},
]}
/>
</>
onSizeChange: async (size: number) => {
await refresh(data.page, size)
},
}}
columns={[
{
accessorKey: 'resource_no', header: `套餐编号`,
},
{
accessorKey: 'type', header: `类型`, cell: ({row}) => (
<div className="flex gap-2 items-center">
{row.original.long.type === 1 && (
<div className="flex gap-2 items-center bg-green-50 w-fit px-2 py-1 rounded-md">
<Timer size={20}/>
<span></span>
</div>
)}
{row.original.long.type === 2 && (
<div className="flex gap-2 items-center bg-blue-50 w-fit px-2 py-1 rounded-md">
<Box size={20}/>
<span></span>
</div>
)}
</div>
),
},
{
accessorKey: 'live', header: `IP 时效`, cell: ({row}) => (
<span>
{row.original.long.live}
{' '}
</span>
),
},
{
accessorKey: 'expire', header: `使用情况`, cell: ({row}) => (
<div className="flex gap-1">
{row.original.long.type === 1 ? (
<div className="flex gap-1">
{isAfter(row.original.long.expire, new Date())
? <span className="text-green-500"></span>
: <span className="text-red-500"></span>}
<span>|</span>
<span>
{row.original.long.daily_used}
{' '}
/
{row.original.long.daily_limit}
</span>
<span>|</span>
<span>
{intlFormatDistance(row.original.long.expire, new Date())}
{' '}
</span>
</div>
) : row.original.long.type === 2 ? (
<div className="flex gap-1">
{row.original.long.used < row.original.long.quota
? <span className="text-green-500"></span>
: <span className="text-red-500"></span>}
<span>|</span>
<span>
{row.original.long.used}
{' '}
/
{row.original.long.quota}
</span>
</div>
) : (
<span>-</span>
)}
</div>
),
},
{
accessorKey: 'daily_last', header: '最近使用时间', cell: ({row}) => {
return (
format(row.original.long.daily_last, 'yyyy-MM-dd') === '0001-01-01'
? '-'
: format(row.original.long.daily_last, 'yyyy-MM-dd HH:mm')
)
},
},
{
accessorKey: 'created_at', header: '开通时间', cell: ({row}) => (
format(row.getValue('created_at'), 'yyyy-MM-dd HH:mm')
),
},
{
accessorKey: 'action', header: `操作`, cell: item => (
<div className="flex gap-2">
-
</div>
),
},
]}
/>
</>
)
}

View File

@@ -112,198 +112,221 @@ export default function ShortResource(props: ShortResourceProps) {
await refresh(1, data.size)
}
return <>
{/* 操作区 */}
<section className={`flex justify-between flex-wrap`}>
<div>
return (
<>
{/* 操作区 */}
<section className="flex justify-between flex-wrap">
<div>
</div>
</div>
<Form form={form} onSubmit={onSubmit} className={`flex items-end gap-4 flex-wrap`}>
<FormField name={`resource_no`} label={<span className={`text-sm`}></span>}>
{({id, field}) => (
<Input {...field} id={id} className={`h-9`}/>
)}
</FormField>
<FormField name={`type`} label={<span className={`text-sm`}></span>}>
{({field}) => (
<Select value={field.value} onValueChange={field.onChange}>
<SelectTrigger className={`w-24 h-9`}>
<SelectValue placeholder={`选择套餐类型`}/>
</SelectTrigger>
<SelectContent>
<SelectItem value={`all`}></SelectItem>
<SelectItem value={`expire`}></SelectItem>
<SelectItem value={`quota`}></SelectItem>
</SelectContent>
</Select>
)}
</FormField>
<div className={`flex flex-col gap-2`}>
<Label className={`text-sm`}></Label>
<div className={`flex items-center`}>
<FormField name={`create_after`}>
{({field}) => (
<DatePicker
{...field}
className={`w-36`}
placeholder={`开始时间`}
format={`yyyy-MM-dd`}
/>
)}
</FormField>
<span className={`px-1`}>-</span>
<FormField name={`create_before`}>
{({field}) => (
<DatePicker
{...field}
className={`w-36`}
placeholder={`结束时间`}
format={`yyyy-MM-dd`}
/>
)}
</FormField>
</div>
</div>
<div className={`flex flex-col gap-2`}>
<Label className={`text-sm`}>使</Label>
<div className={`flex items-center`}>
<FormField name={`expire_after`}>
{({field}) => (
<DatePicker
{...field}
className={`w-36`}
placeholder={`开始时间`}
format={`yyyy-MM-dd`}
/>
)}
</FormField>
<span className={`px-1`}>-</span>
<FormField name={`expire_before`}>
{({field}) => (
<DatePicker
{...field}
className={`w-36`}
placeholder={`结束时间`}
format={`yyyy-MM-dd`}
/>
)}
</FormField>
</div>
</div>
<div className={`flex gap-4`}>
<Button className={`h-9`}>
<Search/>
<span></span>
</Button>
<Button theme={`outline`} className={`h-9`} onClick={() => form.reset({
type: 'all',
resource_no: '',
create_after: undefined,
create_before: undefined,
expire_after: undefined,
expire_before: undefined,
})}>
<Eraser/>
<span></span>
</Button>
</div>
</Form>
</section>
{/* 数据表 */}
<DataTable
data={data.list}
status={status}
pagination={{
total: data.total,
page: data.page,
size: data.size,
onPageChange: async (page: number) => {
await refresh(page, data.size)
},
onSizeChange: async (size: number) => {
await refresh(data.page, size)
},
}}
columns={[
{
accessorKey: 'resource_no', header: `套餐编号`,
},
{
accessorKey: 'type', header: `类型`, cell: ({row}) => (
<div className={`flex gap-2 items-center`}>
{row.original.short.type === 1 && (
<div className={`flex gap-2 items-center bg-green-50 w-fit px-2 py-1 rounded-md`}>
<Timer size={20}/>
<span></span>
</div>
)}
{row.original.short.type === 2 && (
<div className={`flex gap-2 items-center bg-blue-50 w-fit px-2 py-1 rounded-md`}>
<Box size={20}/>
<span></span>
</div>
)}
<Form form={form} onSubmit={onSubmit} className="flex items-end gap-4 flex-wrap">
<FormField name="resource_no" label={<span className="text-sm"></span>}>
{({id, field}) => (
<Input {...field} id={id} className="h-9"/>
)}
</FormField>
<FormField name="type" label={<span className="text-sm"></span>}>
{({field}) => (
<Select value={field.value} onValueChange={field.onChange}>
<SelectTrigger className="w-24 h-9">
<SelectValue placeholder="选择套餐类型"/>
</SelectTrigger>
<SelectContent>
<SelectItem value="all"></SelectItem>
<SelectItem value="expire"></SelectItem>
<SelectItem value="quota"></SelectItem>
</SelectContent>
</Select>
)}
</FormField>
<div className="flex flex-col gap-2">
<Label className="text-sm"></Label>
<div className="flex items-center">
<FormField name="create_after">
{({field}) => (
<DatePicker
{...field}
className="w-36"
placeholder="开始时间"
format="yyyy-MM-dd"
/>
)}
</FormField>
<span className="px-1">-</span>
<FormField name="create_before">
{({field}) => (
<DatePicker
{...field}
className="w-36"
placeholder="结束时间"
format="yyyy-MM-dd"
/>
)}
</FormField>
</div>
),
},
{
accessorKey: 'live', header: `IP 时效`, cell: ({row}) => (
<span>
{row.original.short.live / 60}
</span>
),
},
{
accessorKey: 'expire', header: `使用情况`, cell: ({row}) => (
<div className={`flex gap-1`}>
{row.original.short.type === 1 ? (
<div className={`flex gap-1`}>
{isAfter(row.original.short.expire, new Date())
? <span className={`text-green-500`}></span>
: <span className={`text-red-500`}></span>}
<span>|</span>
<span>{row.original.short.daily_used} / {row.original.short.daily_limit}</span>
<span>|</span>
<span>{intlFormatDistance(row.original.short.expire, new Date())} </span>
</div>
) : row.original.short.type === 2 ? (
<div className={`flex gap-1`}>
{row.original.short.used < row.original.short.quota
? <span className={`text-green-500`}></span>
: <span className={`text-red-500`}></span>}
<span>|</span>
<span>{row.original.short.used} / {row.original.short.quota}</span>
</div>
) : (
<span>-</span>
)}
</div>
<div className="flex flex-col gap-2">
<Label className="text-sm">使</Label>
<div className="flex items-center">
<FormField name="expire_after">
{({field}) => (
<DatePicker
{...field}
className="w-36"
placeholder="开始时间"
format="yyyy-MM-dd"
/>
)}
</FormField>
<span className="px-1">-</span>
<FormField name="expire_before">
{({field}) => (
<DatePicker
{...field}
className="w-36"
placeholder="结束时间"
format="yyyy-MM-dd"
/>
)}
</FormField>
</div>
),
},
{
accessorKey: 'daily_last', header: '最近使用时间', cell: ({row}) => {
return (
format(row.original.short.daily_last, 'yyyy-MM-dd') === '0001-01-01'
? '-'
: format(row.original.short.daily_last, 'yyyy-MM-dd HH:mm')
)
</div>
<div className="flex gap-4">
<Button className="h-9">
<Search/>
<span></span>
</Button>
<Button
theme="outline"
className="h-9"
onClick={() => form.reset({
type: 'all',
resource_no: '',
create_after: undefined,
create_before: undefined,
expire_after: undefined,
expire_before: undefined,
})}>
<Eraser/>
<span></span>
</Button>
</div>
</Form>
</section>
{/* 数据表 */}
<DataTable
data={data.list}
status={status}
pagination={{
total: data.total,
page: data.page,
size: data.size,
onPageChange: async (page: number) => {
await refresh(page, data.size)
},
},
{
accessorKey: 'created_at', header: '开通时间', cell: ({row}) => (
format(row.getValue('created_at'), 'yyyy-MM-dd HH:mm')
),
},
{
accessorKey: 'action', header: `操作`, cell: (item) => (
<div className={`flex gap-2`}>
-
</div>
),
},
]}
/>
</>
onSizeChange: async (size: number) => {
await refresh(data.page, size)
},
}}
columns={[
{
accessorKey: 'resource_no', header: `套餐编号`,
},
{
accessorKey: 'type', header: `类型`, cell: ({row}) => (
<div className="flex gap-2 items-center">
{row.original.short.type === 1 && (
<div className="flex gap-2 items-center bg-green-50 w-fit px-2 py-1 rounded-md">
<Timer size={20}/>
<span></span>
</div>
)}
{row.original.short.type === 2 && (
<div className="flex gap-2 items-center bg-blue-50 w-fit px-2 py-1 rounded-md">
<Box size={20}/>
<span></span>
</div>
)}
</div>
),
},
{
accessorKey: 'live', header: `IP 时效`, cell: ({row}) => (
<span>
{row.original.short.live / 60}
{' '}
</span>
),
},
{
accessorKey: 'expire', header: `使用情况`, cell: ({row}) => (
<div className="flex gap-1">
{row.original.short.type === 1 ? (
<div className="flex gap-1">
{isAfter(row.original.short.expire, new Date())
? <span className="text-green-500"></span>
: <span className="text-red-500"></span>}
<span>|</span>
<span>
{row.original.short.daily_used}
{' '}
/
{row.original.short.daily_limit}
</span>
<span>|</span>
<span>
{intlFormatDistance(row.original.short.expire, new Date())}
{' '}
</span>
</div>
) : row.original.short.type === 2 ? (
<div className="flex gap-1">
{row.original.short.used < row.original.short.quota
? <span className="text-green-500"></span>
: <span className="text-red-500"></span>}
<span>|</span>
<span>
{row.original.short.used}
{' '}
/
{row.original.short.quota}
</span>
</div>
) : (
<span>-</span>
)}
</div>
),
},
{
accessorKey: 'daily_last', header: '最近使用时间', cell: ({row}) => {
return (
format(row.original.short.daily_last, 'yyyy-MM-dd') === '0001-01-01'
? '-'
: format(row.original.short.daily_last, 'yyyy-MM-dd HH:mm')
)
},
},
{
accessorKey: 'created_at', header: '开通时间', cell: ({row}) => (
format(row.getValue('created_at'), 'yyyy-MM-dd HH:mm')
),
},
{
accessorKey: 'action', header: `操作`, cell: item => (
<div className="flex gap-2">
-
</div>
),
},
]}
/>
</>
)
}

View File

@@ -1,6 +1,5 @@
import { ReactNode } from 'react'
import { Metadata } from 'next'
import {ReactNode} from 'react'
import {Metadata} from 'next'
export async function generateMetadata(): Promise<Metadata> {
return {
@@ -9,9 +8,9 @@ export async function generateMetadata(): Promise<Metadata> {
}
export type ResourcesLayoutProps = {
children: ReactNode
children: ReactNode
}
export default async function ResourcesLayout(props: ResourcesLayoutProps) {
return props.children
}
}

View File

@@ -4,22 +4,21 @@ import ShortResource from '@/app/admin/resources/_client/short'
import LongResource from '@/app/admin/resources/_client/long'
export default async function ResourcesPage() {
// ======================
// render
// ======================
return (
<Page>
<Tabs defaultValue={`short`}>
<TabsList className={`bg-card p-1.5 rounded-lg`}>
<TabsTrigger value={`short`} className={`w-30 h-9 data-[state=active]:bg-primary-muted text-base rounded-md`}></TabsTrigger>
<TabsTrigger value={`long`} className={`w-30 h-9 data-[state=active]:bg-primary-muted text-base rounded-md`}></TabsTrigger>
<Tabs defaultValue="short">
<TabsList className="bg-card p-1.5 rounded-lg">
<TabsTrigger value="short" className="w-30 h-9 data-[state=active]:bg-primary-muted text-base rounded-md"></TabsTrigger>
<TabsTrigger value="long" className="w-30 h-9 data-[state=active]:bg-primary-muted text-base rounded-md"></TabsTrigger>
</TabsList>
<TabsContent value={`short`} className={`flex flex-col gap-4`}>
<TabsContent value="short" className="flex flex-col gap-4">
<ShortResource/>
</TabsContent>
<TabsContent value={`long`} className={`flex flex-col gap-4`}>
<TabsContent value="long" className="flex flex-col gap-4">
<LongResource/>
</TabsContent>
</Tabs>

View File

@@ -1,6 +1,5 @@
import { ReactNode } from 'react'
import { Metadata } from 'next'
import {ReactNode} from 'react'
import {Metadata} from 'next'
export async function generateMetadata(): Promise<Metadata> {
return {
@@ -9,9 +8,9 @@ export async function generateMetadata(): Promise<Metadata> {
}
export type WhitelistLayoutProps = {
children: ReactNode
children: ReactNode
}
export default async function WhitelistLayout(props: WhitelistLayoutProps) {
return props.children
}
}

View File

@@ -149,7 +149,7 @@ export default function WhitelistPage(props: WhitelistPageProps) {
}
else {
const newSelection = new Set<number>()
data.list.forEach(item => {
data.list.forEach((item) => {
newSelection.add(item.id)
})
setSelection(newSelection)
@@ -231,8 +231,8 @@ export default function WhitelistPage(props: WhitelistPageProps) {
</Button>
<Button
theme={`fail`}
className={`ml-2`}
theme="fail"
className="ml-2"
disabled={selection.size === 0 || wait}
onClick={() => confirmRemove()}>
<Trash2/>
@@ -267,7 +267,7 @@ export default function WhitelistPage(props: WhitelistPageProps) {
cell: ({row}) => (
<div className="flex justify-end gap-2">
<Button
className={`h-9 w-9`}
className="h-9 w-9"
theme="outline"
onClick={() => openDialog('edit', row.original)}
disabled={wait}
@@ -275,9 +275,9 @@ export default function WhitelistPage(props: WhitelistPageProps) {
<Edit className="w-4 h-4"/>
</Button>
<Button
className={`h-9 w-9`}
className="h-9 w-9"
onClick={() => confirmRemove(row.original.id)}
theme={`fail`}
theme="fail"
disabled={wait}
>
<Trash2 className="w-4 h-4"/>
@@ -297,22 +297,22 @@ export default function WhitelistPage(props: WhitelistPageProps) {
</DialogTitle>
</DialogHeader>
<Form<SchemaType>
className={`flex flex-col gap-4 py-4`}
className="flex flex-col gap-4 py-4"
form={form}
onSubmit={onSubmit}>
<FormField name={`host`} label={`IP地址`}>
<FormField name="host" label="IP地址">
{({id, field}) => (
<Input {...field} id={id} placeholder="输入IP地址"/>
)}
</FormField>
<FormField name={`remark`} label={`备注`}>
<FormField name="remark" label="备注">
{({id, field}) => (
<Textarea {...field} id={id} placeholder="输入备注信息(可选)" disabled={wait}/>
)}
</FormField>
<DialogFooter className={`gap-4 mt-4`}>
<Button theme={`outline`} type="button" onClick={() => toggleDialog(false)} disabled={wait}></Button>
<Button type={`submit`} disabled={wait}>
<DialogFooter className="gap-4 mt-4">
<Button theme="outline" type="button" onClick={() => toggleDialog(false)} disabled={wait}></Button>
<Button type="submit" disabled={wait}>
{wait && <Loader2 className="w-4 h-4 mr-2 animate-spin"/>}
</Button>

View File

@@ -22,7 +22,6 @@ export default async function RootLayout({
}: Readonly<{
children: ReactNode
}>) {
const result = await getProfile()
const user = result.success ? result.data : null
@@ -32,7 +31,7 @@ export default async function RootLayout({
<StoreProvider user={user}>
{children}
</StoreProvider>
<Toaster position={'top-center'} richColors expand/>
<Toaster position="top-center" richColors expand/>
</body>
</html>
)

View File

@@ -1,10 +1,10 @@
import Qqwwee from "@/components/docs/qqwwee.mdx"
import Markdown from "@/components/markdown"
import Qqwwee from '@/components/docs/qqwwee.mdx'
import Markdown from '@/components/markdown'
export default async function TestPage(){
export default async function TestPage() {
return (
<Markdown>
<Qqwwee/>
</Markdown>
)
}
}