'use client'; import { ProTable, ProTableActions } from '@/components/pro-table'; import { createSubscribeApplication, deleteSubscribeApplication, getSubscribeApplicationList, updateSubscribeApplication, } from '@/services/admin/application'; import { zodResolver } from '@hookform/resolvers/zod'; import { ColumnDef } from '@tanstack/react-table'; import { Badge } from '@workspace/ui/components/badge'; import { Button } from '@workspace/ui/components/button'; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from '@workspace/ui/components/form'; import { Input } from '@workspace/ui/components/input'; import { ScrollArea } from '@workspace/ui/components/scroll-area'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@workspace/ui/components/select'; import { Sheet, SheetContent, SheetFooter, SheetHeader, SheetTitle, } from '@workspace/ui/components/sheet'; import { Switch } from '@workspace/ui/components/switch'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@workspace/ui/components/tabs'; import { Textarea } from '@workspace/ui/components/textarea'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '@workspace/ui/components/tooltip'; import { ConfirmButton } from '@workspace/ui/custom-components/confirm-button'; import { GoTemplateEditor } from '@workspace/ui/custom-components/editor'; import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input'; import { Icon } from '@workspace/ui/custom-components/icon'; import { UploadImage } from '@workspace/ui/custom-components/upload-image'; import { useTranslations } from 'next-intl'; import Image from 'next/image'; import { useRef, useState } from 'react'; import { useForm } from 'react-hook-form'; import { toast } from 'sonner'; import { z } from 'zod'; // 表单验证规则 - 基于 API.CreateSubscribeApplicationRequest const createClientFormSchema = (t: any) => z.object({ name: z.string().min(1, t('form.validation.nameRequired')), description: z.string().optional(), icon: z.string().optional(), user_agent: z.string().min(1, `User-Agent ${t('form.validation.userAgentRequiredSuffix')}`), schema: z.string().default(''), template: z.string().default(''), output_format: z.string().default(''), download_link: z.object({ windows: z.string().optional(), mac: z.string().optional(), linux: z.string().optional(), ios: z.string().optional(), android: z.string().optional(), harmony: z.string().optional(), }), }); type ClientFormData = z.infer>; export function ProtocolForm() { const t = useTranslations('subscribe'); const [loading, setLoading] = useState(false); const [open, setOpen] = useState(false); const [editingClient, setEditingClient] = useState(null); const tableRef = useRef(null); const clientFormSchema = createClientFormSchema(t); const form = useForm({ resolver: zodResolver(clientFormSchema), defaultValues: { name: '', description: '', icon: '', user_agent: '', schema: '', template: '', output_format: '', download_link: { windows: '', mac: '', linux: '', ios: '', android: '', harmony: '', }, }, }); // API请求函数 const request = async ( pagination: { page: number; size: number }, filter: Record, ) => { const { data } = await getSubscribeApplicationList({ page: pagination.page, size: pagination.size, }); return { list: data.data?.list || [], total: data.data?.total || 0, }; }; // 表格列定义 const columns: ColumnDef[] = [ { accessorKey: 'is_default', header: t('table.columns.default'), cell: ({ row }) => ( { await updateSubscribeApplication({ ...row.original, is_default: checked, }); tableRef.current?.refresh(); }} /> ), }, { accessorKey: 'name', header: t('table.columns.name'), cell: ({ row }) => (
{row.original.icon && (
{row.original.name} { console.log(`Failed to load image for ${row.original.name}`); }} />
)} {row.original.name}
), }, { accessorKey: 'description', header: t('table.columns.description'), cell: ({ row }) => (
{row.original.description}

{row.original.description}

), }, { accessorKey: 'output_format', header: t('table.columns.outputFormat'), cell: ({ row }) => ( {t(`outputFormats.${row.original.output_format}`) || row.original.output_format} ), }, { accessorKey: 'download_link', header: t('table.columns.supportedPlatforms'), cell: ({ row }) => { return (
{Object.entries(row.original.download_link || {}).map(([key, value]) => { if (value) { return ( {t(`platforms.${key}`)} ); } return null; })}
); }, }, { accessorKey: 'user_agent', header: 'User-Agent', cell: ({ row }) => (
{row.original.user_agent}
), }, ]; const handleAdd = () => { setEditingClient(null); form.reset({ name: '', description: '', icon: '', user_agent: '', schema: '', template: '', output_format: '', download_link: { windows: '', mac: '', linux: '', ios: '', android: '', harmony: '', }, }); setOpen(true); }; const handleEdit = (client: API.SubscribeApplication) => { setEditingClient(client); form.reset({ name: client.name, description: client.description || '', icon: client.icon || '', user_agent: client.user_agent, schema: client.proxy_template || '', template: client.template || '', output_format: client.output_format || '', download_link: { windows: '', mac: '', linux: '', ios: '', android: '', harmony: '', }, }); setOpen(true); }; const handleDelete = async (client: API.SubscribeApplication) => { setLoading(true); try { await deleteSubscribeApplication({ id: client.id }); tableRef.current?.refresh(); toast.success(t('actions.deleteSuccess')); } catch (error) { console.error('Failed to delete client:', error); toast.error(t('actions.deleteFailed')); } finally { setLoading(false); } }; const handleBatchDelete = async (clients: API.SubscribeApplication[]) => { setLoading(true); try { await Promise.all(clients.map((client) => deleteSubscribeApplication({ id: client.id }))); tableRef.current?.refresh(); toast.success(t('actions.batchDeleteSuccess', { count: clients.length })); } catch (error) { console.error('Failed to batch delete clients:', error); toast.error(t('actions.deleteFailed')); } finally { setLoading(false); } }; const onSubmit = async (data: ClientFormData) => { setLoading(true); try { if (editingClient) { await updateSubscribeApplication({ ...data, proxy_template: data.schema || '', is_default: editingClient.is_default, id: editingClient.id, }); toast.success(t('actions.updateSuccess')); } else { await createSubscribeApplication({ ...data, proxy_template: data.schema || '', is_default: false, }); toast.success(t('actions.createSuccess')); } setOpen(false); tableRef.current?.refresh(); } catch (error) { console.error('Failed to save client:', error); toast.error(t('actions.saveFailed')); } finally { setLoading(false); } }; return ( <> > action={tableRef} columns={columns} request={request} header={{ title:

{t('protocol.title')}

, toolbar: , }} actions={{ render: (row) => [ , {t('actions.delete')} } title={t('actions.confirmDelete')} description={t('actions.deleteWarning')} onConfirm={() => handleDelete(row as unknown as API.SubscribeApplication)} cancelText={t('actions.cancel')} confirmText={t('actions.confirm')} />, ], batchRender: (rows) => [ {t('actions.batchDelete')}} title={t('actions.confirmDelete')} description={t('actions.batchDeleteWarning', { count: rows.length })} onConfirm={() => handleBatchDelete(rows as unknown as API.SubscribeApplication[])} cancelText={t('actions.cancel')} confirmText={t('actions.confirm')} />, ], }} /> {editingClient ? t('form.editTitle') : t('form.addTitle')}
{t('form.tabs.basic')} {t('form.tabs.template')} {t('form.tabs.download')} ( {t('form.fields.icon')} { form.setValue(field.name, value as string); }} /> } value={field.value} onValueChange={(value) => { form.setValue(field.name, value as string); }} /> {t('form.descriptions.icon')} )} /> ( {t('form.fields.name')} {t('form.descriptions.name')} )} /> ( User-Agent {t('form.descriptions.userAgentPrefix')} )} /> ( {t('form.fields.description')}