🐛 fix(user): Update user subscribe display

This commit is contained in:
web@ppanel 2025-04-09 03:27:22 -04:00
parent 7023875548
commit 3bb714d15c
31 changed files with 393 additions and 206 deletions

View File

@ -1,24 +1,23 @@
<a name="readme-top"></a> <a name="readme-top"></a>
# Changelog # Changelog
# [1.0.0-beta.34](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.33...v1.0.0-beta.34) (2025-04-02) # [1.0.0-beta.34](https://github.com/perfect-panel/ppanel-web/compare/v1.0.0-beta.33...v1.0.0-beta.34) (2025-04-02)
### ✨ Features ### ✨ Features
* **admin**: Add application and rule management entries to localization files ([8b43e69](https://github.com/perfect-panel/ppanel-web/commit/8b43e69)) - **admin**: Add application and rule management entries to localization files ([8b43e69](https://github.com/perfect-panel/ppanel-web/commit/8b43e69))
* **api**: Add an interface to obtain user subscription details, update related type definitions and localized text ([cf5c39c](https://github.com/perfect-panel/ppanel-web/commit/cf5c39c)) - **api**: Add an interface to obtain user subscription details, update related type definitions and localized text ([cf5c39c](https://github.com/perfect-panel/ppanel-web/commit/cf5c39c))
* **user**: Integrate subscription list into user management, update request parameters and types ([8d49dac](https://github.com/perfect-panel/ppanel-web/commit/8d49dac)) - **user**: Integrate subscription list into user management, update request parameters and types ([8d49dac](https://github.com/perfect-panel/ppanel-web/commit/8d49dac))
### 🐛 Bug Fixes ### 🐛 Bug Fixes
* **admin**: Hidden versions and system upgrades ([64cd842](https://github.com/perfect-panel/ppanel-web/commit/64cd842)) - **admin**: Hidden versions and system upgrades ([64cd842](https://github.com/perfect-panel/ppanel-web/commit/64cd842))
* **admin**: Modify the label type in the rule form to a string array ([a7aa5fe](https://github.com/perfect-panel/ppanel-web/commit/a7aa5fe)) - **admin**: Modify the label type in the rule form to a string array ([a7aa5fe](https://github.com/perfect-panel/ppanel-web/commit/a7aa5fe))
* **node**: Handle potential null value for online users count ([fa2fb28](https://github.com/perfect-panel/ppanel-web/commit/fa2fb28)) - **node**: Handle potential null value for online users count ([fa2fb28](https://github.com/perfect-panel/ppanel-web/commit/fa2fb28))
* **subscribe**: Add value prop to field in subscription form for proper state management ([328838d](https://github.com/perfect-panel/ppanel-web/commit/328838d)) - **subscribe**: Add value prop to field in subscription form for proper state management ([328838d](https://github.com/perfect-panel/ppanel-web/commit/328838d))
* **subscribe**: Refactor discount calculations and default selection logic in subscription forms ([423b240](https://github.com/perfect-panel/ppanel-web/commit/423b240)) - **subscribe**: Refactor discount calculations and default selection logic in subscription forms ([423b240](https://github.com/perfect-panel/ppanel-web/commit/423b240))
* **subscribe**: Update default selection logic in subscription form to ensure proper state management ([ef15374](https://github.com/perfect-panel/ppanel-web/commit/ef15374)) - **subscribe**: Update default selection logic in subscription form to ensure proper state management ([ef15374](https://github.com/perfect-panel/ppanel-web/commit/ef15374))
<a name="readme-top"></a> <a name="readme-top"></a>

View File

@ -1748,6 +1748,7 @@ declare namespace API {
subscribe: Subscribe; subscribe: Subscribe;
start_time: number; start_time: number;
expire_time: number; expire_time: number;
finished_at: number;
reset_time: number; reset_time: number;
traffic: number; traffic: number;
download: number; download: number;

View File

@ -25,6 +25,24 @@ export async function getApplication(options?: { [key: string]: any }) {
}); });
} }
/** Check verification code POST /v1/common/check_verification_code */
export async function checkVerificationCode(
body: API.CheckVerificationCodeRequest,
options?: { [key: string]: any },
) {
return request<API.Response & { data?: API.CheckVerificationCodeRespone }>(
'/v1/common/check_verification_code',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
},
);
}
/** Get verification code POST /v1/common/send_code */ /** Get verification code POST /v1/common/send_code */
export async function sendEmailCode(body: API.SendCodeRequest, options?: { [key: string]: any }) { export async function sendEmailCode(body: API.SendCodeRequest, options?: { [key: string]: any }) {
return request<API.Response & { data?: API.SendCodeResponse }>('/v1/common/send_code', { return request<API.Response & { data?: API.SendCodeResponse }>('/v1/common/send_code', {

View File

@ -121,6 +121,17 @@ declare namespace API {
telephone: string; telephone: string;
}; };
type CheckVerificationCodeRequest = {
method: 'email' | 'mobile';
account: string;
code: string;
type: number;
};
type CheckVerificationCodeRespone = {
status: boolean;
};
type CloseOrderRequest = { type CloseOrderRequest = {
orderNo: string; orderNo: string;
}; };
@ -910,6 +921,7 @@ declare namespace API {
subscribe: Subscribe; subscribe: Subscribe;
start_time: number; start_time: number;
expire_time: number; expire_time: number;
finished_at: number;
reset_time: number; reset_time: number;
traffic: number; traffic: number;
download: number; download: number;

View File

@ -32,6 +32,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@workspace/ui/componen
import { Separator } from '@workspace/ui/components/separator'; import { Separator } from '@workspace/ui/components/separator';
import { Tabs, TabsList, TabsTrigger } from '@workspace/ui/components/tabs'; import { Tabs, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
import { Icon } from '@workspace/ui/custom-components/icon'; import { Icon } from '@workspace/ui/custom-components/icon';
import { cn } from '@workspace/ui/lib/utils';
import { differenceInDays, formatDate, isBrowser } from '@workspace/ui/utils'; import { differenceInDays, formatDate, isBrowser } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import Image from 'next/image'; import Image from 'next/image';
@ -88,6 +89,12 @@ export default function Content() {
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
}); });
const statusWatermarks = {
2: t('finished'),
3: t('expired'),
4: t('deducted'),
};
return ( return (
<> <>
{userSubscribe.length ? ( {userSubscribe.length ? (
@ -159,209 +166,259 @@ export default function Content() {
</Tabs> </Tabs>
)} )}
</div> </div>
{userSubscribe.map((item) => ( {userSubscribe.map((item) => {
<Card key={item.id}> return (
<CardHeader className='flex flex-row flex-wrap items-center justify-between gap-2 space-y-0'> <Card
<CardTitle className='font-medium'> key={item.id}
{item.subscribe.name} className={cn('relative', {
<p className='text-foreground/50 mt-1 text-sm'>{formatDate(item.start_time)}</p> 'relative opacity-80 grayscale': item.status === 3,
</CardTitle> 'relative hidden opacity-60 blur-[0.3px] grayscale': item.status === 4,
<div className='flex flex-wrap gap-2'> })}
<AlertDialog> >
<AlertDialogTrigger asChild> {item.status >= 2 && (
<Button size='sm' variant='destructive'> <div
{t('resetSubscription')} className={cn(
</Button> 'pointer-events-none absolute left-0 top-0 z-10 h-full w-full overflow-hidden mix-blend-difference',
</AlertDialogTrigger> {
<AlertDialogContent> 'text-destructive': item.status === 2,
<AlertDialogHeader> 'text-white': item.status === 3 || item.status === 4,
<AlertDialogTitle>{t('prompt')}</AlertDialogTitle> },
<AlertDialogDescription> )}
{t('confirmResetSubscription')} style={{
</AlertDialogDescription> filter: 'contrast(200%) brightness(150%) invert(0.2)',
</AlertDialogHeader> }}
<AlertDialogFooter> >
<AlertDialogCancel>{t('cancel')}</AlertDialogCancel> <div className='absolute inset-0'>
<AlertDialogAction {Array.from({ length: 16 }).map((_, i) => {
onClick={async () => { const row = Math.floor(i / 4);
await resetUserSubscribeToken({ const col = i % 4;
user_subscribe_id: item.id, // 计算位置百分比
}); const top = 10 + row * 25 + (col % 2 === 0 ? 5 : -5);
await refetch(); const left = 5 + col * 30 + (row % 2 === 0 ? 0 : 10);
toast.success(t('resetSuccess'));
}}
>
{t('confirm')}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<ResetTraffic id={item.id} replacement={item.subscribe.replacement} />
<Renewal id={item.id} subscribe={item.subscribe} />
<Unsubscribe id={item.id} allowDeduction={item.subscribe.allow_deduction} />
</div>
</CardHeader>
<CardContent>
<ul className='grid grid-cols-2 gap-3 *:flex *:flex-col *:justify-between lg:grid-cols-4'>
<li>
<span className='text-muted-foreground'>{t('used')}</span>
<span className='text-2xl font-bold'>
<Display
type='traffic'
value={item.upload + item.download}
unlimited={!item.traffic}
/>
</span>
</li>
<li>
<span className='text-muted-foreground'>{t('totalTraffic')}</span>
<span className='text-2xl font-bold'>
<Display type='traffic' value={item.traffic} unlimited={!item.traffic} />
</span>
</li>
<li>
<span className='text-muted-foreground'>{t('nextResetDays')}</span>
<span className='text-2xl font-semibold'>
{item.reset_time
? differenceInDays(new Date(item.reset_time), new Date())
: t('noReset')}
</span>
</li>
<li>
<span className='text-muted-foreground'>{t('expirationDays')}</span>
<span className='text-2xl font-semibold'>
{}
{item.expire_time
? differenceInDays(new Date(item.expire_time), new Date()) || t('unknown')
: t('noLimit')}
</span>
</li>
</ul>
<Separator className='mt-4' />
<Accordion type='single' collapsible defaultValue='0' className='w-full'>
{getUserSubscribe(item.token, protocol)?.map((url, index) => (
<AccordionItem key={url} value={String(index)}>
<AccordionTrigger className='hover:no-underline'>
<div className='flex w-full flex-row items-center justify-between'>
<CardTitle className='text-sm font-medium'>
{t('subscriptionUrl')} {index + 1}
</CardTitle>
<CopyToClipboard return (
text={url} <span
onCopy={(text, result) => { key={i}
if (result) { className='absolute rotate-[-30deg] whitespace-nowrap text-lg font-black opacity-40'
toast.success(t('copySuccess')); style={{
} top: `${top}%`,
left: `${left}%`,
textShadow: '0px 0px 1px rgba(255,255,255,0.5)',
}} }}
> >
<span {statusWatermarks[item.status as keyof typeof statusWatermarks]}
className='text-primary hover:bg-accent mr-4 flex cursor-pointer rounded p-2 text-sm' </span>
onClick={(e) => e.stopPropagation()} );
})}
</div>
</div>
)}
<CardHeader className='flex flex-row flex-wrap items-center justify-between gap-2 space-y-0'>
<CardTitle className='font-medium'>
{item.subscribe.name}
<p className='text-foreground/50 mt-1 text-sm'>{formatDate(item.start_time)}</p>
</CardTitle>
{item.status !== 4 && (
<div className='flex flex-wrap gap-2'>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button size='sm' variant='destructive'>
{t('resetSubscription')}
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{t('prompt')}</AlertDialogTitle>
<AlertDialogDescription>
{t('confirmResetSubscription')}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{t('cancel')}</AlertDialogCancel>
<AlertDialogAction
onClick={async () => {
await resetUserSubscribeToken({
user_subscribe_id: item.id,
});
await refetch();
toast.success(t('resetSuccess'));
}}
> >
<Icon icon='uil:copy' className='mr-2 size-5' /> {t('confirm')}
{t('copy')} </AlertDialogAction>
</span> </AlertDialogFooter>
</CopyToClipboard> </AlertDialogContent>
</div> </AlertDialog>
</AccordionTrigger> <ResetTraffic id={item.id} replacement={item.subscribe.replacement} />
<AccordionContent> <Renewal id={item.id} subscribe={item.subscribe} />
<div className='grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6'>
{applications
?.filter((application) => {
const platformApps = application.platform?.[platform];
return platformApps && platformApps.length > 0;
})
.map((application) => {
const platformApps = application.platform?.[platform];
const app =
platformApps?.find((item) => item.is_default) || platformApps?.[0];
if (!app) return null;
const handleCopy = (text: string, result: boolean) => { <Unsubscribe id={item.id} allowDeduction={item.subscribe.allow_deduction} />
</div>
)}
</CardHeader>
<CardContent>
<ul className='grid grid-cols-2 gap-3 *:flex *:flex-col *:justify-between lg:grid-cols-4'>
<li>
<span className='text-muted-foreground'>{t('used')}</span>
<span className='text-2xl font-bold'>
<Display
type='traffic'
value={item.upload + item.download}
unlimited={!item.traffic}
/>
</span>
</li>
<li>
<span className='text-muted-foreground'>{t('totalTraffic')}</span>
<span className='text-2xl font-bold'>
<Display type='traffic' value={item.traffic} unlimited={!item.traffic} />
</span>
</li>
<li>
<span className='text-muted-foreground'>{t('nextResetDays')}</span>
<span className='text-2xl font-semibold'>
{item.reset_time
? differenceInDays(new Date(item.reset_time), new Date())
: t('noReset')}
</span>
</li>
<li>
<span className='text-muted-foreground'>{t('expirationDays')}</span>
<span className='text-2xl font-semibold'>
{}
{item.expire_time
? differenceInDays(new Date(item.expire_time), new Date()) || t('unknown')
: t('noLimit')}
</span>
</li>
</ul>
<Separator className='mt-4' />
<Accordion type='single' collapsible defaultValue='0' className='w-full'>
{getUserSubscribe(item.token, protocol)?.map((url, index) => (
<AccordionItem key={url} value={String(index)}>
<AccordionTrigger className='hover:no-underline'>
<div className='flex w-full flex-row items-center justify-between'>
<CardTitle className='text-sm font-medium'>
{t('subscriptionUrl')} {index + 1}
</CardTitle>
<CopyToClipboard
text={url}
onCopy={(text, result) => {
if (result) { if (result) {
const href = getAppSubLink(application.subscribe_type, url); toast.success(t('copySuccess'));
const showSuccessMessage = () => {
toast.success(
<>
<p>{t('copySuccess')}</p>
<br />
<p>{t('manualImportMessage')}</p>
</>,
);
};
if (isBrowser() && href) {
window.location.href = href;
const checkRedirect = setTimeout(() => {
if (window.location.href !== href) {
showSuccessMessage();
}
clearTimeout(checkRedirect);
}, 1000);
return;
}
showSuccessMessage();
} }
}; }}
>
return ( <span
<div className='text-primary hover:bg-accent mr-4 flex cursor-pointer rounded p-2 text-sm'
key={application.name} onClick={(e) => e.stopPropagation()}
className='text-muted-foreground flex size-full flex-col items-center justify-between gap-2 text-xs' >
> <Icon icon='uil:copy' className='mr-2 size-5' />
<span>{application.name}</span> {t('copy')}
</span>
{application.icon && ( </CopyToClipboard>
<Image
src={application.icon}
alt={application.name}
width={64}
height={64}
className='p-1'
/>
)}
<div className='flex'>
<Button
size='sm'
variant='secondary'
className='rounded-r-none px-1.5'
asChild
>
<Link href={app.url}>{t('download')}</Link>
</Button>
<CopyToClipboard
text={getAppSubLink(application.subscribe_type, url) || url}
onCopy={handleCopy}
>
<Button size='sm' className='rounded-l-none p-2'>
{t('import')}
</Button>
</CopyToClipboard>
</div>
</div>
);
})}
<div className='text-muted-foreground hidden size-full flex-col items-center justify-between gap-2 text-sm lg:flex'>
<span>{t('qrCode')}</span>
<QRCodeCanvas
value={url}
size={80}
bgColor='transparent'
fgColor='rgb(59, 130, 246)'
/>
<span className='text-center'>{t('scanToSubscribe')}</span>
</div> </div>
</div> </AccordionTrigger>
</AccordionContent> <AccordionContent>
</AccordionItem> <div className='grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6'>
))} {applications
</Accordion> ?.filter((application) => {
</CardContent> const platformApps = application.platform?.[platform];
</Card> return platformApps && platformApps.length > 0;
))} })
.map((application) => {
const platformApps = application.platform?.[platform];
const app =
platformApps?.find((item) => item.is_default) ||
platformApps?.[0];
if (!app) return null;
const handleCopy = (text: string, result: boolean) => {
if (result) {
const href = getAppSubLink(application.subscribe_type, url);
const showSuccessMessage = () => {
toast.success(
<>
<p>{t('copySuccess')}</p>
<br />
<p>{t('manualImportMessage')}</p>
</>,
);
};
if (isBrowser() && href) {
window.location.href = href;
const checkRedirect = setTimeout(() => {
if (window.location.href !== href) {
showSuccessMessage();
}
clearTimeout(checkRedirect);
}, 1000);
return;
}
showSuccessMessage();
}
};
return (
<div
key={application.name}
className='text-muted-foreground flex size-full flex-col items-center justify-between gap-2 text-xs'
>
<span>{application.name}</span>
{application.icon && (
<Image
src={application.icon}
alt={application.name}
width={64}
height={64}
className='p-1'
/>
)}
<div className='flex'>
<Button
size='sm'
variant='secondary'
className='rounded-r-none px-1.5'
asChild
>
<Link href={app.url}>{t('download')}</Link>
</Button>
<CopyToClipboard
text={getAppSubLink(application.subscribe_type, url) || url}
onCopy={handleCopy}
>
<Button size='sm' className='rounded-l-none p-2'>
{t('import')}
</Button>
</CopyToClipboard>
</div>
</div>
);
})}
<div className='text-muted-foreground hidden size-full flex-col items-center justify-between gap-2 text-sm lg:flex'>
<span>{t('qrCode')}</span>
<QRCodeCanvas
value={url}
size={80}
bgColor='transparent'
fgColor='rgb(59, 130, 246)'
/>
<span className='text-center'>{t('scanToSubscribe')}</span>
</div>
</div>
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</CardContent>
</Card>
);
})}
</> </>
) : ( ) : (
<> <>

View File

@ -5,8 +5,11 @@
"copy": "kopírovat", "copy": "kopírovat",
"copyFailure": "Kopírování selhalo, prosím zkopírujte ručně", "copyFailure": "Kopírování selhalo, prosím zkopírujte ručně",
"copySuccess": "Kopírování úspěšné", "copySuccess": "Kopírování úspěšné",
"deducted": "Zrušeno",
"download": "stáhnout", "download": "stáhnout",
"expirationDays": "Doba platnosti/dny", "expirationDays": "Doba platnosti/dny",
"expired": "Vypršelo",
"finished": "Provoz vyčerpán",
"import": "Importovat", "import": "Importovat",
"latestAnnouncement": "Nejnovější oznámení", "latestAnnouncement": "Nejnovější oznámení",
"manualImportMessage": "Tato aplikace momentálně nepodporuje spuštění, prosím importujte ručně, adresa předplatného byla automaticky zkopírována", "manualImportMessage": "Tato aplikace momentálně nepodporuje spuštění, prosím importujte ručně, adresa předplatného byla automaticky zkopírována",

View File

@ -5,8 +5,11 @@
"copy": "Kopieren", "copy": "Kopieren",
"copyFailure": "Kopieren fehlgeschlagen, bitte manuell kopieren", "copyFailure": "Kopieren fehlgeschlagen, bitte manuell kopieren",
"copySuccess": "Kopieren erfolgreich", "copySuccess": "Kopieren erfolgreich",
"deducted": "Storniert",
"download": "Herunterladen", "download": "Herunterladen",
"expirationDays": "Ablaufzeit/Tage", "expirationDays": "Ablaufzeit/Tage",
"expired": "Abgelaufen",
"finished": "Verkehr erschöpft",
"import": "Importieren", "import": "Importieren",
"latestAnnouncement": "Neueste Ankündigung", "latestAnnouncement": "Neueste Ankündigung",
"manualImportMessage": "Diese App unterstützt derzeit keine Aktivierung. Bitte manuell importieren, die Abonnementadresse wurde automatisch kopiert.", "manualImportMessage": "Diese App unterstützt derzeit keine Aktivierung. Bitte manuell importieren, die Abonnementadresse wurde automatisch kopiert.",

View File

@ -5,8 +5,11 @@
"copy": "Copy", "copy": "Copy",
"copyFailure": "Copy failed, please copy manually", "copyFailure": "Copy failed, please copy manually",
"copySuccess": "Copy successful", "copySuccess": "Copy successful",
"deducted": "Canceled",
"download": "Download", "download": "Download",
"expirationDays": "Expiration Days", "expirationDays": "Expiration Days",
"expired": "Expired",
"finished": "Traffic exhausted",
"import": "Import", "import": "Import",
"latestAnnouncement": "Latest Announcement", "latestAnnouncement": "Latest Announcement",
"manualImportMessage": "This app does not support activation. Please import manually. The subscription address has been copied.", "manualImportMessage": "This app does not support activation. Please import manually. The subscription address has been copied.",

View File

@ -5,8 +5,11 @@
"copy": "Copiar", "copy": "Copiar",
"copyFailure": "Error al copiar, por favor copia manualmente", "copyFailure": "Error al copiar, por favor copia manualmente",
"copySuccess": "Copia exitosa", "copySuccess": "Copia exitosa",
"deducted": "Cancelado",
"download": "descargar", "download": "descargar",
"expirationDays": "Días de vencimiento", "expirationDays": "Días de vencimiento",
"expired": "Caducado",
"finished": "Tráfico agotado",
"import": "importar", "import": "importar",
"latestAnnouncement": "Último anuncio", "latestAnnouncement": "Último anuncio",
"manualImportMessage": "Esta aplicación no admite la activación por el momento. Por favor, importe manualmente. La dirección de suscripción ha sido copiada automáticamente.", "manualImportMessage": "Esta aplicación no admite la activación por el momento. Por favor, importe manualmente. La dirección de suscripción ha sido copiada automáticamente.",

View File

@ -5,8 +5,11 @@
"copy": "copiar", "copy": "copiar",
"copyFailure": "Error al copiar, por favor copia manualmente", "copyFailure": "Error al copiar, por favor copia manualmente",
"copySuccess": "Copia exitosa", "copySuccess": "Copia exitosa",
"deducted": "Cancelado",
"download": "descargar", "download": "descargar",
"expirationDays": "Días de vencimiento", "expirationDays": "Días de vencimiento",
"expired": "Expirado",
"finished": "Tráfico agotado",
"import": "Importar", "import": "Importar",
"latestAnnouncement": "Último anuncio", "latestAnnouncement": "Último anuncio",
"manualImportMessage": "Esta aplicación no admite la activación por el momento, por favor importe manualmente, la dirección de suscripción se ha copiado automáticamente", "manualImportMessage": "Esta aplicación no admite la activación por el momento, por favor importe manualmente, la dirección de suscripción se ha copiado automáticamente",

View File

@ -5,8 +5,11 @@
"copy": "کپی", "copy": "کپی",
"copyFailure": "کپی ناموفق بود، لطفاً به صورت دستی کپی کنید", "copyFailure": "کپی ناموفق بود، لطفاً به صورت دستی کپی کنید",
"copySuccess": "کپی با موفقیت انجام شد", "copySuccess": "کپی با موفقیت انجام شد",
"deducted": "لغو شده",
"download": "دانلود", "download": "دانلود",
"expirationDays": "روزهای انقضا", "expirationDays": "روزهای انقضا",
"expired": "منقضی شده",
"finished": "ترافیک تمام شده",
"import": "وارد کردن", "import": "وارد کردن",
"latestAnnouncement": "آخرین اعلامیه", "latestAnnouncement": "آخرین اعلامیه",
"manualImportMessage": "این برنامه از فعال‌سازی پشتیبانی نمی‌کند. لطفاً به صورت دستی وارد کنید. آدرس اشتراک کپی شده است.", "manualImportMessage": "این برنامه از فعال‌سازی پشتیبانی نمی‌کند. لطفاً به صورت دستی وارد کنید. آدرس اشتراک کپی شده است.",

View File

@ -5,8 +5,11 @@
"copy": "kopioi", "copy": "kopioi",
"copyFailure": "Kopiointi epäonnistui, kopioi manuaalisesti", "copyFailure": "Kopiointi epäonnistui, kopioi manuaalisesti",
"copySuccess": "Kopiointi onnistui", "copySuccess": "Kopiointi onnistui",
"deducted": "Peruutettu",
"download": "lataa", "download": "lataa",
"expirationDays": "Vanhentumispäivät", "expirationDays": "Vanhentumispäivät",
"expired": "Vanhentunut",
"finished": "Liikenne loppunut",
"import": "Tuo", "import": "Tuo",
"latestAnnouncement": "Viimeisin ilmoitus", "latestAnnouncement": "Viimeisin ilmoitus",
"manualImportMessage": "Tämä sovellus ei tue herätystä tällä hetkellä, tuo manuaalisesti, tilausosoite on kopioitu automaattisesti", "manualImportMessage": "Tämä sovellus ei tue herätystä tällä hetkellä, tuo manuaalisesti, tilausosoite on kopioitu automaattisesti",

View File

@ -5,8 +5,11 @@
"copy": "Copier", "copy": "Copier",
"copyFailure": "Échec de la copie, veuillez copier manuellement", "copyFailure": "Échec de la copie, veuillez copier manuellement",
"copySuccess": "Copie réussie", "copySuccess": "Copie réussie",
"deducted": "Annulé",
"download": "télécharger", "download": "télécharger",
"expirationDays": "Date d'expiration/jours", "expirationDays": "Date d'expiration/jours",
"expired": "Expiré",
"finished": "Trafic épuisé",
"import": "Importer", "import": "Importer",
"latestAnnouncement": "Dernière annonce", "latestAnnouncement": "Dernière annonce",
"manualImportMessage": "Cette application ne prend pas encore en charge l'activation, veuillez importer manuellement, l'adresse d'abonnement a été copiée automatiquement", "manualImportMessage": "Cette application ne prend pas encore en charge l'activation, veuillez importer manuellement, l'adresse d'abonnement a été copiée automatiquement",

View File

@ -5,8 +5,11 @@
"copy": "प्रतिलिपि", "copy": "प्रतिलिपि",
"copyFailure": "प्रतिलिपि बनाने में विफल, कृपया मैन्युअल रूप से प्रतिलिपि बनाएँ", "copyFailure": "प्रतिलिपि बनाने में विफल, कृपया मैन्युअल रूप से प्रतिलिपि बनाएँ",
"copySuccess": "प्रतिलिपि सफल", "copySuccess": "प्रतिलिपि सफल",
"deducted": "रद्द किया गया",
"download": "डाउनलोड", "download": "डाउनलोड",
"expirationDays": "समाप्ति समय/दिन", "expirationDays": "समाप्ति समय/दिन",
"expired": "समाप्त",
"finished": "ट्रैफ़िक समाप्त",
"import": "आयात", "import": "आयात",
"latestAnnouncement": "नवीनतम घोषणा", "latestAnnouncement": "नवीनतम घोषणा",
"manualImportMessage": "यह ऐप फिलहाल जागृत करने का समर्थन नहीं करता है, कृपया मैन्युअल रूप से आयात करें, सदस्यता पता स्वचालित रूप से कॉपी कर लिया गया है।", "manualImportMessage": "यह ऐप फिलहाल जागृत करने का समर्थन नहीं करता है, कृपया मैन्युअल रूप से आयात करें, सदस्यता पता स्वचालित रूप से कॉपी कर लिया गया है।",

View File

@ -5,8 +5,11 @@
"copy": "Másolás", "copy": "Másolás",
"copyFailure": "Másolás sikertelen, kérjük, másolja kézzel", "copyFailure": "Másolás sikertelen, kérjük, másolja kézzel",
"copySuccess": "Sikeres másolás", "copySuccess": "Sikeres másolás",
"deducted": "Törölve",
"download": "letöltés", "download": "letöltés",
"expirationDays": "Lejárati idő/nap", "expirationDays": "Lejárati idő/nap",
"expired": "Lejárt",
"finished": "Forgalom kimerült",
"import": "importálás", "import": "importálás",
"latestAnnouncement": "Legújabb bejelentés", "latestAnnouncement": "Legújabb bejelentés",
"manualImportMessage": "Ez az alkalmazás jelenleg nem támogatja az ébresztést, kérjük, importálja manuálisan, az előfizetési cím automatikusan másolva lett", "manualImportMessage": "Ez az alkalmazás jelenleg nem támogatja az ébresztést, kérjük, importálja manuálisan, az előfizetési cím automatikusan másolva lett",

View File

@ -5,8 +5,11 @@
"copy": "コピー", "copy": "コピー",
"copyFailure": "コピーに失敗しました。手動でコピーしてください", "copyFailure": "コピーに失敗しました。手動でコピーしてください",
"copySuccess": "コピー成功", "copySuccess": "コピー成功",
"deducted": "キャンセルされた",
"download": "ダウンロード", "download": "ダウンロード",
"expirationDays": "有効期限/日", "expirationDays": "有効期限/日",
"expired": "期限切れ",
"finished": "トラフィックが使い切られました",
"import": "インポート", "import": "インポート",
"latestAnnouncement": "最新のお知らせ", "latestAnnouncement": "最新のお知らせ",
"manualImportMessage": "このアプリは現在起動をサポートしていません。手動でインポートしてください。サブスクリプションアドレスは自動的にコピーされました。", "manualImportMessage": "このアプリは現在起動をサポートしていません。手動でインポートしてください。サブスクリプションアドレスは自動的にコピーされました。",

View File

@ -5,8 +5,11 @@
"copy": "복사", "copy": "복사",
"copyFailure": "복사 실패, 수동으로 복사하세요", "copyFailure": "복사 실패, 수동으로 복사하세요",
"copySuccess": "복사 성공", "copySuccess": "복사 성공",
"deducted": "취소됨",
"download": "다운로드", "download": "다운로드",
"expirationDays": "만료일/일", "expirationDays": "만료일/일",
"expired": "만료됨",
"finished": "트래픽 소진됨",
"import": "가져오기", "import": "가져오기",
"latestAnnouncement": "최신 공지", "latestAnnouncement": "최신 공지",
"manualImportMessage": "이 앱은 현재 호출을 지원하지 않습니다. 수동으로 가져오세요. 구독 주소가 자동으로 복사되었습니다.", "manualImportMessage": "이 앱은 현재 호출을 지원하지 않습니다. 수동으로 가져오세요. 구독 주소가 자동으로 복사되었습니다.",

View File

@ -5,8 +5,11 @@
"copy": "kopier", "copy": "kopier",
"copyFailure": "Kopiering mislyktes, vennligst kopier manuelt", "copyFailure": "Kopiering mislyktes, vennligst kopier manuelt",
"copySuccess": "Kopiering vellykket", "copySuccess": "Kopiering vellykket",
"deducted": "Avbrutt",
"download": "last ned", "download": "last ned",
"expirationDays": "Utløpsdato/dager", "expirationDays": "Utløpsdato/dager",
"expired": "Utløpt",
"finished": "Trafikk brukt opp",
"import": "Importer", "import": "Importer",
"latestAnnouncement": "Siste kunngjøring", "latestAnnouncement": "Siste kunngjøring",
"manualImportMessage": "Denne appen støtter foreløpig ikke oppstart, vennligst importer manuelt, abonnementsadressen er automatisk kopiert", "manualImportMessage": "Denne appen støtter foreløpig ikke oppstart, vennligst importer manuelt, abonnementsadressen er automatisk kopiert",

View File

@ -5,8 +5,11 @@
"copy": "kopiuj", "copy": "kopiuj",
"copyFailure": "Kopiowanie nie powiodło się, proszę skopiować ręcznie", "copyFailure": "Kopiowanie nie powiodło się, proszę skopiować ręcznie",
"copySuccess": "Skopiowano pomyślnie", "copySuccess": "Skopiowano pomyślnie",
"deducted": "Anulowane",
"download": "pobierz", "download": "pobierz",
"expirationDays": "Czas wygaśnięcia/dni", "expirationDays": "Czas wygaśnięcia/dni",
"expired": "Wygasło",
"finished": "Wykończony ruch",
"import": "Importuj", "import": "Importuj",
"latestAnnouncement": "Najnowsze ogłoszenie", "latestAnnouncement": "Najnowsze ogłoszenie",
"manualImportMessage": "Ta aplikacja tymczasowo nie obsługuje wywoływania, proszę zaimportować ręcznie, adres subskrypcji został automatycznie skopiowany", "manualImportMessage": "Ta aplikacja tymczasowo nie obsługuje wywoływania, proszę zaimportować ręcznie, adres subskrypcji został automatycznie skopiowany",

View File

@ -5,8 +5,11 @@
"copy": "Copiar", "copy": "Copiar",
"copyFailure": "Falha ao copiar, por favor copie manualmente", "copyFailure": "Falha ao copiar, por favor copie manualmente",
"copySuccess": "Cópia bem-sucedida", "copySuccess": "Cópia bem-sucedida",
"deducted": "Cancelado",
"download": "baixar", "download": "baixar",
"expirationDays": "Data de expiração/dias", "expirationDays": "Data de expiração/dias",
"expired": "Expirado",
"finished": "Tráfego esgotado",
"import": "Importar", "import": "Importar",
"latestAnnouncement": "Último Anúncio", "latestAnnouncement": "Último Anúncio",
"manualImportMessage": "Este aplicativo não suporta ativação no momento, por favor, importe manualmente. O endereço de assinatura foi copiado automaticamente.", "manualImportMessage": "Este aplicativo não suporta ativação no momento, por favor, importe manualmente. O endereço de assinatura foi copiado automaticamente.",

View File

@ -5,8 +5,11 @@
"copy": "Copiază", "copy": "Copiază",
"copyFailure": "Copierea a eșuat, vă rugăm să copiați manual", "copyFailure": "Copierea a eșuat, vă rugăm să copiați manual",
"copySuccess": "Copiere reușită", "copySuccess": "Copiere reușită",
"deducted": "Anulat",
"download": "descărcare", "download": "descărcare",
"expirationDays": "Zile până la expirare", "expirationDays": "Zile până la expirare",
"expired": "Expirat",
"finished": "Trafic epuizat",
"import": "Import", "import": "Import",
"latestAnnouncement": "Ultimul anunț", "latestAnnouncement": "Ultimul anunț",
"manualImportMessage": "Această aplicație nu suportă momentan activarea, vă rugăm să importați manual, adresa de abonament a fost copiată automat", "manualImportMessage": "Această aplicație nu suportă momentan activarea, vă rugăm să importați manual, adresa de abonament a fost copiată automat",

View File

@ -5,8 +5,11 @@
"copy": "Копировать", "copy": "Копировать",
"copyFailure": "Не удалось скопировать, пожалуйста, скопируйте вручную", "copyFailure": "Не удалось скопировать, пожалуйста, скопируйте вручную",
"copySuccess": "Копирование успешно", "copySuccess": "Копирование успешно",
"deducted": "Отменено",
"download": "скачать", "download": "скачать",
"expirationDays": "Срок действия/дни", "expirationDays": "Срок действия/дни",
"expired": "Истекло",
"finished": "Трафик исчерпан",
"import": "Импорт", "import": "Импорт",
"latestAnnouncement": "Последнее объявление", "latestAnnouncement": "Последнее объявление",
"manualImportMessage": "Это приложение временно не поддерживает вызов, пожалуйста, импортируйте вручную, адрес подписки уже скопирован", "manualImportMessage": "Это приложение временно не поддерживает вызов, пожалуйста, импортируйте вручную, адрес подписки уже скопирован",

View File

@ -5,8 +5,11 @@
"copy": "คัดลอก", "copy": "คัดลอก",
"copyFailure": "คัดลอกไม่สำเร็จ กรุณาคัดลอกด้วยตนเอง", "copyFailure": "คัดลอกไม่สำเร็จ กรุณาคัดลอกด้วยตนเอง",
"copySuccess": "คัดลอกสำเร็จ", "copySuccess": "คัดลอกสำเร็จ",
"deducted": "ยกเลิก",
"download": "ดาวน์โหลด", "download": "ดาวน์โหลด",
"expirationDays": "วันหมดอายุ/วัน", "expirationDays": "วันหมดอายุ/วัน",
"expired": "หมดอายุ",
"finished": "การใช้งานหมด",
"import": "นำเข้า", "import": "นำเข้า",
"latestAnnouncement": "ประกาศล่าสุด", "latestAnnouncement": "ประกาศล่าสุด",
"manualImportMessage": "แอปนี้ยังไม่รองรับการเปิดใช้งาน กรุณานำเข้าด้วยตนเอง ที่อยู่การสมัครสมาชิกได้ถูกคัดลอกอัตโนมัติแล้ว", "manualImportMessage": "แอปนี้ยังไม่รองรับการเปิดใช้งาน กรุณานำเข้าด้วยตนเอง ที่อยู่การสมัครสมาชิกได้ถูกคัดลอกอัตโนมัติแล้ว",

View File

@ -5,8 +5,11 @@
"copy": "kopyala", "copy": "kopyala",
"copyFailure": "Kopyalama başarısız oldu, lütfen elle kopyalayın", "copyFailure": "Kopyalama başarısız oldu, lütfen elle kopyalayın",
"copySuccess": "Kopyalama başarılı", "copySuccess": "Kopyalama başarılı",
"deducted": "İptal edildi",
"download": "indir", "download": "indir",
"expirationDays": "Son Kullanma Süresi/Gün", "expirationDays": "Son Kullanma Süresi/Gün",
"expired": "Süresi dolmuş",
"finished": "Trafik tükendi",
"import": "İçe Aktar", "import": "İçe Aktar",
"latestAnnouncement": "Son Duyuru", "latestAnnouncement": "Son Duyuru",
"manualImportMessage": "Bu uygulama şu anda uyandırmayı desteklemiyor, lütfen elle içe aktarın, abone adresi otomatik olarak kopyalandı", "manualImportMessage": "Bu uygulama şu anda uyandırmayı desteklemiyor, lütfen elle içe aktarın, abone adresi otomatik olarak kopyalandı",

View File

@ -5,8 +5,11 @@
"copy": "Копіювати", "copy": "Копіювати",
"copyFailure": "Не вдалося скопіювати, будь ласка, скопіюйте вручну", "copyFailure": "Не вдалося скопіювати, будь ласка, скопіюйте вручну",
"copySuccess": "Скопійовано успішно", "copySuccess": "Скопійовано успішно",
"deducted": "Скасовано",
"download": "завантажити", "download": "завантажити",
"expirationDays": "Термін дії/дні", "expirationDays": "Термін дії/дні",
"expired": "Термін закінчився",
"finished": "Трафік вичерпано",
"import": "Імпорт", "import": "Імпорт",
"latestAnnouncement": "Останнє оголошення", "latestAnnouncement": "Останнє оголошення",
"manualImportMessage": "Цей додаток тимчасово не підтримує виклик, будь ласка, імпортуйте вручну, адресу підписки вже скопійовано", "manualImportMessage": "Цей додаток тимчасово не підтримує виклик, будь ласка, імпортуйте вручну, адресу підписки вже скопійовано",

View File

@ -5,8 +5,11 @@
"copy": "Sao chép", "copy": "Sao chép",
"copyFailure": "Sao chép thất bại, vui lòng sao chép thủ công", "copyFailure": "Sao chép thất bại, vui lòng sao chép thủ công",
"copySuccess": "Sao chép thành công", "copySuccess": "Sao chép thành công",
"deducted": "Đã hủy",
"download": "tải xuống", "download": "tải xuống",
"expirationDays": "Thời gian hết hạn/ngày", "expirationDays": "Thời gian hết hạn/ngày",
"expired": "Hết hạn",
"finished": "Lưu lượng đã sử dụng",
"import": "Nhập khẩu", "import": "Nhập khẩu",
"latestAnnouncement": "Thông báo mới nhất", "latestAnnouncement": "Thông báo mới nhất",
"manualImportMessage": "Ứng dụng này hiện không hỗ trợ kích hoạt, vui lòng nhập thủ công, địa chỉ đăng ký đã được sao chép tự động", "manualImportMessage": "Ứng dụng này hiện không hỗ trợ kích hoạt, vui lòng nhập thủ công, địa chỉ đăng ký đã được sao chép tự động",

View File

@ -5,8 +5,11 @@
"copy": "复制", "copy": "复制",
"copyFailure": "复制失败,请手动复制", "copyFailure": "复制失败,请手动复制",
"copySuccess": "复制成功", "copySuccess": "复制成功",
"deducted": "已取消",
"download": "下载", "download": "下载",
"expirationDays": "到期时间/天", "expirationDays": "到期时间/天",
"expired": "已过期",
"finished": "流量已用尽",
"import": "导入", "import": "导入",
"latestAnnouncement": "最新公告", "latestAnnouncement": "最新公告",
"manualImportMessage": "该应用暂不支持唤起,请手动导入,已自动复制订阅地址", "manualImportMessage": "该应用暂不支持唤起,请手动导入,已自动复制订阅地址",

View File

@ -5,8 +5,11 @@
"copy": "複製", "copy": "複製",
"copyFailure": "複製失敗,請手動複製", "copyFailure": "複製失敗,請手動複製",
"copySuccess": "複製成功", "copySuccess": "複製成功",
"deducted": "已取消",
"download": "下載", "download": "下載",
"expirationDays": "到期時間/天", "expirationDays": "到期時間/天",
"expired": "已過期",
"finished": "流量已用盡",
"import": "匯入", "import": "匯入",
"latestAnnouncement": "最新公告", "latestAnnouncement": "最新公告",
"manualImportMessage": "該應用暫不支援喚起,請手動導入,已自動複製訂閱地址", "manualImportMessage": "該應用暫不支援喚起,請手動導入,已自動複製訂閱地址",

View File

@ -25,6 +25,24 @@ export async function getApplication(options?: { [key: string]: any }) {
}); });
} }
/** Check verification code POST /v1/common/check_verification_code */
export async function checkVerificationCode(
body: API.CheckVerificationCodeRequest,
options?: { [key: string]: any },
) {
return request<API.Response & { data?: API.CheckVerificationCodeRespone }>(
'/v1/common/check_verification_code',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: body,
...(options || {}),
},
);
}
/** Get verification code POST /v1/common/send_code */ /** Get verification code POST /v1/common/send_code */
export async function sendEmailCode(body: API.SendCodeRequest, options?: { [key: string]: any }) { export async function sendEmailCode(body: API.SendCodeRequest, options?: { [key: string]: any }) {
return request<API.Response & { data?: API.SendCodeResponse }>('/v1/common/send_code', { return request<API.Response & { data?: API.SendCodeResponse }>('/v1/common/send_code', {

View File

@ -121,6 +121,17 @@ declare namespace API {
telephone: string; telephone: string;
}; };
type CheckVerificationCodeRequest = {
method: 'email' | 'mobile';
account: string;
code: string;
type: number;
};
type CheckVerificationCodeRespone = {
status: boolean;
};
type CloseOrderRequest = { type CloseOrderRequest = {
orderNo: string; orderNo: string;
}; };
@ -910,6 +921,7 @@ declare namespace API {
subscribe: Subscribe; subscribe: Subscribe;
start_time: number; start_time: number;
expire_time: number; expire_time: number;
finished_at: number;
reset_time: number; reset_time: number;
traffic: number; traffic: number;
download: number; download: number;

View File

@ -1010,6 +1010,7 @@ declare namespace API {
subscribe: Subscribe; subscribe: Subscribe;
start_time: number; start_time: number;
expire_time: number; expire_time: number;
finished_at: number;
reset_time: number; reset_time: number;
traffic: number; traffic: number;
download: number; download: number;