Compare commits
6 Commits
v1.2.2
...
b2c36196b4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2c36196b4 | ||
|
|
d2d6c1709c | ||
|
|
a76e61beb0 | ||
|
|
d83ad11241 | ||
|
|
bce7e41adf | ||
| 2b77ea189b |
12
README.md
12
README.md
@@ -1,5 +1,9 @@
|
||||
## TODO
|
||||
|
||||
- 导航栏
|
||||
- 账单页面
|
||||
- 实名认证响应
|
||||
|
||||
分离公共 api 接口 env 定义
|
||||
|
||||
统一前端基础库(类型,api)
|
||||
@@ -127,11 +131,3 @@ stores:共享状态组件
|
||||
|
||||
- 图片使用 Next.js Image 组件自动优化
|
||||
- 动态导入 (next/dynamic) 实现纯客户端组件
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ export async function listResourceShort(props: {
|
||||
size: number
|
||||
resource_no?: string
|
||||
type?: number
|
||||
status?: number
|
||||
create_after?: Date
|
||||
create_before?: Date
|
||||
expire_after?: Date
|
||||
@@ -37,6 +38,7 @@ export async function listResourceLong(props: {
|
||||
size: number
|
||||
resource_no?: string
|
||||
type?: number
|
||||
status?: number
|
||||
create_after?: Date
|
||||
create_before?: Date
|
||||
expire_after?: Date
|
||||
|
||||
@@ -141,7 +141,7 @@ export default function LoginCard() {
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={-1}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 cursor-pointer"
|
||||
onClick={() => setShowPwd(v => !v)}
|
||||
aria-label={showPwd ? '隐藏密码' : '显示密码'}
|
||||
>
|
||||
|
||||
@@ -28,8 +28,8 @@ export function HeroSection() {
|
||||
</div>
|
||||
|
||||
<FreeTrial className={[
|
||||
`mt-32 max-md:mt-20 w-96 max-md:w-full h-16 md:h-24 rounded-lg shadow-lg`,
|
||||
`bg-linear-to-r from-blue-500 to-cyan-400 text-white text-xl lg:text-4xl`,
|
||||
`mt-32 max-md:mt-20 w-96 max-md:w-full h-16 md:h-24 rounded-lg shadow-lg cursor-pointer`,
|
||||
`bg-linear-to-r from-blue-500 to-cyan-400 text-white text-xl lg:text-4xl cursor-pointer`,
|
||||
].join(' ')}/>
|
||||
</Wrap>
|
||||
</section>
|
||||
|
||||
@@ -16,7 +16,7 @@ export default function SidebarDrawer() {
|
||||
<span className="font-medium text-slate-900">帮助中心</span>
|
||||
<Drawer open={open} onOpenChange={setOpen}>
|
||||
<DrawerTrigger asChild>
|
||||
<button className="flex items-center gap-2 text-slate-600 hover:text-slate-900 p-1">
|
||||
<button className="flex items-center gap-2 text-slate-600 hover:text-slate-900 p-1 cursor-pointer">
|
||||
<Menu size={20}/>
|
||||
<span className="text-sm">目录</span>
|
||||
</button>
|
||||
|
||||
@@ -225,7 +225,7 @@ function MenuItem(props: {
|
||||
onPointerLeave={props.onPointerLeave}
|
||||
className={[
|
||||
`h-full px-4 flex gap-3 items-center cursor-pointer text-lg`,
|
||||
`transition-colors duration-200 ease-in-out`,
|
||||
`transition-colors duration-200 ease-in-out cursor-pointer`,
|
||||
props.active
|
||||
? `text-blue-500`
|
||||
: ``,
|
||||
|
||||
@@ -99,7 +99,7 @@ export default function BillsPage(props: BillsPageProps) {
|
||||
<SelectItem value="all">全部</SelectItem>
|
||||
<SelectItem value="3">充值</SelectItem>
|
||||
<SelectItem value="1">消费</SelectItem>
|
||||
<SelectItem value="2">退款</SelectItem>
|
||||
{/* <SelectItem value="2">退款</SelectItem> */}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
@@ -284,13 +284,13 @@ export default function BillsPage(props: BillsPageProps) {
|
||||
format(new Date(row.original.created_at), 'yyyy-MM-dd HH:mm')
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'action', header: `操作`, cell: item => (
|
||||
<div className="flex gap-2">
|
||||
-
|
||||
</div>
|
||||
),
|
||||
},
|
||||
// {
|
||||
// accessorKey: 'action', header: `操作`, cell: item => (
|
||||
// <div className="flex gap-2">
|
||||
// -
|
||||
// </div>
|
||||
// ),
|
||||
// },
|
||||
]}
|
||||
/>
|
||||
</Page>
|
||||
|
||||
@@ -116,7 +116,14 @@ export function Header() {
|
||||
</div>
|
||||
|
||||
{/* right */}
|
||||
<div className="flex-none flex items-center justify-end pr-4">
|
||||
<div className="flex-none flex items-center justify-end pr-4 max-md:hidden gap-2">
|
||||
<Link
|
||||
href="/"
|
||||
className={merge(
|
||||
`flex-none h-16 flex items-center justify-center`,
|
||||
)}>
|
||||
返回首页
|
||||
</Link>
|
||||
<Suspense>
|
||||
<HeaderUserCenter/>
|
||||
</Suspense>
|
||||
|
||||
@@ -98,6 +98,16 @@ export default function IdentifyPage(props: IdentifyPageProps) {
|
||||
const profile = useProfileStore(store => store.profile)
|
||||
const refreshProfile = useProfileStore(store => store.refreshProfile)
|
||||
|
||||
// 重置认证流程
|
||||
const handleDialogOpenChange = async (open: boolean) => {
|
||||
setOpenDialog(open)
|
||||
if (!open) {
|
||||
setStep('form')
|
||||
setTarget('')
|
||||
await refreshProfile()
|
||||
}
|
||||
}
|
||||
|
||||
// ======================
|
||||
// render
|
||||
// ======================
|
||||
@@ -125,7 +135,7 @@ export default function IdentifyPage(props: IdentifyPageProps) {
|
||||
</div>
|
||||
<Suspense>
|
||||
<IfNotIdentofy>
|
||||
<Dialog open={openDialog} onOpenChange={setOpenDialog}>
|
||||
<Dialog open={openDialog} onOpenChange={handleDialogOpenChange}>
|
||||
<DialogTrigger asChild>
|
||||
<Button className="w-full">去认证</Button>
|
||||
</DialogTrigger>
|
||||
@@ -165,10 +175,7 @@ export default function IdentifyPage(props: IdentifyPageProps) {
|
||||
<div className="flex flex-col gap-4 items-center">
|
||||
<canvas ref={canvas} width={256} height={256}/>
|
||||
<p className="text-sm text-gray-600">请扫码完成认证</p>
|
||||
<Button onClick={async () => {
|
||||
await refreshProfile()
|
||||
setOpenDialog(false)
|
||||
}}>
|
||||
<Button onClick={() => handleDialogOpenChange(false)}>
|
||||
已完成认证
|
||||
</Button>
|
||||
</div>
|
||||
@@ -223,7 +230,7 @@ export default function IdentifyPage(props: IdentifyPageProps) {
|
||||
<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>支付订单</span>
|
||||
</span>
|
||||
<Image alt="步骤配图" src={step3}/>
|
||||
</p>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {Eraser, Search} from 'lucide-react'
|
||||
export interface ResourceFilterValues {
|
||||
resource_no: string
|
||||
type: 'expire' | 'quota' | 'all'
|
||||
status: '0' | '1' | '2'
|
||||
create_after?: Date
|
||||
create_before?: Date
|
||||
expire_after?: Date
|
||||
@@ -41,13 +42,27 @@ export default function ResourceFilter({form, onSubmit, onReset}: ResourceFilter
|
||||
<SelectValue placeholder="选择套餐类型"/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">全部</SelectItem>
|
||||
<SelectItem value="all" >全部</SelectItem>
|
||||
<SelectItem value="expire">包时</SelectItem>
|
||||
<SelectItem value="quota">包量</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</FormField>
|
||||
<FormField name="status" 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="0">全部</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">
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
ExpireBadge,
|
||||
formatDateTime,
|
||||
getTodayUsage,
|
||||
isValidResourcestatus,
|
||||
isValidResourceType,
|
||||
ResourceTypeBadge,
|
||||
} from './utils'
|
||||
@@ -24,6 +25,7 @@ import {
|
||||
const filterSchema = zod.object({
|
||||
resource_no: zod.string().optional().default(''),
|
||||
type: zod.enum(['expire', 'quota', 'all']).default('all'),
|
||||
status: zod.enum(['0', '1', '2']).default('1'),
|
||||
create_after: zod.date().optional(),
|
||||
create_before: zod.date().optional(),
|
||||
expire_after: zod.date().optional(),
|
||||
@@ -47,12 +49,13 @@ export default function ResourceList({resourceType}: ResourceListProps) {
|
||||
// 从 URL 参数初始化筛选条件
|
||||
const params = useSearchParams()
|
||||
const paramType = params.get('type')
|
||||
|
||||
const paramStatus = params.get('status')
|
||||
const form = useForm<ResourceFilterValues>({
|
||||
resolver: zodResolver(filterSchema),
|
||||
defaultValues: {
|
||||
resource_no: params.get('resource_no') || '',
|
||||
type: isValidResourceType(paramType) ? paramType : 'all',
|
||||
status: isValidResourcestatus(paramStatus) ? paramStatus : '1',
|
||||
create_after: params.get('create_after') ? new Date(params.get('create_after')!) : undefined,
|
||||
create_before: params.get('create_before') ? new Date(params.get('create_before')!) : undefined,
|
||||
expire_after: params.get('expire_after') ? new Date(params.get('expire_after')!) : undefined,
|
||||
@@ -71,6 +74,7 @@ export default function ResourceList({resourceType}: ResourceListProps) {
|
||||
expire: 1,
|
||||
quota: 2,
|
||||
}[getValues('type')]
|
||||
const status = getValues('status')
|
||||
const create_after = getValues('create_after')
|
||||
const create_before = getValues('create_before')
|
||||
const expire_after = getValues('expire_after')
|
||||
@@ -82,6 +86,7 @@ export default function ResourceList({resourceType}: ResourceListProps) {
|
||||
page,
|
||||
size,
|
||||
type,
|
||||
status: Number(status),
|
||||
create_after,
|
||||
create_before,
|
||||
expire_after,
|
||||
@@ -116,6 +121,7 @@ export default function ResourceList({resourceType}: ResourceListProps) {
|
||||
const handleReset = () => {
|
||||
form.reset({
|
||||
type: 'all',
|
||||
status: '1',
|
||||
resource_no: '',
|
||||
create_after: undefined,
|
||||
create_before: undefined,
|
||||
|
||||
@@ -7,6 +7,11 @@ export function isValidResourceType(type: string | null): type is 'expire' | 'qu
|
||||
return type === 'expire' || type === 'quota' || type === 'all'
|
||||
}
|
||||
|
||||
// 状态
|
||||
export function isValidResourcestatus(status: string | null): status is '0' | '1' | '2' {
|
||||
return status === '0' || status === '1' || status === '2'
|
||||
}
|
||||
|
||||
// 资源类型徽章
|
||||
export function ResourceTypeBadge({type}: {type: number}) {
|
||||
if (type === 1) {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 31 KiB |
@@ -5,8 +5,10 @@ import {Loader} from 'lucide-react'
|
||||
import {useState} from 'react'
|
||||
import Image from 'next/image'
|
||||
import {PaymentModalProps} from './payment-modal'
|
||||
import {getTradeMethodDecoration} from '@/lib/models/trade'
|
||||
|
||||
export function DesktopPayment(props: PaymentModalProps) {
|
||||
const decoration = getTradeMethodDecoration(props.method)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const onSubmit = async () => {
|
||||
@@ -19,10 +21,10 @@ export function DesktopPayment(props: PaymentModalProps) {
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex gap-2 items-center">
|
||||
{props.decoration.icon ? (
|
||||
{decoration.icon ? (
|
||||
<Image
|
||||
src={props.decoration.icon}
|
||||
alt={props.decoration.text}
|
||||
src={decoration.icon}
|
||||
alt={decoration.text}
|
||||
width={24}
|
||||
height={24}
|
||||
className="rounded-md"
|
||||
@@ -30,7 +32,7 @@ export function DesktopPayment(props: PaymentModalProps) {
|
||||
) : (
|
||||
<div className="w-6 h-6 bg-gray-200 rounded-full"/>
|
||||
)}
|
||||
<span>{props.decoration.text}</span>
|
||||
<span>{decoration.text}</span>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -43,7 +45,7 @@ export function DesktopPayment(props: PaymentModalProps) {
|
||||
/>
|
||||
<p className="text-sm text-gray-600">
|
||||
请使用
|
||||
{props.decoration.text}
|
||||
{decoration.text}
|
||||
扫码支付
|
||||
</p>
|
||||
|
||||
|
||||
@@ -6,8 +6,10 @@ import {CreditCard, Loader} from 'lucide-react'
|
||||
import {useState} from 'react'
|
||||
import Image from 'next/image'
|
||||
import {PaymentModalProps} from './payment-modal'
|
||||
import {getTradeMethodDecoration} from '@/lib/models/trade'
|
||||
|
||||
export function MobilePayment(props: PaymentModalProps) {
|
||||
const decoration = getTradeMethodDecoration(props.method)
|
||||
const [loading, setLoading] = useState(false) // 加载状态
|
||||
const [paymentInitiated, setPaymentInitiated] = useState(false) // 是否已发起支付
|
||||
|
||||
@@ -54,16 +56,16 @@ export function MobilePayment(props: PaymentModalProps) {
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">支付方式</span>
|
||||
<div className="flex items-center gap-2">
|
||||
{props.decoration.icon && (
|
||||
{decoration.icon && (
|
||||
<Image
|
||||
src={props.decoration.icon}
|
||||
alt={props.decoration.text}
|
||||
src={decoration.icon}
|
||||
alt={decoration.text}
|
||||
width={28}
|
||||
height={28}
|
||||
className="rounded-md"
|
||||
/>
|
||||
)}
|
||||
<span>{props.decoration.text}</span>
|
||||
<span>{decoration.text}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
|
||||
@@ -6,7 +6,7 @@ import {Dialog} from '@/components/ui/dialog'
|
||||
import {PaymentProps} from './type'
|
||||
import {payClose} from '@/actions/resource'
|
||||
import {useEffect} from 'react'
|
||||
import {useRouter} from 'next/navigation'
|
||||
import {UniversalDesktopPayment} from './universal-desktop-payment'
|
||||
|
||||
export type PaymentModalProps = {
|
||||
onConfirm: (showFail: boolean) => Promise<void>
|
||||
@@ -61,17 +61,13 @@ export function PaymentModal(props: PaymentModalProps) {
|
||||
onOpenChange={(open) => {
|
||||
if (!open) handleClose()
|
||||
}}>
|
||||
{props.platform === TradePlatform.Mobile ? (
|
||||
<MobilePayment
|
||||
{...props}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
) : (
|
||||
<DesktopPayment
|
||||
{...props}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* {props.platform === TradePlatform.Mobile
|
||||
? <MobilePayment {...props} onClose={handleClose}/>
|
||||
: <DesktopPayment {...props} onClose={handleClose}/>
|
||||
} */}
|
||||
|
||||
<UniversalDesktopPayment {...props} onClose={handleClose}/>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,8 +8,4 @@ export type PaymentProps = {
|
||||
amount: number
|
||||
platform: TradePlatform
|
||||
method: TradeMethod
|
||||
decoration: {
|
||||
icon: StaticImageData
|
||||
text: string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
'use client'
|
||||
import {DialogClose, DialogContent, DialogHeader, DialogTitle} from '@/components/ui/dialog'
|
||||
import {Button} from '@/components/ui/button'
|
||||
import {Loader} from 'lucide-react'
|
||||
import {useState} from 'react'
|
||||
import Image from 'next/image'
|
||||
import {PaymentModalProps} from './payment-modal'
|
||||
import {getTradeMethodDecoration, TradePlatform} from '@/lib/models/trade'
|
||||
|
||||
export function UniversalDesktopPayment(props: PaymentModalProps) {
|
||||
const decoration = getTradeMethodDecoration(props.method)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const onSubmit = async () => {
|
||||
setLoading(true)
|
||||
await props.onConfirm(true)
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex gap-2 items-center">
|
||||
{decoration.icon ? (
|
||||
<Image
|
||||
src={decoration.icon}
|
||||
alt={decoration.text}
|
||||
width={24}
|
||||
height={24}
|
||||
className="rounded-md"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-6 h-6 bg-gray-200 rounded-full"/>
|
||||
)}
|
||||
<span>{decoration.text}</span>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<Image
|
||||
src={props.pay_url}
|
||||
width={208}
|
||||
height={208}
|
||||
alt="二维码"
|
||||
/>
|
||||
<div className="flex-none flex flex-col gap-1 items-center">
|
||||
<p className="text-sm text-gray-600">
|
||||
请使用 微信 或 支付宝 扫码支付
|
||||
</p>
|
||||
<p className="text-sm text-gray-600">
|
||||
手机端长按二维码下载并识图
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="w-full text-center space-y-2">
|
||||
<p className="text-sm font-medium">
|
||||
支付金额:
|
||||
<span className="text-accent">
|
||||
¥
|
||||
{props.amount?.toFixed(2) || '0.00'}
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-xs text-gray-500">
|
||||
订单号:
|
||||
{props.inner_no}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4 w-full justify-center">
|
||||
<Button onClick={onSubmit}>
|
||||
{loading && <Loader className="animate-spin mr-2"/>}
|
||||
已完成支付
|
||||
</Button>
|
||||
<DialogClose asChild>
|
||||
<Button theme="outline" onClick={() => props.onClose?.()}>
|
||||
关闭
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
)
|
||||
}
|
||||
@@ -234,7 +234,7 @@ export default function CollectPage() {
|
||||
<div className="text-blue-600 font-bold text-2xl md:text-2xl text-center md:text-left">
|
||||
现在注册,免费领取5000IP
|
||||
</div>
|
||||
<FreeTrial className={merge('bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-md whitespace-nowrap')}/>
|
||||
<FreeTrial className={merge('bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-md whitespace-nowrap cursor-pointer')}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
'use client'
|
||||
import {createContext} from 'react'
|
||||
import {useForm, UseFormReturn} from 'react-hook-form'
|
||||
import {useForm} from 'react-hook-form'
|
||||
import Center from '@/components/composites/purchase/long/center'
|
||||
import Right from '@/components/composites/purchase/long/right'
|
||||
import {Form} from '@/components/ui/form'
|
||||
@@ -20,13 +19,6 @@ const schema = z.object({
|
||||
// 从架构中推断类型
|
||||
export type Schema = z.infer<typeof schema>
|
||||
|
||||
type PurchaseFormContextType = {
|
||||
form: UseFormReturn<Schema>
|
||||
onSubmit?: () => void
|
||||
}
|
||||
|
||||
export const LongFormContext = createContext<PurchaseFormContextType | undefined>(undefined)
|
||||
|
||||
export default function LongForm() {
|
||||
const form = useForm<Schema>({
|
||||
resolver: zodResolver(schema),
|
||||
@@ -36,16 +28,14 @@ export default function LongForm() {
|
||||
quota: 500,
|
||||
expire: '30', // 天
|
||||
daily_limit: 100,
|
||||
pay_type: 'balance', // 余额支付
|
||||
pay_type: 'wechat', // 余额支付
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<Form form={form} className="flex flex-col lg:flex-row gap-4">
|
||||
<LongFormContext.Provider value={{form}}>
|
||||
<Center/>
|
||||
<Right/>
|
||||
</LongFormContext.Provider>
|
||||
<Center/>
|
||||
<Right/>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
'use client'
|
||||
import {Suspense, use, useContext, useEffect, useMemo, useState} from 'react'
|
||||
import {PurchaseFormContext} from '@/components/composites/purchase/short/form'
|
||||
import {RadioGroup} from '@/components/ui/radio-group'
|
||||
import {FormField} from '@/components/ui/form'
|
||||
import FormOption from '@/components/composites/purchase/option'
|
||||
import Image from 'next/image'
|
||||
import alipay from '../_assets/alipay.svg'
|
||||
import wechat from '../_assets/wechat.svg'
|
||||
import balance from '../_assets/balance.svg'
|
||||
import {Suspense, use, useEffect, useState} from 'react'
|
||||
import {useProfileStore} from '@/components/stores/profile'
|
||||
import RechargeModal from '@/components/composites/recharge'
|
||||
import Pay from '@/components/composites/purchase/pay'
|
||||
import {buttonVariants} from '@/components/ui/button'
|
||||
import Link from 'next/link'
|
||||
@@ -17,8 +8,9 @@ import {merge} from '@/lib/utils'
|
||||
import {useFormContext, useWatch} from 'react-hook-form'
|
||||
import {Schema} from '@/components/composites/purchase/long/form'
|
||||
import {Card} from '@/components/ui/card'
|
||||
import {getPrice, CreateResourceReq} from '@/actions/resource'
|
||||
import {getPrice} from '@/actions/resource'
|
||||
import {ExtraResp} from '@/lib/api'
|
||||
import {FieldPayment} from '../shared/field-payment'
|
||||
|
||||
export default function Right() {
|
||||
const {control} = useFormContext<Schema>()
|
||||
@@ -164,52 +156,7 @@ function BalanceOrLogin(props: {
|
||||
const profile = use(useProfileStore(store => store.profile))
|
||||
return profile ? (
|
||||
<>
|
||||
<FormField name="pay_type" label="支付方式" className="flex flex-col gap-6">
|
||||
{({id, field}) => (
|
||||
<RadioGroup
|
||||
id={id}
|
||||
defaultValue={field.value}
|
||||
onValueChange={field.onChange}
|
||||
className="flex flex-col gap-3">
|
||||
|
||||
{/* <div className="w-full p-3 flex flex-col gap-4 bg-gray-100 rounded-md">
|
||||
<p className="flex items-center gap-3">
|
||||
<Image src={balance} alt="余额icon"/>
|
||||
<span className="text-sm text-gray-500">账户余额</span>
|
||||
</p>
|
||||
<p className="flex justify-between items-center">
|
||||
<span className="text-xl">{profile?.balance}</span>
|
||||
<RechargeModal/>
|
||||
</p>
|
||||
</div> */}
|
||||
|
||||
{/* <FormOption
|
||||
id={`${id}-balance`}
|
||||
value="balance"
|
||||
compare={field.value}
|
||||
className="p-3 w-full flex-row gap-2 justify-center">
|
||||
<Image src={balance} alt="余额 icon"/>
|
||||
<span>余额</span>
|
||||
</FormOption> */}
|
||||
<FormOption
|
||||
id={`${id}-wechat`}
|
||||
value="wechat"
|
||||
compare={field.value}
|
||||
className="p-3 w-full flex-row gap-2 justify-center">
|
||||
<Image src={wechat} alt="微信 logo"/>
|
||||
<span>微信</span>
|
||||
</FormOption>
|
||||
<FormOption
|
||||
id={`${id}-alipay`}
|
||||
value="alipay"
|
||||
compare={field.value}
|
||||
className="p-3 w-full flex-row gap-2 justify-center">
|
||||
<Image src={alipay} alt="支付宝 logo"/>
|
||||
<span>支付宝</span>
|
||||
</FormOption>
|
||||
</RadioGroup>
|
||||
)}
|
||||
</FormField>
|
||||
{/* <FieldPayment/> */}
|
||||
<Pay
|
||||
method={props.method}
|
||||
balance={profile.balance}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {useRouter} from 'next/navigation'
|
||||
import {completeResource, createResource, CreateResourceReq, prepareResource} from '@/actions/resource'
|
||||
import {
|
||||
TradeMethod,
|
||||
TradeMethodDecoration,
|
||||
TradePlatform,
|
||||
} from '@/lib/models/trade'
|
||||
import {PaymentModal} from '@/components/composites/payment/payment-modal'
|
||||
import {PaymentProps} from '@/components/composites/payment/type'
|
||||
@@ -32,7 +32,7 @@ export default function Pay(props: PayProps) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [trade, setTrade] = useState<PaymentProps | null>(null)
|
||||
const router = useRouter()
|
||||
const platform = usePlatformType()
|
||||
// const platform = usePlatformType()
|
||||
|
||||
const onOpen = async () => {
|
||||
setOpen(true)
|
||||
@@ -45,7 +45,7 @@ export default function Pay(props: PayProps) {
|
||||
const req = {
|
||||
...props.resource,
|
||||
payment_method: method,
|
||||
payment_platform: platform,
|
||||
payment_platform: TradePlatform.Desktop,
|
||||
}
|
||||
|
||||
const resp = await prepareResource(req)
|
||||
@@ -60,9 +60,8 @@ export default function Pay(props: PayProps) {
|
||||
inner_no: resp.data.trade_no,
|
||||
pay_url: resp.data.pay_url,
|
||||
amount: Number(props.amount),
|
||||
platform: platform,
|
||||
platform: TradePlatform.Desktop,
|
||||
method: method,
|
||||
decoration: TradeMethodDecoration[props.method],
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
57
src/components/composites/purchase/shared/field-payment.tsx
Normal file
57
src/components/composites/purchase/shared/field-payment.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import FormOption from '../option'
|
||||
import {RadioGroup} from '@/components/ui/radio-group'
|
||||
import {FormField} from '@/components/ui/form'
|
||||
import Image from 'next/image'
|
||||
import alipay from '../_assets/alipay.svg'
|
||||
import wechat from '../_assets/wechat.svg'
|
||||
|
||||
export function FieldPayment() {
|
||||
return (
|
||||
<FormField name="pay_type" label="支付方式" className="flex flex-col gap-6">
|
||||
{({id, field}) => (
|
||||
<RadioGroup
|
||||
id={id}
|
||||
defaultValue={field.value}
|
||||
onValueChange={field.onChange}
|
||||
className="flex flex-col gap-3">
|
||||
|
||||
{/* <div className="w-full p-3 flex flex-col gap-4 bg-gray-100 rounded-md">
|
||||
<p className="flex items-center gap-3">
|
||||
<Image src={balance} alt="余额icon"/>
|
||||
<span className="text-sm text-gray-500">账户余额</span>
|
||||
</p>
|
||||
<p className="flex justify-between items-center">
|
||||
<span className="text-xl">{profile.balance}</span>
|
||||
<RechargeModal/>
|
||||
</p>
|
||||
</div> */}
|
||||
|
||||
{/* <FormOption
|
||||
id={`${id}-balance`}
|
||||
value="balance"
|
||||
compare={field.value}
|
||||
className="p-3 w-full flex-row gap-2 justify-center">
|
||||
<Image src={balance} alt="余额 icon"/>
|
||||
<span>余额</span>
|
||||
</FormOption> */}
|
||||
<FormOption
|
||||
id={`${id}-wechat`}
|
||||
value="wechat"
|
||||
compare={field.value}
|
||||
className="p-3 w-full flex-row gap-2 justify-center">
|
||||
<Image src={wechat} alt="微信 logo"/>
|
||||
<span>微信</span>
|
||||
</FormOption>
|
||||
<FormOption
|
||||
id={`${id}-alipay`}
|
||||
value="alipay"
|
||||
compare={field.value}
|
||||
className="p-3 w-full flex-row gap-2 justify-center">
|
||||
<Image src={alipay} alt="支付宝 logo"/>
|
||||
<span>支付宝</span>
|
||||
</FormOption>
|
||||
</RadioGroup>
|
||||
)}
|
||||
</FormField>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
'use client'
|
||||
import {createContext} from 'react'
|
||||
import {useForm, UseFormReturn} from 'react-hook-form'
|
||||
import {useForm} from 'react-hook-form'
|
||||
import Center from '@/components/composites/purchase/short/center'
|
||||
import Right from '@/components/composites/purchase/short/right'
|
||||
import {Form} from '@/components/ui/form'
|
||||
@@ -20,16 +19,7 @@ const schema = z.object({
|
||||
// 从架构中推断类型
|
||||
export type Schema = z.infer<typeof schema>
|
||||
|
||||
type PurchaseFormContextType = {
|
||||
form: UseFormReturn<Schema>
|
||||
onSubmit?: () => void
|
||||
}
|
||||
|
||||
export const PurchaseFormContext = createContext<PurchaseFormContextType | undefined>(undefined)
|
||||
|
||||
export type PurchaseFormProps = {}
|
||||
|
||||
export default function PurchaseForm(props: PurchaseFormProps) {
|
||||
export default function ShortForm() {
|
||||
const form = useForm<Schema>({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: {
|
||||
@@ -38,7 +28,7 @@ export default function PurchaseForm(props: PurchaseFormProps) {
|
||||
quota: 10_000, // >= 10000
|
||||
expire: '30', // 天
|
||||
daily_limit: 2_000, // >= 2000
|
||||
pay_type: 'balance', // 余额支付
|
||||
pay_type: 'wechat', // 余额支付
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -1,23 +1,16 @@
|
||||
'use client'
|
||||
import {Suspense, use, useEffect, useMemo, useState} from 'react'
|
||||
import {Suspense, use, useEffect, useState} from 'react'
|
||||
import {Schema} from '@/components/composites/purchase/short/form'
|
||||
import {RadioGroup} from '@/components/ui/radio-group'
|
||||
import {FormField} from '@/components/ui/form'
|
||||
import FormOption from '@/components/composites/purchase/option'
|
||||
import Image from 'next/image'
|
||||
import alipay from '../_assets/alipay.svg'
|
||||
import wechat from '../_assets/wechat.svg'
|
||||
import balance from '../_assets/balance.svg'
|
||||
import {useProfileStore} from '@/components/stores/profile'
|
||||
import RechargeModal from '@/components/composites/recharge'
|
||||
import {buttonVariants} from '@/components/ui/button'
|
||||
import Link from 'next/link'
|
||||
import {merge} from '@/lib/utils'
|
||||
import Pay from '@/components/composites/purchase/pay'
|
||||
import {useFormContext, useWatch} from 'react-hook-form'
|
||||
import {Card} from '@/components/ui/card'
|
||||
import {CreateResourceReq, getPrice} from '@/actions/resource'
|
||||
import {getPrice} from '@/actions/resource'
|
||||
import {ExtraResp} from '@/lib/api'
|
||||
import {FieldPayment} from '../shared/field-payment'
|
||||
|
||||
export default function Right() {
|
||||
const {control} = useFormContext<Schema>()
|
||||
@@ -165,52 +158,7 @@ function BalanceOrLogin(props: {
|
||||
const profile = use(useProfileStore(store => store.profile))
|
||||
return profile ? (
|
||||
<>
|
||||
<FormField name="pay_type" label="支付方式" className="flex flex-col gap-6">
|
||||
{({id, field}) => (
|
||||
<RadioGroup
|
||||
id={id}
|
||||
defaultValue={field.value}
|
||||
onValueChange={field.onChange}
|
||||
className="flex flex-col gap-3">
|
||||
|
||||
{/* <div className="w-full p-3 flex flex-col gap-4 bg-gray-100 rounded-md">
|
||||
<p className="flex items-center gap-3">
|
||||
<Image src={balance} alt="余额icon"/>
|
||||
<span className="text-sm text-gray-500">账户余额</span>
|
||||
</p>
|
||||
<p className="flex justify-between items-center">
|
||||
<span className="text-xl">{profile.balance}</span>
|
||||
<RechargeModal/>
|
||||
</p>
|
||||
</div> */}
|
||||
|
||||
{/* <FormOption
|
||||
id={`${id}-balance`}
|
||||
value="balance"
|
||||
compare={field.value}
|
||||
className="p-3 w-full flex-row gap-2 justify-center">
|
||||
<Image src={balance} alt="余额 icon"/>
|
||||
<span>余额</span>
|
||||
</FormOption> */}
|
||||
<FormOption
|
||||
id={`${id}-wechat`}
|
||||
value="wechat"
|
||||
compare={field.value}
|
||||
className="p-3 w-full flex-row gap-2 justify-center">
|
||||
<Image src={wechat} alt="微信 logo"/>
|
||||
<span>微信</span>
|
||||
</FormOption>
|
||||
<FormOption
|
||||
id={`${id}-alipay`}
|
||||
value="alipay"
|
||||
compare={field.value}
|
||||
className="p-3 w-full flex-row gap-2 justify-center">
|
||||
<Image src={alipay} alt="支付宝 logo"/>
|
||||
<span>支付宝</span>
|
||||
</FormOption>
|
||||
</RadioGroup>
|
||||
)}
|
||||
</FormField>
|
||||
{/* <FieldPayment/> */}
|
||||
<Pay
|
||||
method={props.method}
|
||||
balance={profile.balance}
|
||||
|
||||
@@ -21,7 +21,6 @@ import {merge} from '@/lib/utils'
|
||||
import {
|
||||
TradePlatform,
|
||||
TradeMethod,
|
||||
TradeMethodDecoration,
|
||||
} from '@/lib/models/trade'
|
||||
import {PaymentModal} from '@/components/composites/payment/payment-modal'
|
||||
import Image from 'next/image'
|
||||
@@ -77,7 +76,6 @@ export default function RechargeModal(props: RechargeModelProps) {
|
||||
amount: data.amount,
|
||||
platform: platform,
|
||||
method: method,
|
||||
decoration: TradeMethodDecoration[data.method],
|
||||
})
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -11,6 +11,7 @@ export const buttonVariants = cva(
|
||||
'aria-invalid:ring-fail/20 dark:aria-invalid:ring-fail/40 aria-invalid:border-fail', // 无效状态样式
|
||||
'inline-flex items-center justify-center gap-2', // 布局
|
||||
'[&_svg]:pointer-events-none [&_svg:not([class*="size-"])]:size-4 shrink-0 [&_svg]:shrink-0 ',
|
||||
'cursor-pointer',
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
|
||||
@@ -6,10 +6,9 @@ export const usePlatformType = (): TradePlatform => {
|
||||
// 在SSR环境下返回默认值
|
||||
const [platform, setPlatform] = useState<TradePlatform>(() => {
|
||||
if (typeof window === 'undefined') return TradePlatform.Desktop
|
||||
// return window.matchMedia('(max-width: 768px)').matches
|
||||
// ? TradePlatform.Mobile
|
||||
// : TradePlatform.Desktop
|
||||
return TradePlatform.Desktop
|
||||
return window.matchMedia('(max-width: 768px)').matches
|
||||
? TradePlatform.Mobile
|
||||
: TradePlatform.Desktop
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
import {StaticImageData} from 'next/image'
|
||||
import wechat from '@/components/composites/purchase/_assets/wechat.svg'
|
||||
import alipay from '@/components/composites/purchase/_assets/alipay.svg'
|
||||
import balance from '@/components/composites/purchase/_assets/balance.svg'
|
||||
|
||||
export const TradeMethodDecoration = {
|
||||
alipay: {
|
||||
text: '支付宝',
|
||||
icon: alipay,
|
||||
},
|
||||
wechat: {
|
||||
text: '微信支付',
|
||||
icon: wechat,
|
||||
},
|
||||
export function getTradeMethodDecoration(method: TradeMethod) {
|
||||
switch (method) {
|
||||
case TradeMethod.Alipay:
|
||||
return {
|
||||
text: '支付宝',
|
||||
icon: alipay,
|
||||
}
|
||||
case TradeMethod.Wechat:
|
||||
return {
|
||||
text: '微信支付',
|
||||
icon: wechat,
|
||||
}
|
||||
default:
|
||||
return {
|
||||
text: '扫码支付',
|
||||
icon: balance,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 支付方法枚举
|
||||
|
||||
Reference in New Issue
Block a user