🐛 fix: 修改样式
This commit is contained in:
parent
c8401af672
commit
75db379624
@ -1,6 +1,5 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Display } from '@/components/display';
|
|
||||||
import { ProTable, ProTableActions } from '@/components/pro-table';
|
import { ProTable, ProTableActions } from '@/components/pro-table';
|
||||||
import {
|
import {
|
||||||
createUser,
|
createUser,
|
||||||
@ -12,7 +11,6 @@ import {
|
|||||||
import { useSubscribe } from '@/store/subscribe';
|
import { useSubscribe } from '@/store/subscribe';
|
||||||
import { formatDate } from '@/utils/common';
|
import { formatDate } from '@/utils/common';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { Badge } from '@workspace/ui/components/badge';
|
|
||||||
import { Button } from '@workspace/ui/components/button';
|
import { Button } from '@workspace/ui/components/button';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@ -20,6 +18,13 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@workspace/ui/components/dropdown-menu';
|
} from '@workspace/ui/components/dropdown-menu';
|
||||||
|
import { Input } from '@workspace/ui/components/input';
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverClose,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@workspace/ui/components/popover';
|
||||||
import { ScrollArea } from '@workspace/ui/components/scroll-area';
|
import { ScrollArea } from '@workspace/ui/components/scroll-area';
|
||||||
import {
|
import {
|
||||||
Sheet,
|
Sheet,
|
||||||
@ -31,6 +36,7 @@ import {
|
|||||||
import { Switch } from '@workspace/ui/components/switch';
|
import { Switch } from '@workspace/ui/components/switch';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
|
||||||
import { ConfirmButton } from '@workspace/ui/custom-components/confirm-button';
|
import { ConfirmButton } from '@workspace/ui/custom-components/confirm-button';
|
||||||
|
import { FilePenLine } from 'lucide-react';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
@ -38,11 +44,69 @@ import { useRef, useState } from 'react';
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { UserDetail } from './user-detail';
|
import { UserDetail } from './user-detail';
|
||||||
import UserForm from './user-form';
|
import UserForm from './user-form';
|
||||||
import { AuthMethodsForm } from './user-profile/auth-methods-form';
|
|
||||||
import { BasicInfoForm } from './user-profile/basic-info-form';
|
import { BasicInfoForm } from './user-profile/basic-info-form';
|
||||||
import { NotifySettingsForm } from './user-profile/notify-settings-form';
|
import { NotifySettingsForm } from './user-profile/notify-settings-form';
|
||||||
import UserSubscription from './user-subscription';
|
import UserSubscription from './user-subscription';
|
||||||
|
|
||||||
|
function getDeviceTypeInfo(userAgent = '') {
|
||||||
|
let deviceType = 'Unknown';
|
||||||
|
const ua = userAgent.toLowerCase();
|
||||||
|
|
||||||
|
if (ua.includes('android')) {
|
||||||
|
deviceType = 'Android';
|
||||||
|
} else if (ua.includes('iphone') || ua.includes('ios')) {
|
||||||
|
deviceType = 'iPhone';
|
||||||
|
} else if (ua.includes('ipad')) {
|
||||||
|
deviceType = 'iPad';
|
||||||
|
} else if (ua.includes('mac os') || ua.includes('mac')) {
|
||||||
|
deviceType = 'Mac';
|
||||||
|
} else if (ua.includes('windows')) {
|
||||||
|
deviceType = 'Windows';
|
||||||
|
} else if (ua.includes('linux')) {
|
||||||
|
deviceType = 'Linux';
|
||||||
|
}
|
||||||
|
|
||||||
|
return { deviceType };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为 RemarkForm 组件定义 props 类型
|
||||||
|
interface RemarkFormProps {
|
||||||
|
initialRemark?: string | null;
|
||||||
|
onSave: (remark: string) => void;
|
||||||
|
CloseComponent: React.ComponentType<{ asChild?: boolean; children: React.ReactNode }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新的子组件,在管理它自己的备注状态
|
||||||
|
const RemarkForm: React.FC<RemarkFormProps> = ({ onSave, initialRemark, CloseComponent }) => {
|
||||||
|
const [remark, setRemark] = useState<string>(initialRemark ?? '');
|
||||||
|
|
||||||
|
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
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() {
|
export default function Page() {
|
||||||
const t = useTranslations('user');
|
const t = useTranslations('user');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@ -56,6 +120,7 @@ export default function Page() {
|
|||||||
user_id: sp.get('user_id') || undefined,
|
user_id: sp.get('user_id') || undefined,
|
||||||
subscribe_id: sp.get('subscribe_id') || undefined,
|
subscribe_id: sp.get('subscribe_id') || undefined,
|
||||||
user_subscribe_id: sp.get('user_subscribe_id') || undefined,
|
user_subscribe_id: sp.get('user_subscribe_id') || undefined,
|
||||||
|
device_id: sp.get('device_id') || undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -128,20 +193,96 @@ export default function Page() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'auth_methods',
|
accessorKey: 'auth_methods',
|
||||||
header: t('userName'),
|
header: '绑定邮箱',
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const method = row.original.auth_methods?.[0];
|
const method = row.original.auth_methods;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Badge className='mr-1 uppercase' title={method?.verified ? t('verified') : ''}>
|
<Popover>
|
||||||
{method?.auth_type}
|
<PopoverTrigger>
|
||||||
</Badge>
|
<div className={'flex items-center'}>
|
||||||
{method?.auth_identifier}
|
{method?.find((v) => v.auth_type === 'email')?.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>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
accessorKey: 'user_devices',
|
||||||
|
header: '绑定设备',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const devices = row?.original.user_devices ?? [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
||||||
|
{devices.map((v, index) => {
|
||||||
|
const { deviceType } = getDeviceTypeInfo(v.user_agent);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={v.id + '_wrapper'}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: '4px 6px',
|
||||||
|
background: '#f8f8f8',
|
||||||
|
borderRadius: '4px',
|
||||||
|
border: '1px solid #e0e0e0',
|
||||||
|
fontSize: '12px',
|
||||||
|
lineHeight: '16px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ fontWeight: 500 }}>
|
||||||
|
ID:{v.id}({deviceType})
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{index !== devices.length - 1 && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: '1px',
|
||||||
|
background: '#eee',
|
||||||
|
margin: '4px 0',
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/*{
|
||||||
accessorKey: 'balance',
|
accessorKey: 'balance',
|
||||||
header: t('balance'),
|
header: t('balance'),
|
||||||
cell: ({ row }) => <Display type='currency' value={row.getValue('balance')} />,
|
cell: ({ row }) => <Display type='currency' value={row.getValue('balance')} />,
|
||||||
@ -155,7 +296,7 @@ export default function Page() {
|
|||||||
accessorKey: 'commission',
|
accessorKey: 'commission',
|
||||||
header: t('commission'),
|
header: t('commission'),
|
||||||
cell: ({ row }) => <Display type='currency' value={row.getValue('commission')} />,
|
cell: ({ row }) => <Display type='currency' value={row.getValue('commission')} />,
|
||||||
},
|
},*/
|
||||||
{
|
{
|
||||||
accessorKey: 'refer_code',
|
accessorKey: 'refer_code',
|
||||||
header: t('inviteCode'),
|
header: t('inviteCode'),
|
||||||
@ -203,6 +344,10 @@ export default function Page() {
|
|||||||
key: 'user_subscribe_id',
|
key: 'user_subscribe_id',
|
||||||
placeholder: t('subscriptionId'),
|
placeholder: t('subscriptionId'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'device_id',
|
||||||
|
placeholder: '设备id',
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
actions={{
|
actions={{
|
||||||
render: (row) => {
|
render: (row) => {
|
||||||
@ -281,7 +426,7 @@ function ProfileSheet({ userId }: { userId: number }) {
|
|||||||
<TabsList className='mb-3'>
|
<TabsList className='mb-3'>
|
||||||
<TabsTrigger value='basic'>{t('basicInfoTitle')}</TabsTrigger>
|
<TabsTrigger value='basic'>{t('basicInfoTitle')}</TabsTrigger>
|
||||||
<TabsTrigger value='notify'>{t('notifySettingsTitle')}</TabsTrigger>
|
<TabsTrigger value='notify'>{t('notifySettingsTitle')}</TabsTrigger>
|
||||||
<TabsTrigger value='auth'>{t('authMethodsTitle')}</TabsTrigger>
|
{/*<TabsTrigger value='auth'>{t('authMethodsTitle')}</TabsTrigger>*/}
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value='basic' className='mt-0'>
|
<TabsContent value='basic' className='mt-0'>
|
||||||
<BasicInfoForm user={user} refetch={refetch as any} />
|
<BasicInfoForm user={user} refetch={refetch as any} />
|
||||||
@ -289,9 +434,9 @@ function ProfileSheet({ userId }: { userId: number }) {
|
|||||||
<TabsContent value='notify' className='mt-0'>
|
<TabsContent value='notify' className='mt-0'>
|
||||||
<NotifySettingsForm user={user} refetch={refetch as any} />
|
<NotifySettingsForm user={user} refetch={refetch as any} />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value='auth' className='mt-0'>
|
{/*<TabsContent value='auth' className='mt-0'>
|
||||||
<AuthMethodsForm user={user} refetch={refetch as any} />
|
<AuthMethodsForm user={user} refetch={refetch as any} />
|
||||||
</TabsContent>
|
</TabsContent>*/}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -148,7 +148,8 @@ export function UserDetail({ id }: { id: number }) {
|
|||||||
|
|
||||||
const identifier =
|
const identifier =
|
||||||
data?.auth_methods.find((m) => m.auth_type === 'email')?.auth_identifier ||
|
data?.auth_methods.find((m) => m.auth_type === 'email')?.auth_identifier ||
|
||||||
data?.auth_methods[0]?.auth_identifier;
|
`设备Id:${data?.user_devices[0]?.id}` ||
|
||||||
|
'账号不存在';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HoverCard>
|
<HoverCard>
|
||||||
|
|||||||
1
apps/admin/services/admin/typings.d.ts
vendored
1
apps/admin/services/admin/typings.d.ts
vendored
@ -2360,6 +2360,7 @@ declare namespace API {
|
|||||||
id: number;
|
id: number;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
balance: number;
|
balance: number;
|
||||||
|
remark: string;
|
||||||
commission: number;
|
commission: number;
|
||||||
referral_percentage: number;
|
referral_percentage: number;
|
||||||
only_first_purchase: boolean;
|
only_first_purchase: boolean;
|
||||||
|
|||||||
@ -1,4 +0,0 @@
|
|||||||
import { config } from '@workspace/eslint-config/base';
|
|
||||||
|
|
||||||
/** @type {import("eslint").Linter.Config} */
|
|
||||||
export default config;
|
|
||||||
@ -10,6 +10,7 @@ const Popover = PopoverPrimitive.Root;
|
|||||||
const PopoverTrigger = PopoverPrimitive.Trigger;
|
const PopoverTrigger = PopoverPrimitive.Trigger;
|
||||||
|
|
||||||
const PopoverAnchor = PopoverPrimitive.Anchor;
|
const PopoverAnchor = PopoverPrimitive.Anchor;
|
||||||
|
const PopoverClose = PopoverPrimitive.Close;
|
||||||
|
|
||||||
const PopoverContent = React.forwardRef<
|
const PopoverContent = React.forwardRef<
|
||||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||||
@ -30,4 +31,4 @@ const PopoverContent = React.forwardRef<
|
|||||||
));
|
));
|
||||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
||||||
|
|
||||||
export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger };
|
export { Popover, PopoverAnchor, PopoverClose, PopoverContent, PopoverTrigger };
|
||||||
|
|||||||
@ -109,7 +109,7 @@ export function ProTable<
|
|||||||
const [rowCount, setRowCount] = useState<number>(0);
|
const [rowCount, setRowCount] = useState<number>(0);
|
||||||
const [pagination, setPagination] = useState({
|
const [pagination, setPagination] = useState({
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
pageSize: 10,
|
pageSize: 200,
|
||||||
});
|
});
|
||||||
const loading = useRef(false);
|
const loading = useRef(false);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user