🐛 fix: Remove GroupTable and related components, simplify SubscribeTable and update language handling in subscription forms

This commit is contained in:
web 2025-09-03 15:46:36 -07:00
parent 4563c570ac
commit 1ab9b39e8a
14 changed files with 135 additions and 552 deletions

View File

@ -1,143 +0,0 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { Button } from '@workspace/ui/components/button';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@workspace/ui/components/form';
import { ScrollArea } from '@workspace/ui/components/scroll-area';
import {
Sheet,
SheetContent,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from '@workspace/ui/components/sheet';
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
import { Icon } from '@workspace/ui/custom-components/icon';
import { useTranslations } from 'next-intl';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
const formSchema = z.object({
name: z.string(),
description: z.string().optional(),
});
interface GroupFormProps<T> {
onSubmit: (data: T) => Promise<boolean> | boolean;
initialValues?: T;
loading?: boolean;
trigger: string;
title: string;
}
export default function GroupForm<T extends Record<string, any>>({
onSubmit,
initialValues,
loading,
trigger,
title,
}: GroupFormProps<T>) {
const t = useTranslations('product');
const [open, setOpen] = useState(false);
const form = useForm({
resolver: zodResolver(formSchema),
defaultValues: {
...initialValues,
},
});
useEffect(() => {
form?.reset(initialValues);
}, [form, initialValues]);
async function handleSubmit(data: { [x: string]: any }) {
const bool = await onSubmit(data as T);
if (bool) setOpen(false);
}
return (
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
<Button
onClick={() => {
form.reset();
setOpen(true);
}}
>
{trigger}
</Button>
</SheetTrigger>
<SheetContent className='w-[500px] max-w-full md:max-w-screen-md'>
<SheetHeader>
<SheetTitle>{title}</SheetTitle>
</SheetHeader>
<ScrollArea className='-mx-6 h-[calc(100dvh-48px-36px-36px-env(safe-area-inset-top))]'>
<Form {...form}>
<form onSubmit={form.handleSubmit(handleSubmit)} className='space-y-4 px-6 pt-4'>
<FormField
control={form.control}
name='name'
render={({ field }) => (
<FormItem>
<FormLabel>{t('group.form.name')}</FormLabel>
<FormControl>
<EnhancedInput
{...field}
onValueChange={(value) => {
form.setValue(field.name, value);
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='description'
render={({ field }) => (
<FormItem>
<FormLabel>{t('group.form.description')}</FormLabel>
<FormControl>
<EnhancedInput
{...field}
onValueChange={(value) => {
form.setValue(field.name, value);
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</ScrollArea>
<SheetFooter className='flex-row justify-end gap-2 pt-3'>
<Button
variant='outline'
disabled={loading}
onClick={() => {
setOpen(false);
}}
>
{t('group.form.cancel')}
</Button>
<Button disabled={loading} onClick={form.handleSubmit(handleSubmit)}>
{loading && <Icon icon='mdi:loading' className='mr-2 animate-spin' />}{' '}
{t('group.form.confirm')}
</Button>
</SheetFooter>
</SheetContent>
</Sheet>
);
}

View File

@ -1,141 +0,0 @@
'use client';
import { ProTable, ProTableActions } from '@/components/pro-table';
import {
batchDeleteSubscribeGroup,
createSubscribeGroup,
deleteSubscribeGroup,
getSubscribeGroupList,
updateSubscribeGroup,
} from '@/services/admin/subscribe';
import { Button } from '@workspace/ui/components/button';
import { ConfirmButton } from '@workspace/ui/custom-components/confirm-button';
import { formatDate } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl';
import { useRef, useState } from 'react';
import { toast } from 'sonner';
import GroupForm from './form';
const GroupTable = () => {
const t = useTranslations('product');
const [loading, setLoading] = useState(false);
const ref = useRef<ProTableActions>(null);
return (
<ProTable<API.SubscribeGroup, any>
action={ref}
header={{
title: t('group.title'),
toolbar: (
<GroupForm<API.CreateSubscribeGroupRequest>
trigger={t('group.create')}
title={t('group.createSubscribeGroup')}
loading={loading}
onSubmit={async (values) => {
setLoading(true);
try {
await createSubscribeGroup(values);
toast.success(t('group.createSuccess'));
ref.current?.refresh();
setLoading(false);
return true;
} catch (error) {
setLoading(false);
return false;
}
}}
/>
),
}}
columns={[
{
accessorKey: 'name',
header: t('group.name'),
},
{
accessorKey: 'description',
header: t('group.description'),
},
{
accessorKey: 'updated_at',
header: t('group.updatedAt'),
cell: ({ row }) => formatDate(row.getValue('updated_at')),
},
]}
request={async () => {
const { data } = await getSubscribeGroupList();
return {
list: data.data?.list || [],
total: data.data?.total || 0,
};
}}
actions={{
render: (row) => [
<GroupForm<API.SubscribeGroup>
key='edit'
trigger={t('group.edit')}
title={t('group.editSubscribeGroup')}
loading={loading}
initialValues={row}
onSubmit={async (values) => {
setLoading(true);
try {
await updateSubscribeGroup({
...row,
...values,
});
toast.success(t('group.updateSuccess'));
ref.current?.refresh();
setLoading(false);
return true;
} catch (error) {
setLoading(false);
return false;
}
}}
/>,
<ConfirmButton
key='delete'
trigger={<Button variant='destructive'>{t('group.delete')}</Button>}
title={t('group.confirmDelete')}
description={t('group.deleteWarning')}
onConfirm={async () => {
await deleteSubscribeGroup({
id: row.id,
});
toast.success(t('group.deleteSuccess'));
ref.current?.refresh();
}}
cancelText={t('group.cancel')}
confirmText={t('group.confirm')}
/>,
],
batchRender(rows) {
return [
<ConfirmButton
key='delete'
trigger={<Button variant='destructive'>{t('group.delete')}</Button>}
title={t('group.confirmDelete')}
description={t('group.deleteWarning')}
onConfirm={async () => {
await batchDeleteSubscribeGroup({
ids: rows.map((item) => item.id),
});
toast.success(t('group.deleteSuccess'));
ref.current?.refresh();
}}
cancelText={t('group.cancel')}
confirmText={t('group.confirm')}
/>,
];
},
}}
/>
);
};
export default GroupTable;

View File

@ -1,25 +1,9 @@
import { getTranslations } from 'next-intl/server';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
import GroupTable from './group/table';
import SubscribeTable from './subscribe-table';
export default async function Page() {
const t = await getTranslations('product');
return (
<Tabs defaultValue='subscribe'>
<TabsList>
<TabsTrigger value='subscribe'>{t('tabs.subscribe')}</TabsTrigger>
<TabsTrigger value='group'>{t('tabs.subscribeGroup')}</TabsTrigger>
</TabsList>
<TabsContent value='subscribe'>
<SubscribeTable />
</TabsContent>
<TabsContent value='group'>
<GroupTable />
</TabsContent>
</Tabs>
);
return <SubscribeTable />;
}

View File

@ -1,7 +1,6 @@
'use client';
import { filterNodeList, queryNodeTag } from '@/services/admin/server';
import { getSubscribeGroupList } from '@/services/admin/subscribe';
import { zodResolver } from '@hookform/resolvers/zod';
import { useQuery } from '@tanstack/react-query';
import {
@ -62,6 +61,7 @@ const defaultValues = {
traffic: 0,
quota: 0,
discount: [],
language: '',
node_tags: [],
nodes: [],
unit_time: 'Month',
@ -102,8 +102,7 @@ export default function SubscribeForm<T extends Record<string, any>>({
device_limit: z.number().optional(),
traffic: z.number().optional(),
quota: z.number().optional(),
group_id: z.number().optional().nullish(),
// Use tags as group identifiers; accept string (tag) or number (legacy id)
language: z.string().optional(),
node_tags: z.array(z.string()).optional(),
nodes: z.array(z.number()).optional(),
deduction_ratio: z.number().optional(),
@ -230,14 +229,6 @@ export default function SubscribeForm<T extends Record<string, any>>({
if (bool) setOpen(false);
}
const { data: group } = useQuery({
queryKey: ['getSubscribeGroupList'],
queryFn: async () => {
const { data } = await getSubscribeGroupList();
return data.data?.list as API.SubscribeGroup[];
},
});
const { data: nodes } = useQuery({
queryKey: ['filterNodeListAll'],
queryFn: async () => {
@ -328,22 +319,20 @@ export default function SubscribeForm<T extends Record<string, any>>({
/>
<FormField
control={form.control}
name='group_id'
name='language'
render={({ field }) => (
<FormItem>
<FormLabel>{t('form.groupId')}</FormLabel>
<FormLabel>
{t('form.language')}
<span className='text-muted-foreground ml-1 text-[0.8rem]'>
{t('form.languageDescription')}
</span>
</FormLabel>
<FormControl>
<Combobox<number, false>
placeholder={t('form.selectSubscribeGroup')}
<EnhancedInput
{...field}
value={field.value ?? undefined}
onChange={(value) => {
form.setValue(field.name, value || 0);
}}
options={group?.map((item) => ({
label: item.name,
value: item.id,
}))}
placeholder={t('form.languagePlaceholder')}
onValueChange={(v) => form.setValue(field.name, v as string)}
/>
</FormControl>
<FormMessage />

View File

@ -6,12 +6,10 @@ import {
batchDeleteSubscribe,
createSubscribe,
deleteSubscribe,
getSubscribeGroupList,
getSubscribeList,
subscribeSort,
updateSubscribe,
} from '@/services/admin/subscribe';
import { useQuery } from '@tanstack/react-query';
import { Badge } from '@workspace/ui/components/badge';
import { Button } from '@workspace/ui/components/button';
import { Switch } from '@workspace/ui/components/switch';
@ -24,16 +22,6 @@ import SubscribeForm from './subscribe-form';
export default function SubscribeTable() {
const t = useTranslations('product');
const [loading, setLoading] = useState(false);
const { data: groups } = useQuery({
queryKey: ['getSubscribeGroupList', 'all'],
queryFn: async () => {
const { data } = await getSubscribeGroupList({
page: 1,
size: 9999,
});
return data.data?.list as API.SubscribeGroup[];
},
});
const ref = useRef<ProTableActions>(null);
return (
<ProTable<API.SubscribeItem, { group_id: number; query: string }>
@ -67,14 +55,6 @@ export default function SubscribeTable() {
),
}}
params={[
{
key: 'group_id',
placeholder: t('subscribeGroup'),
options: groups?.map((item) => ({
label: item.name,
value: String(item.id),
})),
},
{
key: 'search',
},
@ -176,11 +156,11 @@ export default function SubscribeTable() {
cell: ({ row }) => <Display type='number' value={row.getValue('quota')} unlimited />,
},
{
accessorKey: 'group_id',
header: t('subscribeGroup'),
accessorKey: 'language',
header: t('language'),
cell: ({ row }) => {
const name = groups?.find((group) => group.id === row.getValue('group_id'))?.name;
return name ? <Badge variant='outline'>{name}</Badge> : '--';
const language = row.getValue('language') as string;
return language ? <Badge variant='outline'>{language}</Badge> : '--';
},
},
{

View File

@ -35,8 +35,10 @@
"discountPercent": "Discount Percentage",
"discount_price": "Discount Price",
"duration": "Duration (months)",
"groupId": "Subscription Group",
"inventory": "Subscription Limit",
"language": "Language",
"languageDescription": "Leave empty for default without language restriction",
"languagePlaceholder": "Language identifier for the subscription, e.g., en-US, zh-CN",
"monthlyReset": "Monthly Reset",
"name": "Name",
"noLimit": "No Limit",
@ -51,7 +53,6 @@
"resetCycle": "Reset Cycle",
"resetOn1st": "Reset on the 1st",
"selectResetCycle": "Please select a reset cycle",
"selectSubscribeGroup": "Select Subscription Group",
"selectUnitTime": "Please select a unit of time",
"node": "Node",
"nodeGroup": "Node Group",
@ -61,32 +62,8 @@
"unitPrice": "Unit Price",
"unitTime": "Unit Time"
},
"group": {
"actions": "Actions",
"cancel": "Cancel",
"confirm": "Confirm",
"confirmDelete": "Are you sure you want to delete?",
"create": "Create",
"createSubscribeGroup": "Create Subscription Group",
"createSuccess": "Create Successful",
"delete": "Delete",
"deleteSuccess": "Delete Successful",
"deleteWarning": "Data cannot be recovered after deletion. Please proceed with caution.",
"description": "Description",
"edit": "Edit",
"editSubscribeGroup": "Edit Subscription Group",
"form": {
"cancel": "Cancel",
"confirm": "Confirm",
"description": "Description",
"name": "Name"
},
"name": "Name",
"title": "Subscription Group List",
"updateSuccess": "Update Successful",
"updatedAt": "Updated At"
},
"inventory": "Subscription Limit",
"language": "Language",
"name": "Name",
"quota": "Purchase Limit/Time",
"replacement": "Reset Price/Time",
@ -94,11 +71,6 @@
"show": "Display",
"sold": "Subscription Count",
"subscribe": "Subscribe",
"subscribeGroup": "Subscription Group",
"tabs": {
"subscribe": "Subscribe",
"subscribeGroup": "Subscription Group"
},
"traffic": "Traffic",
"unitPrice": "Unit Price",
"updateSuccess": "Update Successful"

View File

@ -35,8 +35,10 @@
"discountPercent": "折扣比",
"discount_price": "折扣价格",
"duration": "时长(月)",
"groupId": "订阅组",
"inventory": "订阅限制",
"language": "语言",
"languageDescription": "不填写则为没有语言限制的默认选项",
"languagePlaceholder": "订阅产品支持的语言标识符en-US, zh-CN",
"monthlyReset": "按月重置",
"name": "名称",
"noLimit": "无限制",
@ -51,7 +53,6 @@
"resetCycle": "重置周期",
"resetOn1st": "1号重置",
"selectResetCycle": "请选择重置周期",
"selectSubscribeGroup": "请选择订阅组",
"selectUnitTime": "请选择单位时间",
"node": "节点",
"nodeGroup": "节点组",
@ -61,32 +62,8 @@
"unitPrice": "单价",
"unitTime": "单位时间"
},
"group": {
"actions": "操作",
"cancel": "取消",
"confirm": "确认",
"confirmDelete": "确定删除吗?",
"create": "创建",
"createSubscribeGroup": "新建订阅组",
"createSuccess": "创建成功",
"delete": "删除",
"deleteSuccess": "删除成功",
"deleteWarning": "删除后数据无法恢复,请谨慎操作。",
"description": "描述",
"edit": "编辑",
"editSubscribeGroup": "编辑订阅组",
"form": {
"cancel": "取消",
"confirm": "确认",
"description": "描述",
"name": "名称"
},
"name": "名称",
"title": "订阅组列表",
"updateSuccess": "更新成功",
"updatedAt": "更新时间"
},
"inventory": "订阅限制",
"language": "语言",
"name": "名称",
"quota": "限购/次",
"replacement": "重置价格/次",
@ -94,13 +71,6 @@
"show": "显示",
"sold": "订阅数量",
"subscribe": "订阅",
"subscribeGroup": "订阅组",
"tabs": {
"subscribe": "订阅",
"subscribeApp": "应用配置",
"subscribeConfig": "订阅配置",
"subscribeGroup": "订阅组"
},
"traffic": "流量",
"unitPrice": "单价",
"updateSuccess": "更新成功"

View File

@ -191,14 +191,6 @@ export async function updateSubscribeConfig(
});
}
/** Get subscribe type GET /v1/admin/system/subscribe_type */
export async function getSubscribeType(options?: { [key: string]: any }) {
return request<API.Response & { data?: API.SubscribeType }>('/v1/admin/system/subscribe_type', {
method: 'GET',
...(options || {}),
});
}
/** Get Team of Service Config GET /v1/admin/system/tos_config */
export async function getTosConfig(options?: { [key: string]: any }) {
return request<API.Response & { data?: API.TosConfig }>('/v1/admin/system/tos_config', {

View File

@ -325,6 +325,7 @@ declare namespace API {
type CreateSubscribeRequest = {
name: string;
language: string;
description: string;
unit_price: number;
unit_time: string;
@ -335,7 +336,6 @@ declare namespace API {
speed_limit: number;
device_limit: number;
quota: number;
group_id: number;
nodes: number[];
node_tags: string[];
show: boolean;
@ -1045,14 +1045,14 @@ declare namespace API {
type GetSubscribeListParams = {
page: number;
size: number;
group_id?: number;
language?: string;
search?: string;
};
type GetSubscribeListRequest = {
page: number;
size: number;
group_id?: number;
language?: string;
search?: string;
};
@ -1829,6 +1829,7 @@ declare namespace API {
type Subscribe = {
id: number;
name: string;
language: string;
description: string;
unit_price: number;
unit_time: string;
@ -1839,7 +1840,6 @@ declare namespace API {
speed_limit: number;
device_limit: number;
quota: number;
group_id: number;
nodes: number[];
node_tags: string[];
show: boolean;
@ -1893,6 +1893,7 @@ declare namespace API {
type SubscribeItem = {
id?: number;
name?: string;
language?: string;
description?: string;
unit_price?: number;
unit_time?: string;
@ -1903,7 +1904,6 @@ declare namespace API {
speed_limit?: number;
device_limit?: number;
quota?: number;
group_id?: number;
nodes?: number[];
node_tags?: string[];
show?: boolean;
@ -2145,6 +2145,7 @@ declare namespace API {
type UpdateSubscribeRequest = {
id: number;
name: string;
language: string;
description: string;
unit_price: number;
unit_time: string;
@ -2155,7 +2156,6 @@ declare namespace API {
speed_limit: number;
device_limit: number;
quota: number;
group_id: number;
nodes: number[];
node_tags: string[];
show: boolean;

View File

@ -731,6 +731,7 @@ declare namespace API {
type Subscribe = {
id: number;
name: string;
language: string;
description: string;
unit_price: number;
unit_time: string;
@ -741,7 +742,6 @@ declare namespace API {
speed_limit: number;
device_limit: number;
quota: number;
group_id: number;
nodes: number[];
node_tags: string[];
show: boolean;

View File

@ -1,15 +1,14 @@
'use client';
import { Display } from '@/components/display';
import { querySubscribeGroupList, querySubscribeList } from '@/services/user/subscribe';
import { querySubscribeList } from '@/services/user/subscribe';
import { useQuery } from '@tanstack/react-query';
import { Button } from '@workspace/ui/components/button';
import { Card, CardContent, CardFooter, CardHeader } from '@workspace/ui/components/card';
import { Separator } from '@workspace/ui/components/separator';
import { Tabs, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
import { Icon } from '@workspace/ui/custom-components/icon';
import { cn } from '@workspace/ui/lib/utils';
import { useTranslations } from 'next-intl';
import { useLocale, useTranslations } from 'next-intl';
import { useState } from 'react';
import { Empty } from '@/components/empty';
@ -18,125 +17,102 @@ import Purchase from '@/components/subscribe/purchase';
export default function Page() {
const t = useTranslations('subscribe');
const locale = useLocale();
const [subscribe, setSubscribe] = useState<API.Subscribe>();
const [group, setGroup] = useState<string>('');
const { data: groups } = useQuery({
queryKey: ['querySubscribeGroupList'],
queryFn: async () => {
const { data } = await querySubscribeGroupList();
return data.data?.list || [];
},
});
const { data } = useQuery({
queryKey: ['querySubscribeList'],
queryKey: ['querySubscribeList', locale],
queryFn: async () => {
const { data } = await querySubscribeList();
console.log('Fetching subscription list...');
const { data } = await querySubscribeList({ language: locale });
return data.data?.list || [];
},
});
const filteredData = data?.filter((item) => item.show);
return (
<>
<Tabs value={group} onValueChange={setGroup} className='space-y-4'>
{groups && groups.length > 0 && (
<>
<h1 className='text-muted-foreground w-full'>{t('category')}</h1>
<TabsList>
<TabsTrigger value=''>{t('all')}</TabsTrigger>
{groups.map((group) => (
<TabsTrigger key={group.id} value={String(group.id)}>
{group.name}
</TabsTrigger>
))}
</TabsList>
<h2 className='text-muted-foreground w-full'>{t('products')}</h2>
</>
)}
<div className='space-y-4'>
<div className='grid gap-4 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3'>
{data
?.filter((item) => item.show)
?.filter((item) => (group ? item.group_id === Number(group) : true))
?.map((item) => (
<Card className='flex flex-col' key={item.id}>
<CardHeader className='bg-muted/50 text-xl font-medium'>{item.name}</CardHeader>
<CardContent className='flex flex-grow flex-col gap-3 p-6 *:!text-sm'>
{/* <div className='font-semibold'>{t('productDescription')}</div> */}
<ul className='flex flex-grow flex-col gap-3'>
{(() => {
let parsedDescription;
try {
parsedDescription = JSON.parse(item.description);
} catch {
parsedDescription = { description: '', features: [] };
}
{filteredData?.map((item) => (
<Card className='flex flex-col' key={item.id}>
<CardHeader className='bg-muted/50 text-xl font-medium'>{item.name}</CardHeader>
<CardContent className='flex flex-grow flex-col gap-3 p-6 *:!text-sm'>
{/* <div className='font-semibold'>{t('productDescription')}</div> */}
<ul className='flex flex-grow flex-col gap-3'>
{(() => {
let parsedDescription;
try {
parsedDescription = JSON.parse(item.description);
} catch {
parsedDescription = { description: '', features: [] };
}
const { description, features } = parsedDescription;
return (
<>
{description && <li className='text-muted-foreground'>{description}</li>}
{features?.map(
(
feature: {
icon: string;
label: string;
type: 'default' | 'success' | 'destructive';
},
index: number,
) => (
<li
className={cn('flex items-center gap-1', {
'text-muted-foreground line-through':
feature.type === 'destructive',
})}
key={index}
>
{feature.icon && (
<Icon
icon={feature.icon}
className={cn('text-primary size-5', {
'text-green-500': feature.type === 'success',
'text-destructive': feature.type === 'destructive',
})}
/>
)}
{feature.label}
</li>
),
)}
</>
);
})()}
</ul>
<SubscribeDetail
subscribe={{
...item,
name: undefined,
}}
/>
</CardContent>
<Separator />
<CardFooter className='relative mt-2 flex flex-col gap-2'>
<h2 className='pb-5 text-2xl font-semibold sm:text-3xl'>
<Display type='currency' value={item.unit_price} />
<span className='text-base font-medium'>/{t(item.unit_time || 'Month')}</span>
</h2>
<Button
className='absolute bottom-0 w-full rounded-b-xl rounded-t-none'
onClick={() => {
setSubscribe(item);
}}
>
{t('buy')}
</Button>
</CardFooter>
</Card>
))}
const { description, features } = parsedDescription;
return (
<>
{description && <li className='text-muted-foreground'>{description}</li>}
{features?.map(
(
feature: {
icon: string;
label: string;
type: 'default' | 'success' | 'destructive';
},
index: number,
) => (
<li
className={cn('flex items-center gap-1', {
'text-muted-foreground line-through':
feature.type === 'destructive',
})}
key={index}
>
{feature.icon && (
<Icon
icon={feature.icon}
className={cn('text-primary size-5', {
'text-green-500': feature.type === 'success',
'text-destructive': feature.type === 'destructive',
})}
/>
)}
{feature.label}
</li>
),
)}
</>
);
})()}
</ul>
<SubscribeDetail
subscribe={{
...item,
name: undefined,
}}
/>
</CardContent>
<Separator />
<CardFooter className='relative mt-2 flex flex-col gap-2'>
<h2 className='pb-5 text-2xl font-semibold sm:text-3xl'>
<Display type='currency' value={item.unit_price} />
<span className='text-base font-medium'>/{t(item.unit_time || 'Month')}</span>
</h2>
<Button
className='absolute bottom-0 w-full rounded-b-xl rounded-t-none'
onClick={() => {
setSubscribe(item);
}}
>
{t('buy')}
</Button>
</CardFooter>
</Card>
))}
</div>
{data?.length === 0 && <Empty />}
</Tabs>
{filteredData?.length === 0 && <Empty />}
</div>
<Purchase subscribe={subscribe} setSubscribe={setSubscribe} />
</>
);

View File

@ -731,6 +731,7 @@ declare namespace API {
type Subscribe = {
id: number;
name: string;
language: string;
description: string;
unit_price: number;
unit_time: string;
@ -741,7 +742,6 @@ declare namespace API {
speed_limit: number;
device_limit: number;
quota: number;
group_id: number;
nodes: number[];
node_tags: string[];
show: boolean;

View File

@ -2,23 +2,19 @@
/* eslint-disable */
import request from '@/utils/request';
/** Get subscribe group list GET /v1/public/subscribe/group/list */
export async function querySubscribeGroupList(options?: { [key: string]: any }) {
return request<API.Response & { data?: API.QuerySubscribeGroupListResponse }>(
'/v1/public/subscribe/group/list',
{
method: 'GET',
...(options || {}),
},
);
}
/** Get subscribe list GET /v1/public/subscribe/list */
export async function querySubscribeList(options?: { [key: string]: any }) {
export async function querySubscribeList(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.QuerySubscribeListParams,
options?: { [key: string]: any },
) {
return request<API.Response & { data?: API.QuerySubscribeListResponse }>(
'/v1/public/subscribe/list',
{
method: 'GET',
params: {
...params,
},
...(options || {}),
},
);

View File

@ -642,6 +642,14 @@ declare namespace API {
total: number;
};
type QuerySubscribeListParams = {
language: string;
};
type QuerySubscribeListRequest = {
language: string;
};
type QuerySubscribeListResponse = {
list: Subscribe[];
total: number;
@ -821,6 +829,7 @@ declare namespace API {
type Subscribe = {
id: number;
name: string;
language: string;
description: string;
unit_price: number;
unit_time: string;
@ -831,7 +840,6 @@ declare namespace API {
speed_limit: number;
device_limit: number;
quota: number;
group_id: number;
nodes: number[];
node_tags: string[];
show: boolean;