🐛 fix: Remove GroupTable and related components, simplify SubscribeTable and update language handling in subscription forms
This commit is contained in:
parent
4563c570ac
commit
1ab9b39e8a
@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -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;
|
|
||||||
@ -1,25 +1,9 @@
|
|||||||
import { getTranslations } from 'next-intl/server';
|
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';
|
import SubscribeTable from './subscribe-table';
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
const t = await getTranslations('product');
|
const t = await getTranslations('product');
|
||||||
|
|
||||||
return (
|
return <SubscribeTable />;
|
||||||
<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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { filterNodeList, queryNodeTag } from '@/services/admin/server';
|
import { filterNodeList, queryNodeTag } from '@/services/admin/server';
|
||||||
import { getSubscribeGroupList } from '@/services/admin/subscribe';
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import {
|
import {
|
||||||
@ -62,6 +61,7 @@ const defaultValues = {
|
|||||||
traffic: 0,
|
traffic: 0,
|
||||||
quota: 0,
|
quota: 0,
|
||||||
discount: [],
|
discount: [],
|
||||||
|
language: '',
|
||||||
node_tags: [],
|
node_tags: [],
|
||||||
nodes: [],
|
nodes: [],
|
||||||
unit_time: 'Month',
|
unit_time: 'Month',
|
||||||
@ -102,8 +102,7 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
|||||||
device_limit: z.number().optional(),
|
device_limit: z.number().optional(),
|
||||||
traffic: z.number().optional(),
|
traffic: z.number().optional(),
|
||||||
quota: z.number().optional(),
|
quota: z.number().optional(),
|
||||||
group_id: z.number().optional().nullish(),
|
language: z.string().optional(),
|
||||||
// Use tags as group identifiers; accept string (tag) or number (legacy id)
|
|
||||||
node_tags: z.array(z.string()).optional(),
|
node_tags: z.array(z.string()).optional(),
|
||||||
nodes: z.array(z.number()).optional(),
|
nodes: z.array(z.number()).optional(),
|
||||||
deduction_ratio: 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);
|
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({
|
const { data: nodes } = useQuery({
|
||||||
queryKey: ['filterNodeListAll'],
|
queryKey: ['filterNodeListAll'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
@ -328,22 +319,20 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
|||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name='group_id'
|
name='language'
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<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>
|
<FormControl>
|
||||||
<Combobox<number, false>
|
<EnhancedInput
|
||||||
placeholder={t('form.selectSubscribeGroup')}
|
|
||||||
{...field}
|
{...field}
|
||||||
value={field.value ?? undefined}
|
placeholder={t('form.languagePlaceholder')}
|
||||||
onChange={(value) => {
|
onValueChange={(v) => form.setValue(field.name, v as string)}
|
||||||
form.setValue(field.name, value || 0);
|
|
||||||
}}
|
|
||||||
options={group?.map((item) => ({
|
|
||||||
label: item.name,
|
|
||||||
value: item.id,
|
|
||||||
}))}
|
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
|
|||||||
@ -6,12 +6,10 @@ import {
|
|||||||
batchDeleteSubscribe,
|
batchDeleteSubscribe,
|
||||||
createSubscribe,
|
createSubscribe,
|
||||||
deleteSubscribe,
|
deleteSubscribe,
|
||||||
getSubscribeGroupList,
|
|
||||||
getSubscribeList,
|
getSubscribeList,
|
||||||
subscribeSort,
|
subscribeSort,
|
||||||
updateSubscribe,
|
updateSubscribe,
|
||||||
} from '@/services/admin/subscribe';
|
} from '@/services/admin/subscribe';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { Badge } from '@workspace/ui/components/badge';
|
import { Badge } from '@workspace/ui/components/badge';
|
||||||
import { Button } from '@workspace/ui/components/button';
|
import { Button } from '@workspace/ui/components/button';
|
||||||
import { Switch } from '@workspace/ui/components/switch';
|
import { Switch } from '@workspace/ui/components/switch';
|
||||||
@ -24,16 +22,6 @@ import SubscribeForm from './subscribe-form';
|
|||||||
export default function SubscribeTable() {
|
export default function SubscribeTable() {
|
||||||
const t = useTranslations('product');
|
const t = useTranslations('product');
|
||||||
const [loading, setLoading] = useState(false);
|
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);
|
const ref = useRef<ProTableActions>(null);
|
||||||
return (
|
return (
|
||||||
<ProTable<API.SubscribeItem, { group_id: number; query: string }>
|
<ProTable<API.SubscribeItem, { group_id: number; query: string }>
|
||||||
@ -67,14 +55,6 @@ export default function SubscribeTable() {
|
|||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
params={[
|
params={[
|
||||||
{
|
|
||||||
key: 'group_id',
|
|
||||||
placeholder: t('subscribeGroup'),
|
|
||||||
options: groups?.map((item) => ({
|
|
||||||
label: item.name,
|
|
||||||
value: String(item.id),
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'search',
|
key: 'search',
|
||||||
},
|
},
|
||||||
@ -176,11 +156,11 @@ export default function SubscribeTable() {
|
|||||||
cell: ({ row }) => <Display type='number' value={row.getValue('quota')} unlimited />,
|
cell: ({ row }) => <Display type='number' value={row.getValue('quota')} unlimited />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'group_id',
|
accessorKey: 'language',
|
||||||
header: t('subscribeGroup'),
|
header: t('language'),
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const name = groups?.find((group) => group.id === row.getValue('group_id'))?.name;
|
const language = row.getValue('language') as string;
|
||||||
return name ? <Badge variant='outline'>{name}</Badge> : '--';
|
return language ? <Badge variant='outline'>{language}</Badge> : '--';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -35,8 +35,10 @@
|
|||||||
"discountPercent": "Discount Percentage",
|
"discountPercent": "Discount Percentage",
|
||||||
"discount_price": "Discount Price",
|
"discount_price": "Discount Price",
|
||||||
"duration": "Duration (months)",
|
"duration": "Duration (months)",
|
||||||
"groupId": "Subscription Group",
|
|
||||||
"inventory": "Subscription Limit",
|
"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",
|
"monthlyReset": "Monthly Reset",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"noLimit": "No Limit",
|
"noLimit": "No Limit",
|
||||||
@ -51,7 +53,6 @@
|
|||||||
"resetCycle": "Reset Cycle",
|
"resetCycle": "Reset Cycle",
|
||||||
"resetOn1st": "Reset on the 1st",
|
"resetOn1st": "Reset on the 1st",
|
||||||
"selectResetCycle": "Please select a reset cycle",
|
"selectResetCycle": "Please select a reset cycle",
|
||||||
"selectSubscribeGroup": "Select Subscription Group",
|
|
||||||
"selectUnitTime": "Please select a unit of time",
|
"selectUnitTime": "Please select a unit of time",
|
||||||
"node": "Node",
|
"node": "Node",
|
||||||
"nodeGroup": "Node Group",
|
"nodeGroup": "Node Group",
|
||||||
@ -61,32 +62,8 @@
|
|||||||
"unitPrice": "Unit Price",
|
"unitPrice": "Unit Price",
|
||||||
"unitTime": "Unit Time"
|
"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",
|
"inventory": "Subscription Limit",
|
||||||
|
"language": "Language",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"quota": "Purchase Limit/Time",
|
"quota": "Purchase Limit/Time",
|
||||||
"replacement": "Reset Price/Time",
|
"replacement": "Reset Price/Time",
|
||||||
@ -94,11 +71,6 @@
|
|||||||
"show": "Display",
|
"show": "Display",
|
||||||
"sold": "Subscription Count",
|
"sold": "Subscription Count",
|
||||||
"subscribe": "Subscribe",
|
"subscribe": "Subscribe",
|
||||||
"subscribeGroup": "Subscription Group",
|
|
||||||
"tabs": {
|
|
||||||
"subscribe": "Subscribe",
|
|
||||||
"subscribeGroup": "Subscription Group"
|
|
||||||
},
|
|
||||||
"traffic": "Traffic",
|
"traffic": "Traffic",
|
||||||
"unitPrice": "Unit Price",
|
"unitPrice": "Unit Price",
|
||||||
"updateSuccess": "Update Successful"
|
"updateSuccess": "Update Successful"
|
||||||
|
|||||||
@ -35,8 +35,10 @@
|
|||||||
"discountPercent": "折扣比",
|
"discountPercent": "折扣比",
|
||||||
"discount_price": "折扣价格",
|
"discount_price": "折扣价格",
|
||||||
"duration": "时长(月)",
|
"duration": "时长(月)",
|
||||||
"groupId": "订阅组",
|
|
||||||
"inventory": "订阅限制",
|
"inventory": "订阅限制",
|
||||||
|
"language": "语言",
|
||||||
|
"languageDescription": "不填写则为没有语言限制的默认选项",
|
||||||
|
"languagePlaceholder": "订阅产品支持的语言标识符,如:en-US, zh-CN",
|
||||||
"monthlyReset": "按月重置",
|
"monthlyReset": "按月重置",
|
||||||
"name": "名称",
|
"name": "名称",
|
||||||
"noLimit": "无限制",
|
"noLimit": "无限制",
|
||||||
@ -51,7 +53,6 @@
|
|||||||
"resetCycle": "重置周期",
|
"resetCycle": "重置周期",
|
||||||
"resetOn1st": "1号重置",
|
"resetOn1st": "1号重置",
|
||||||
"selectResetCycle": "请选择重置周期",
|
"selectResetCycle": "请选择重置周期",
|
||||||
"selectSubscribeGroup": "请选择订阅组",
|
|
||||||
"selectUnitTime": "请选择单位时间",
|
"selectUnitTime": "请选择单位时间",
|
||||||
"node": "节点",
|
"node": "节点",
|
||||||
"nodeGroup": "节点组",
|
"nodeGroup": "节点组",
|
||||||
@ -61,32 +62,8 @@
|
|||||||
"unitPrice": "单价",
|
"unitPrice": "单价",
|
||||||
"unitTime": "单位时间"
|
"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": "订阅限制",
|
"inventory": "订阅限制",
|
||||||
|
"language": "语言",
|
||||||
"name": "名称",
|
"name": "名称",
|
||||||
"quota": "限购/次",
|
"quota": "限购/次",
|
||||||
"replacement": "重置价格/次",
|
"replacement": "重置价格/次",
|
||||||
@ -94,13 +71,6 @@
|
|||||||
"show": "显示",
|
"show": "显示",
|
||||||
"sold": "订阅数量",
|
"sold": "订阅数量",
|
||||||
"subscribe": "订阅",
|
"subscribe": "订阅",
|
||||||
"subscribeGroup": "订阅组",
|
|
||||||
"tabs": {
|
|
||||||
"subscribe": "订阅",
|
|
||||||
"subscribeApp": "应用配置",
|
|
||||||
"subscribeConfig": "订阅配置",
|
|
||||||
"subscribeGroup": "订阅组"
|
|
||||||
},
|
|
||||||
"traffic": "流量",
|
"traffic": "流量",
|
||||||
"unitPrice": "单价",
|
"unitPrice": "单价",
|
||||||
"updateSuccess": "更新成功"
|
"updateSuccess": "更新成功"
|
||||||
|
|||||||
@ -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 */
|
/** Get Team of Service Config GET /v1/admin/system/tos_config */
|
||||||
export async function getTosConfig(options?: { [key: string]: any }) {
|
export async function getTosConfig(options?: { [key: string]: any }) {
|
||||||
return request<API.Response & { data?: API.TosConfig }>('/v1/admin/system/tos_config', {
|
return request<API.Response & { data?: API.TosConfig }>('/v1/admin/system/tos_config', {
|
||||||
|
|||||||
12
apps/admin/services/admin/typings.d.ts
vendored
12
apps/admin/services/admin/typings.d.ts
vendored
@ -325,6 +325,7 @@ declare namespace API {
|
|||||||
|
|
||||||
type CreateSubscribeRequest = {
|
type CreateSubscribeRequest = {
|
||||||
name: string;
|
name: string;
|
||||||
|
language: string;
|
||||||
description: string;
|
description: string;
|
||||||
unit_price: number;
|
unit_price: number;
|
||||||
unit_time: string;
|
unit_time: string;
|
||||||
@ -335,7 +336,6 @@ declare namespace API {
|
|||||||
speed_limit: number;
|
speed_limit: number;
|
||||||
device_limit: number;
|
device_limit: number;
|
||||||
quota: number;
|
quota: number;
|
||||||
group_id: number;
|
|
||||||
nodes: number[];
|
nodes: number[];
|
||||||
node_tags: string[];
|
node_tags: string[];
|
||||||
show: boolean;
|
show: boolean;
|
||||||
@ -1045,14 +1045,14 @@ declare namespace API {
|
|||||||
type GetSubscribeListParams = {
|
type GetSubscribeListParams = {
|
||||||
page: number;
|
page: number;
|
||||||
size: number;
|
size: number;
|
||||||
group_id?: number;
|
language?: string;
|
||||||
search?: string;
|
search?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type GetSubscribeListRequest = {
|
type GetSubscribeListRequest = {
|
||||||
page: number;
|
page: number;
|
||||||
size: number;
|
size: number;
|
||||||
group_id?: number;
|
language?: string;
|
||||||
search?: string;
|
search?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1829,6 +1829,7 @@ declare namespace API {
|
|||||||
type Subscribe = {
|
type Subscribe = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
language: string;
|
||||||
description: string;
|
description: string;
|
||||||
unit_price: number;
|
unit_price: number;
|
||||||
unit_time: string;
|
unit_time: string;
|
||||||
@ -1839,7 +1840,6 @@ declare namespace API {
|
|||||||
speed_limit: number;
|
speed_limit: number;
|
||||||
device_limit: number;
|
device_limit: number;
|
||||||
quota: number;
|
quota: number;
|
||||||
group_id: number;
|
|
||||||
nodes: number[];
|
nodes: number[];
|
||||||
node_tags: string[];
|
node_tags: string[];
|
||||||
show: boolean;
|
show: boolean;
|
||||||
@ -1893,6 +1893,7 @@ declare namespace API {
|
|||||||
type SubscribeItem = {
|
type SubscribeItem = {
|
||||||
id?: number;
|
id?: number;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
language?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
unit_price?: number;
|
unit_price?: number;
|
||||||
unit_time?: string;
|
unit_time?: string;
|
||||||
@ -1903,7 +1904,6 @@ declare namespace API {
|
|||||||
speed_limit?: number;
|
speed_limit?: number;
|
||||||
device_limit?: number;
|
device_limit?: number;
|
||||||
quota?: number;
|
quota?: number;
|
||||||
group_id?: number;
|
|
||||||
nodes?: number[];
|
nodes?: number[];
|
||||||
node_tags?: string[];
|
node_tags?: string[];
|
||||||
show?: boolean;
|
show?: boolean;
|
||||||
@ -2145,6 +2145,7 @@ declare namespace API {
|
|||||||
type UpdateSubscribeRequest = {
|
type UpdateSubscribeRequest = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
language: string;
|
||||||
description: string;
|
description: string;
|
||||||
unit_price: number;
|
unit_price: number;
|
||||||
unit_time: string;
|
unit_time: string;
|
||||||
@ -2155,7 +2156,6 @@ declare namespace API {
|
|||||||
speed_limit: number;
|
speed_limit: number;
|
||||||
device_limit: number;
|
device_limit: number;
|
||||||
quota: number;
|
quota: number;
|
||||||
group_id: number;
|
|
||||||
nodes: number[];
|
nodes: number[];
|
||||||
node_tags: string[];
|
node_tags: string[];
|
||||||
show: boolean;
|
show: boolean;
|
||||||
|
|||||||
2
apps/admin/services/common/typings.d.ts
vendored
2
apps/admin/services/common/typings.d.ts
vendored
@ -731,6 +731,7 @@ declare namespace API {
|
|||||||
type Subscribe = {
|
type Subscribe = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
language: string;
|
||||||
description: string;
|
description: string;
|
||||||
unit_price: number;
|
unit_price: number;
|
||||||
unit_time: string;
|
unit_time: string;
|
||||||
@ -741,7 +742,6 @@ declare namespace API {
|
|||||||
speed_limit: number;
|
speed_limit: number;
|
||||||
device_limit: number;
|
device_limit: number;
|
||||||
quota: number;
|
quota: number;
|
||||||
group_id: number;
|
|
||||||
nodes: number[];
|
nodes: number[];
|
||||||
node_tags: string[];
|
node_tags: string[];
|
||||||
show: boolean;
|
show: boolean;
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Display } from '@/components/display';
|
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 { useQuery } from '@tanstack/react-query';
|
||||||
import { Button } from '@workspace/ui/components/button';
|
import { Button } from '@workspace/ui/components/button';
|
||||||
import { Card, CardContent, CardFooter, CardHeader } from '@workspace/ui/components/card';
|
import { Card, CardContent, CardFooter, CardHeader } from '@workspace/ui/components/card';
|
||||||
import { Separator } from '@workspace/ui/components/separator';
|
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 { Icon } from '@workspace/ui/custom-components/icon';
|
||||||
import { cn } from '@workspace/ui/lib/utils';
|
import { cn } from '@workspace/ui/lib/utils';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useLocale, useTranslations } from 'next-intl';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { Empty } from '@/components/empty';
|
import { Empty } from '@/components/empty';
|
||||||
@ -18,125 +17,102 @@ import Purchase from '@/components/subscribe/purchase';
|
|||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const t = useTranslations('subscribe');
|
const t = useTranslations('subscribe');
|
||||||
|
const locale = useLocale();
|
||||||
const [subscribe, setSubscribe] = useState<API.Subscribe>();
|
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({
|
const { data } = useQuery({
|
||||||
queryKey: ['querySubscribeList'],
|
queryKey: ['querySubscribeList', locale],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await querySubscribeList();
|
console.log('Fetching subscription list...');
|
||||||
|
const { data } = await querySubscribeList({ language: locale });
|
||||||
return data.data?.list || [];
|
return data.data?.list || [];
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const filteredData = data?.filter((item) => item.show);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tabs value={group} onValueChange={setGroup} className='space-y-4'>
|
<div 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='grid gap-4 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3'>
|
<div className='grid gap-4 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3'>
|
||||||
{data
|
{filteredData?.map((item) => (
|
||||||
?.filter((item) => item.show)
|
<Card className='flex flex-col' key={item.id}>
|
||||||
?.filter((item) => (group ? item.group_id === Number(group) : true))
|
<CardHeader className='bg-muted/50 text-xl font-medium'>{item.name}</CardHeader>
|
||||||
?.map((item) => (
|
<CardContent className='flex flex-grow flex-col gap-3 p-6 *:!text-sm'>
|
||||||
<Card className='flex flex-col' key={item.id}>
|
{/* <div className='font-semibold'>{t('productDescription')}</div> */}
|
||||||
<CardHeader className='bg-muted/50 text-xl font-medium'>{item.name}</CardHeader>
|
<ul className='flex flex-grow flex-col gap-3'>
|
||||||
<CardContent className='flex flex-grow flex-col gap-3 p-6 *:!text-sm'>
|
{(() => {
|
||||||
{/* <div className='font-semibold'>{t('productDescription')}</div> */}
|
let parsedDescription;
|
||||||
<ul className='flex flex-grow flex-col gap-3'>
|
try {
|
||||||
{(() => {
|
parsedDescription = JSON.parse(item.description);
|
||||||
let parsedDescription;
|
} catch {
|
||||||
try {
|
parsedDescription = { description: '', features: [] };
|
||||||
parsedDescription = JSON.parse(item.description);
|
}
|
||||||
} catch {
|
|
||||||
parsedDescription = { description: '', features: [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
const { description, features } = parsedDescription;
|
const { description, features } = parsedDescription;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{description && <li className='text-muted-foreground'>{description}</li>}
|
{description && <li className='text-muted-foreground'>{description}</li>}
|
||||||
{features?.map(
|
{features?.map(
|
||||||
(
|
(
|
||||||
feature: {
|
feature: {
|
||||||
icon: string;
|
icon: string;
|
||||||
label: string;
|
label: string;
|
||||||
type: 'default' | 'success' | 'destructive';
|
type: 'default' | 'success' | 'destructive';
|
||||||
},
|
},
|
||||||
index: number,
|
index: number,
|
||||||
) => (
|
) => (
|
||||||
<li
|
<li
|
||||||
className={cn('flex items-center gap-1', {
|
className={cn('flex items-center gap-1', {
|
||||||
'text-muted-foreground line-through':
|
'text-muted-foreground line-through':
|
||||||
feature.type === 'destructive',
|
feature.type === 'destructive',
|
||||||
})}
|
})}
|
||||||
key={index}
|
key={index}
|
||||||
>
|
>
|
||||||
{feature.icon && (
|
{feature.icon && (
|
||||||
<Icon
|
<Icon
|
||||||
icon={feature.icon}
|
icon={feature.icon}
|
||||||
className={cn('text-primary size-5', {
|
className={cn('text-primary size-5', {
|
||||||
'text-green-500': feature.type === 'success',
|
'text-green-500': feature.type === 'success',
|
||||||
'text-destructive': feature.type === 'destructive',
|
'text-destructive': feature.type === 'destructive',
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{feature.label}
|
{feature.label}
|
||||||
</li>
|
</li>
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
</ul>
|
</ul>
|
||||||
<SubscribeDetail
|
<SubscribeDetail
|
||||||
subscribe={{
|
subscribe={{
|
||||||
...item,
|
...item,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<Separator />
|
<Separator />
|
||||||
<CardFooter className='relative mt-2 flex flex-col gap-2'>
|
<CardFooter className='relative mt-2 flex flex-col gap-2'>
|
||||||
<h2 className='pb-5 text-2xl font-semibold sm:text-3xl'>
|
<h2 className='pb-5 text-2xl font-semibold sm:text-3xl'>
|
||||||
<Display type='currency' value={item.unit_price} />
|
<Display type='currency' value={item.unit_price} />
|
||||||
<span className='text-base font-medium'>/{t(item.unit_time || 'Month')}</span>
|
<span className='text-base font-medium'>/{t(item.unit_time || 'Month')}</span>
|
||||||
</h2>
|
</h2>
|
||||||
<Button
|
<Button
|
||||||
className='absolute bottom-0 w-full rounded-b-xl rounded-t-none'
|
className='absolute bottom-0 w-full rounded-b-xl rounded-t-none'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSubscribe(item);
|
setSubscribe(item);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('buy')}
|
{t('buy')}
|
||||||
</Button>
|
</Button>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{data?.length === 0 && <Empty />}
|
{filteredData?.length === 0 && <Empty />}
|
||||||
</Tabs>
|
</div>
|
||||||
<Purchase subscribe={subscribe} setSubscribe={setSubscribe} />
|
<Purchase subscribe={subscribe} setSubscribe={setSubscribe} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
2
apps/user/services/common/typings.d.ts
vendored
2
apps/user/services/common/typings.d.ts
vendored
@ -731,6 +731,7 @@ declare namespace API {
|
|||||||
type Subscribe = {
|
type Subscribe = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
language: string;
|
||||||
description: string;
|
description: string;
|
||||||
unit_price: number;
|
unit_price: number;
|
||||||
unit_time: string;
|
unit_time: string;
|
||||||
@ -741,7 +742,6 @@ declare namespace API {
|
|||||||
speed_limit: number;
|
speed_limit: number;
|
||||||
device_limit: number;
|
device_limit: number;
|
||||||
quota: number;
|
quota: number;
|
||||||
group_id: number;
|
|
||||||
nodes: number[];
|
nodes: number[];
|
||||||
node_tags: string[];
|
node_tags: string[];
|
||||||
show: boolean;
|
show: boolean;
|
||||||
|
|||||||
@ -2,23 +2,19 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
import request from '@/utils/request';
|
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 */
|
/** 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 }>(
|
return request<API.Response & { data?: API.QuerySubscribeListResponse }>(
|
||||||
'/v1/public/subscribe/list',
|
'/v1/public/subscribe/list',
|
||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
params: {
|
||||||
|
...params,
|
||||||
|
},
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
10
apps/user/services/user/typings.d.ts
vendored
10
apps/user/services/user/typings.d.ts
vendored
@ -642,6 +642,14 @@ declare namespace API {
|
|||||||
total: number;
|
total: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type QuerySubscribeListParams = {
|
||||||
|
language: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type QuerySubscribeListRequest = {
|
||||||
|
language: string;
|
||||||
|
};
|
||||||
|
|
||||||
type QuerySubscribeListResponse = {
|
type QuerySubscribeListResponse = {
|
||||||
list: Subscribe[];
|
list: Subscribe[];
|
||||||
total: number;
|
total: number;
|
||||||
@ -821,6 +829,7 @@ declare namespace API {
|
|||||||
type Subscribe = {
|
type Subscribe = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
language: string;
|
||||||
description: string;
|
description: string;
|
||||||
unit_price: number;
|
unit_price: number;
|
||||||
unit_time: string;
|
unit_time: string;
|
||||||
@ -831,7 +840,6 @@ declare namespace API {
|
|||||||
speed_limit: number;
|
speed_limit: number;
|
||||||
device_limit: number;
|
device_limit: number;
|
||||||
quota: number;
|
quota: number;
|
||||||
group_id: number;
|
|
||||||
nodes: number[];
|
nodes: number[];
|
||||||
node_tags: string[];
|
node_tags: string[];
|
||||||
show: boolean;
|
show: boolean;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user