feat: 佣金提现和备注功能
This commit is contained in:
@@ -120,6 +120,7 @@ export default function Page() {
|
||||
const { data } = await getTicketList({
|
||||
...pagination,
|
||||
...filters,
|
||||
issue_type: 0,
|
||||
});
|
||||
return {
|
||||
list: data.data?.list || [],
|
||||
|
||||
@@ -5,8 +5,16 @@ import { ProTable, ProTableActions } from '@/components/pro-table';
|
||||
import { getSubscribeList } from '@/services/admin/subscribe';
|
||||
import { createUser, deleteUser, getUserList, updateUserBasicInfo } from '@/services/admin/user';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Badge } from '@workspace/ui/components/badge';
|
||||
import { Button } from '@workspace/ui/components/button';
|
||||
import { Input } from '@workspace/ui/components/input';
|
||||
import { FilePenLine } from 'lucide-react';
|
||||
|
||||
import {
|
||||
Popover,
|
||||
PopoverClose,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@workspace/ui/components/popover';
|
||||
import { Switch } from '@workspace/ui/components/switch';
|
||||
import { ConfirmButton } from '@workspace/ui/custom-components/confirm-button';
|
||||
import { formatDate } from '@workspace/ui/utils';
|
||||
@@ -17,6 +25,37 @@ import { toast } from 'sonner';
|
||||
import { UserDetail } from './user-detail';
|
||||
import UserForm from './user-form';
|
||||
|
||||
// 新的子组件,现在管理它自己的备注状态
|
||||
const RemarkForm = ({ onSave, initialRemark, CloseComponent }) => {
|
||||
const [remark, setRemark] = useState(initialRemark);
|
||||
|
||||
const handleInputChange = (event) => {
|
||||
setRemark(event.target.value);
|
||||
};
|
||||
|
||||
const handleSaveClick = () => {
|
||||
onSave(remark);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='mb-2 text-sm font-semibold'>备注</div>
|
||||
<Input
|
||||
type='text'
|
||||
value={remark}
|
||||
onChange={handleInputChange}
|
||||
placeholder='在此输入备注...'
|
||||
className='w-full'
|
||||
/>
|
||||
<CloseComponent asChild>
|
||||
<Button onClick={handleSaveClick} variant='default' size={'sm'} className={'mt-2'}>
|
||||
保存
|
||||
</Button>
|
||||
</CloseComponent>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
const t = useTranslations('user');
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -32,7 +71,6 @@ export default function Page() {
|
||||
return data.data?.list as API.SubscribeGroup[];
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<ProTable<API.User, API.GetUserListParams>
|
||||
action={ref}
|
||||
@@ -106,10 +144,42 @@ export default function Page() {
|
||||
const method = row.original.auth_methods?.[0];
|
||||
return (
|
||||
<div>
|
||||
<Badge className='mr-1 uppercase' title={method?.verified ? t('verified') : ''}>
|
||||
{method?.auth_type}
|
||||
</Badge>
|
||||
{method?.auth_identifier}
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
<div className={'flex items-center'}>
|
||||
{method?.auth_identifier}
|
||||
{row.original?.remark ? `(${row.original.remark})` : ''}
|
||||
<FilePenLine size={14} className={'text-primary ml-2'} />
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className={'w-64'}>
|
||||
<RemarkForm
|
||||
initialRemark={row.original.remark}
|
||||
CloseComponent={PopoverClose}
|
||||
onSave={async (remark) => {
|
||||
const {
|
||||
auth_methods,
|
||||
user_devices,
|
||||
enable_balance_notify,
|
||||
enable_login_notify,
|
||||
enable_subscribe_notify,
|
||||
enable_trade_notify,
|
||||
updated_at,
|
||||
created_at,
|
||||
id,
|
||||
...rest
|
||||
} = row.original;
|
||||
await updateUserBasicInfo({
|
||||
user_id: id,
|
||||
...rest,
|
||||
remark,
|
||||
} as unknown as API.UpdateUserBasiceInfoRequest);
|
||||
toast.success(t('updateSuccess'));
|
||||
ref.current?.refresh();
|
||||
}}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
'use client';
|
||||
|
||||
import { ProTable, ProTableActions } from '@/components/pro-table';
|
||||
import { getTicket, getTicketList, updateTicketStatus } from '@/services/admin/ticket';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Button } from '@workspace/ui/components/button';
|
||||
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle } from '@workspace/ui/components/drawer';
|
||||
import { ScrollArea } from '@workspace/ui/components/scroll-area';
|
||||
import { ConfirmButton } from '@workspace/ui/custom-components/confirm-button';
|
||||
import { cn } from '@workspace/ui/lib/utils';
|
||||
import { formatDate } from '@workspace/ui/utils';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import NextImage from 'next/legacy/image';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import { UserDetail } from '../user/user-detail';
|
||||
|
||||
export default function Page() {
|
||||
const t = useTranslations('ticket');
|
||||
|
||||
const [ticketId, setTicketId] = useState<any>(null);
|
||||
|
||||
const [message, setMessage] = useState('');
|
||||
|
||||
const { data: ticket, refetch: refetchTicket } = useQuery({
|
||||
queryKey: ['getTicket', ticketId],
|
||||
queryFn: async () => {
|
||||
const { data } = await getTicket({
|
||||
id: ticketId,
|
||||
});
|
||||
return data.data as API.Ticket;
|
||||
},
|
||||
enabled: !!ticketId,
|
||||
refetchInterval: 5000,
|
||||
});
|
||||
|
||||
const scrollRef = useRef<HTMLDivElement | null>(null);
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
if (scrollRef.current) {
|
||||
scrollRef.current.children[1]?.scrollTo({
|
||||
top: scrollRef.current.children[1].scrollHeight,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
}, 66);
|
||||
}, [ticket?.follow?.length]);
|
||||
|
||||
const ref = useRef<ProTableActions>(null);
|
||||
return (
|
||||
<>
|
||||
<ProTable<API.Ticket, { status: number }>
|
||||
action={ref}
|
||||
header={{
|
||||
title: t('ticketList'),
|
||||
}}
|
||||
columns={[
|
||||
{
|
||||
accessorKey: 'title',
|
||||
header: t('title'),
|
||||
},
|
||||
{
|
||||
accessorKey: 'user_id',
|
||||
header: t('user'),
|
||||
cell: ({ row }) => <UserDetail id={row.original.user_id} />,
|
||||
},
|
||||
{
|
||||
accessorKey: 'status',
|
||||
header: t('status.0'),
|
||||
cell: ({ row }) => (
|
||||
<span
|
||||
className={cn(
|
||||
'flex items-center gap-2 before:block before:size-1.5 before:animate-pulse before:rounded-full before:ring-2 before:ring-opacity-50',
|
||||
{
|
||||
'before:bg-rose-500 before:ring-rose-500': row.original.status === 1,
|
||||
'before:bg-yellow-500 before:ring-yellow-500': row.original.status === 2,
|
||||
'before:bg-green-500 before:ring-green-500': row.original.status === 3,
|
||||
'before:bg-zinc-500 before:ring-zinc-500': row.original.status === 4,
|
||||
},
|
||||
)}
|
||||
>
|
||||
{t(`status.${row.original.status}`)}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'updated_at',
|
||||
header: t('updatedAt'),
|
||||
cell: ({ row }) => formatDate(row.getValue('updated_at')),
|
||||
},
|
||||
]}
|
||||
params={[
|
||||
{
|
||||
key: 'status',
|
||||
placeholder: t('status.0'),
|
||||
options: [
|
||||
{
|
||||
label: t('close'),
|
||||
value: '4',
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
request={async (pagination, filters) => {
|
||||
const { data } = await getTicketList({
|
||||
...pagination,
|
||||
...filters,
|
||||
});
|
||||
return {
|
||||
list: data.data?.list || [],
|
||||
total: data.data?.total || 0,
|
||||
};
|
||||
}}
|
||||
actions={{
|
||||
render(row) {
|
||||
if (row.status !== 4) {
|
||||
return [
|
||||
<Button key='reply' size='sm' onClick={() => setTicketId(row.id)}>
|
||||
{t('check')}
|
||||
</Button>,
|
||||
<ConfirmButton
|
||||
key='colse'
|
||||
trigger={
|
||||
<Button size='sm' variant='destructive'>
|
||||
{t('close')}
|
||||
</Button>
|
||||
}
|
||||
title={t('confirmClose')}
|
||||
description={t('closeWarning')}
|
||||
onConfirm={async () => {
|
||||
await updateTicketStatus({
|
||||
id: row.id,
|
||||
status: 4,
|
||||
});
|
||||
toast.success(t('closeSuccess'));
|
||||
ref.current?.refresh();
|
||||
}}
|
||||
cancelText={t('cancel')}
|
||||
confirmText={t('confirm')}
|
||||
/>,
|
||||
];
|
||||
}
|
||||
return [
|
||||
<Button key='check' size='sm' onClick={() => setTicketId(row.id)}>
|
||||
{t('check')}
|
||||
</Button>,
|
||||
];
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<Drawer
|
||||
open={!!ticketId}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setTicketId(null);
|
||||
}}
|
||||
>
|
||||
<DrawerContent className='container mx-auto h-screen'>
|
||||
<DrawerHeader className='border-b text-left'>
|
||||
<DrawerTitle>{ticket?.title}</DrawerTitle>
|
||||
</DrawerHeader>
|
||||
<ScrollArea className='h-full overflow-hidden' ref={scrollRef}>
|
||||
<div className='flex h-full flex-col gap-4 p-4'>
|
||||
<div className={cn('flex items-center gap-4', {})}>
|
||||
<div className={cn('flex flex-col gap-1', {})}>
|
||||
<div className={cn('bg-accent w-fit rounded-lg p-2 font-medium', {})}>
|
||||
<div> 提现金额:{ticket?.title?.split('-')[1]}</div>
|
||||
<div> 提现方式:{ticket?.description?.split('-')[0]}</div>
|
||||
{ticket?.description?.split('-')[1].includes('data:image') ? (
|
||||
<div>
|
||||
<div>收款码:</div>
|
||||
<NextImage
|
||||
src={ticket?.description?.split('-')[1]}
|
||||
width={300}
|
||||
height={300}
|
||||
className='!size-auto object-cover'
|
||||
alt='image'
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div>提现地址:{ticket?.description?.split('-')[1]}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -104,6 +104,11 @@ export const navs = [
|
||||
url: '/dashboard/ticket',
|
||||
icon: 'flat-color-icons:collaboration',
|
||||
},
|
||||
{
|
||||
title: 'Withdraw Ticket Management',
|
||||
url: '/dashboard/withdraw-ticket',
|
||||
icon: 'flat-color-icons:collaboration',
|
||||
},
|
||||
{
|
||||
title: 'Document Management',
|
||||
url: '/dashboard/document',
|
||||
|
||||
@@ -23,5 +23,6 @@
|
||||
"Ticket Management": "Ticket Management",
|
||||
"User": "User",
|
||||
"User Detail": "User Detail",
|
||||
"User Management": "User Management"
|
||||
"User Management": "User Management",
|
||||
"Withdraw Ticket Management": "Withdraw Management"
|
||||
}
|
||||
|
||||
@@ -23,5 +23,6 @@
|
||||
"Ticket Management": "工单管理",
|
||||
"User": "用户",
|
||||
"User Detail": "用户详情",
|
||||
"User Management": "用户管理"
|
||||
"User Management": "用户管理",
|
||||
"Withdraw Ticket Management": "提现管理"
|
||||
}
|
||||
|
||||
+1
@@ -1878,6 +1878,7 @@ declare namespace API {
|
||||
id: number;
|
||||
avatar: string;
|
||||
balance: number;
|
||||
remark: string;
|
||||
commission: number;
|
||||
gift_amount: number;
|
||||
telegram: number;
|
||||
|
||||
Reference in New Issue
Block a user