feat: 修改样式

This commit is contained in:
2025-08-12 02:58:18 -07:00
parent cc522d0754
commit 265519a03d
13 changed files with 567 additions and 648 deletions
+63
View File
@@ -0,0 +1,63 @@
import { AiroButton, buttonVariants } from '@workspace/airo-ui/components/AiroButton';
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogPortal,
AlertDialogTitle,
} from '@workspace/airo-ui/components/alert-dialog';
import CloseSvg from '@workspace/airo-ui/components/close.svg';
import { useImperativeHandle, useState } from 'react';
// Defining the AlertDialogComponent with internal state and onShow prop
const AlertDialogComponent = ({
ref,
title,
description,
cancelText = 'Cancel',
confirmText = 'Confirm',
onConfirm,
}) => {
const [open, setOpen] = useState(false);
function show() {
setOpen(true);
}
useImperativeHandle(ref, () => ({
show,
}));
return (
<AlertDialog open={open} onOpenChange={setOpen}>
<AlertDialogPortal>
<AlertDialogContent className={'py-[30px] sm:rounded-[25px]'}>
<div className={'absolute right-4 top-6'} onClick={() => setOpen(false)}>
<CloseSvg />
</div>
<AlertDialogHeader>
<AlertDialogTitle className={'text-[#E22C2E]'}>{title}</AlertDialogTitle>
<AlertDialogDescription className={'text-base font-light'}>
{description}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter className={'sm:justify-center'}>
<AlertDialogAction asChild className={buttonVariants({ variant: 'primary' })}>
<AiroButton variant={'primary'} onClick={onConfirm}>
{confirmText}
</AiroButton>
</AlertDialogAction>
<AlertDialogCancel asChild className={buttonVariants({ variant: 'danger' })}>
<AiroButton variant={'danger'}>{cancelText}</AiroButton>
</AlertDialogCancel>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialogPortal>
</AlertDialog>
);
};
export default AlertDialogComponent;
+8 -5
View File
@@ -182,14 +182,17 @@ export default function Affiliate() {
const MONTHLY_PRICE = proPlan?.unit_price;
const discountItem = proPlan?.discount?.find((v) => v.quantity === 12);
const YEARLY_PRICE = proPlan?.unit_price * ((discountItem?.discount || 100) / 100) * 12;
const {
common: { invite },
} = useGlobalStore();
const [count, setCount] = useState<number>(10);
const clamp = (n: number) => Math.max(0, Math.min(10000, Math.floor(n)));
const firstMonth = count * MONTHLY_PRICE * 0.5; // 50%
const firstYear = count * YEARLY_PRICE * 0.3; // 30%
const recurMonth = count * MONTHLY_PRICE * 0.2; // 20%
const recurYear = count * YEARLY_PRICE * 0.2; // 20%
const firstMonth = count * MONTHLY_PRICE * (invite.first_purchase_percentage / 100); // 50%
const firstYear =
count * YEARLY_PRICE * (invite.first_yearly_purchase_percentage / 100); // 30%
const recurMonth = count * MONTHLY_PRICE * (invite.non_first_purchase_percentage / 100); // 20%
const recurYear = count * YEARLY_PRICE * (invite.non_first_purchase_percentage / 100); // 20%
return (
<div className='space-y-4'>
+38 -36
View File
@@ -78,36 +78,12 @@ const PriceDisplay = ({ plan }: { plan: ProcessedPlanData }) => {
import { useLoginDialog } from '@/app/auth/LoginDialogContext';
import { Display } from '@/components/display';
import Modal from '@/components/Modal';
import Purchase from '@/components/subscribe/purchase';
import useGlobalStore from '@/config/use-global';
import { queryUserSubscribe } from '@/services/user/user';
import { useTranslations } from 'next-intl';
// 订阅按钮组件
const SubscribeButton = ({ onClick }: { onClick?: () => void }) => {
const { user } = useGlobalStore();
const { openLoginDialog } = useLoginDialog();
const t = useTranslations('components.offerDialog');
function handleClick() {
console.log('click', user);
if (!user) {
// 强制登陆
openLoginDialog(false);
return;
}
onClick();
}
return (
<button
onClick={handleClick}
className='h-10 w-full rounded-full bg-[#0F2C53] text-sm font-medium text-white shadow-md transition-all duration-300 hover:bg-[#225BA9] sm:h-10 sm:text-sm md:h-[40px] md:text-[14px]'
>
{t('subscribe')}
</button>
);
};
// 星级评分组件
const StarRating = ({ rating, maxRating = 5 }: { rating: number; maxRating?: number }) => (
<div className='text-right text-xs font-light leading-[1.8461538461538463em] text-black sm:text-[13px]'>
@@ -186,9 +162,26 @@ const PlanCard = forwardRef<
isFirstCard?: boolean;
}
>(({ plan, onSubscribe, isFirstCard = false }, ref) => {
const handleSubscribe = () => {
const { user } = useGlobalStore();
const { openLoginDialog } = useLoginDialog();
const t = useTranslations('components.offerDialog');
const ModalRef = useRef(null);
async function handleSubscribe() {
if (!user) {
// 强制登陆
openLoginDialog(false);
return;
}
// 有生效套餐进行弹窗提示
const { data } = await queryUserSubscribe();
if (data?.data?.list?.[0].status === 1) {
ModalRef.current.show();
return;
}
onSubscribe?.(plan);
};
}
return (
<div
@@ -202,10 +195,26 @@ const PlanCard = forwardRef<
<PriceDisplay plan={plan} />
{/* 订阅按钮 */}
<SubscribeButton onClick={handleSubscribe} />
<button
onClick={handleSubscribe}
className='h-10 w-full rounded-full bg-[#0F2C53] text-sm font-medium text-white shadow-md transition-all duration-300 hover:bg-[#225BA9] sm:h-10 sm:text-sm md:h-[40px] md:text-[14px]'
>
{t('subscribe')}
</button>
{/* 功能列表 */}
<FeatureList plan={plan} />
<Modal
ref={ModalRef}
title={'【重要提示】'}
description={
'您已有正在生效的套餐,如继续购买新的套餐,原套餐将自动失效。账户套餐将自动转为最新套餐。未使用部分不支持退款或叠加。'
}
confirmText={'同意'}
cancelText={'取消'}
onConfirm={() => onSubscribe?.(plan)}
/>
</div>
);
});
@@ -350,7 +359,6 @@ const OfferDialog = forwardRef<OfferDialogRef>((props, ref) => {
// 处理订阅点击
const handleSubscribe = (plan: ProcessedPlanData) => {
setSelectedPlan(plan);
console.log('用户选择了套餐:', plan);
// 这里可以添加订阅逻辑,比如跳转到支付页面或显示确认对话框
PurchaseRef.current.show(plan, tabValue);
};
@@ -412,12 +420,6 @@ const OfferDialog = forwardRef<OfferDialogRef>((props, ref) => {
<span className='absolute -top-8 left-16 z-10 rounded-md bg-[#E22C2E] px-2 py-0.5 text-[10px] font-bold leading-none text-white shadow sm:text-xs'>
-20%
{/* 小三角箭头 */}
{/* <span className="
absolute right-0 top-full
block h-0 w-0
border-l-[6px] border-r-[6px] border-t-[16px]
border-l-transparent border-r-transparent border-t-[#E22C2E]
" />*/}
<span
className='absolute right-0 top-[80%] h-10 w-2 bg-[#E22C2E]'
style={{ clipPath: 'polygon(100% 0, 100% 100%, 0 0)' }}
+1 -1
View File
@@ -109,11 +109,11 @@ const Purchase = forwardRef<PurchaseDialogRef, PurchaseProps>((props, ref) => {
const response = await purchase(params as API.PurchaseOrderRequest);
const orderNo = response.data.data?.order_no;
if (orderNo) {
await getUserInfo();
const data = await purchaseCheckout({
orderNo: orderNo,
returnUrl: window.location.href,
});
await getUserInfo();
if (data.data?.type === 'url' && data.data.checkout_url) {
window.open(data.data.checkout_url, '_blank');
} else {
+27 -24
View File
@@ -3,7 +3,7 @@
import useGlobalStore from '@/config/use-global';
import { recharge } from '@/services/user/order';
import { AiroButton } from '@workspace/airo-ui/components/AiroButton';
import { Button, ButtonProps } from '@workspace/airo-ui/components/button';
import { ButtonProps } from '@workspace/airo-ui/components/button';
import {
Dialog,
DialogContent,
@@ -43,13 +43,13 @@ export default function Recharge(props: Readonly<ButtonProps>) {
</DialogTrigger>
<DialogContent className='flex h-full flex-col overflow-hidden md:h-auto'>
<DialogHeader>
<DialogTitle>{t('balanceRecharge')}</DialogTitle>
<DialogTitle className={'text-4xl'}>{t('balanceRecharge')}</DialogTitle>
<DialogDescription>{t('rechargeDescription')}</DialogDescription>
</DialogHeader>
<div className='flex flex-col justify-between text-sm'>
<div className='mt-8 flex flex-col justify-between text-sm'>
<div className='grid gap-3'>
<div className='font-semibold'>{t('rechargeAmount')}</div>
<div className='flex'>
<div className='mb-8 flex'>
<EnhancedInput
type='number'
placeholder={t('enterAmount')}
@@ -73,27 +73,30 @@ export default function Recharge(props: Readonly<ButtonProps>) {
onChange={(value) => setParams({ ...params, payment: value })}
/>
</div>
<Button
className='fixed bottom-0 left-0 w-full rounded-none md:relative md:mt-6'
disabled={loading || !params.amount}
onClick={() => {
startTransition(async () => {
try {
const response = await recharge(params);
const orderNo = response.data.data?.order_no;
if (orderNo) {
router.push(`/payment?order_no=${orderNo}`);
setOpen(false);
<div className={'flex items-center justify-center'}>
<AiroButton
variant={'primary'}
className='fixed bottom-0 left-0 md:relative md:mt-6'
disabled={loading || !params.amount}
onClick={() => {
startTransition(async () => {
try {
const response = await recharge(params);
const orderNo = response.data.data?.order_no;
if (orderNo) {
router.push(`/payment?order_no=${orderNo}`);
setOpen(false);
}
} catch (error) {
/* empty */
}
} catch (error) {
/* empty */
}
});
}}
>
{loading && <LoaderCircle className='mr-2 animate-spin' />}
{t('rechargeNow')}
</Button>
});
}}
>
{loading && <LoaderCircle className='mr-2 animate-spin' />}
{t('rechargeNow')}
</AiroButton>
</div>
</div>
</DialogContent>
</Dialog>