mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-04-19 07:57:28 +00:00
refactor: extract binding modal and polish binding management UX
This commit is contained in:
@@ -207,7 +207,7 @@ const EditUserModal = (props) => {
|
|||||||
onSubmit={submit}
|
onSubmit={submit}
|
||||||
>
|
>
|
||||||
{({ values }) => (
|
{({ values }) => (
|
||||||
<div className='p-2'>
|
<div className='p-2 space-y-3'>
|
||||||
{/* 基本信息 */}
|
{/* 基本信息 */}
|
||||||
<Card className='!rounded-2xl shadow-sm border-0'>
|
<Card className='!rounded-2xl shadow-sm border-0'>
|
||||||
<div className='flex items-center mb-2'>
|
<div className='flex items-center mb-2'>
|
||||||
@@ -344,7 +344,7 @@ const EditUserModal = (props) => {
|
|||||||
{t('绑定信息')}
|
{t('绑定信息')}
|
||||||
</Text>
|
</Text>
|
||||||
<div className='text-xs text-gray-600'>
|
<div className='text-xs text-gray-600'>
|
||||||
{t('第三方账户绑定状态(只读)')}
|
{t('管理用户已绑定的第三方账户,支持筛选与解绑')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -353,7 +353,7 @@ const EditUserModal = (props) => {
|
|||||||
theme='outline'
|
theme='outline'
|
||||||
onClick={openBindingModal}
|
onClick={openBindingModal}
|
||||||
>
|
>
|
||||||
{t('修改绑定')}
|
{t('管理绑定')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ const UserBindingManagementModal = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [bindingLoading, setBindingLoading] = React.useState(false);
|
const [bindingLoading, setBindingLoading] = React.useState(false);
|
||||||
const [showUnboundOnly, setShowUnboundOnly] = React.useState(false);
|
const [showBoundOnly, setShowBoundOnly] = React.useState(true);
|
||||||
const [statusInfo, setStatusInfo] = React.useState({});
|
const [statusInfo, setStatusInfo] = React.useState({});
|
||||||
const [customOAuthBindings, setCustomOAuthBindings] = React.useState([]);
|
const [customOAuthBindings, setCustomOAuthBindings] = React.useState([]);
|
||||||
const [bindingActionLoading, setBindingActionLoading] = React.useState({});
|
const [bindingActionLoading, setBindingActionLoading] = React.useState({});
|
||||||
@@ -90,7 +90,7 @@ const UserBindingManagementModal = ({
|
|||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!visible) return;
|
if (!visible) return;
|
||||||
setShowUnboundOnly(false);
|
setShowBoundOnly(true);
|
||||||
setBindingActionLoading({});
|
setBindingActionLoading({});
|
||||||
loadBindingData();
|
loadBindingData();
|
||||||
}, [visible, loadBindingData]);
|
}, [visible, loadBindingData]);
|
||||||
@@ -294,8 +294,12 @@ const UserBindingManagementModal = ({
|
|||||||
...customBindingItems.map((item) => ({ ...item, type: 'custom' })),
|
...customBindingItems.map((item) => ({ ...item, type: 'custom' })),
|
||||||
];
|
];
|
||||||
|
|
||||||
const visibleBindingItems = showUnboundOnly
|
const boundCount = allBindingItems.filter((item) =>
|
||||||
? allBindingItems.filter((item) => !item.value)
|
Boolean(item.value),
|
||||||
|
).length;
|
||||||
|
|
||||||
|
const visibleBindingItems = showBoundOnly
|
||||||
|
? allBindingItems.filter((item) => Boolean(item.value))
|
||||||
: allBindingItems;
|
: allBindingItems;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -308,30 +312,31 @@ const UserBindingManagementModal = ({
|
|||||||
title={
|
title={
|
||||||
<div className='flex items-center'>
|
<div className='flex items-center'>
|
||||||
<IconLink className='mr-2' />
|
<IconLink className='mr-2' />
|
||||||
{t('绑定信息')}
|
{t('账户绑定管理')}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Spin spinning={bindingLoading}>
|
<Spin spinning={bindingLoading}>
|
||||||
|
<div className='max-h-[68vh] overflow-y-auto pr-1 pb-2'>
|
||||||
<div className='flex items-center justify-between mb-4 gap-3 flex-wrap'>
|
<div className='flex items-center justify-between mb-4 gap-3 flex-wrap'>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={showUnboundOnly}
|
checked={showBoundOnly}
|
||||||
onChange={(e) => setShowUnboundOnly(Boolean(e.target.checked))}
|
onChange={(e) => setShowBoundOnly(Boolean(e.target.checked))}
|
||||||
>
|
>
|
||||||
{`${t('筛选')} ${t('未绑定')}`}
|
{t('仅显示已绑定')}
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
<Text type='tertiary'>
|
<Text type='tertiary'>
|
||||||
{t('筛选')} · {visibleBindingItems.length}
|
{t('已绑定')} {boundCount} / {allBindingItems.length}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{visibleBindingItems.length === 0 ? (
|
{visibleBindingItems.length === 0 ? (
|
||||||
<Card className='!rounded-xl border-dashed'>
|
<Card className='!rounded-xl border-dashed'>
|
||||||
<Text type='tertiary'>{t('暂无自定义 OAuth 提供商')}</Text>
|
<Text type='tertiary'>{t('暂无已绑定项')}</Text>
|
||||||
</Card>
|
</Card>
|
||||||
) : (
|
) : (
|
||||||
<div className='grid grid-cols-1 lg:grid-cols-2 gap-3'>
|
<div className='grid grid-cols-1 lg:grid-cols-2 gap-4'>
|
||||||
{visibleBindingItems.map((item) => {
|
{visibleBindingItems.map((item, index) => {
|
||||||
const isBound = Boolean(item.value);
|
const isBound = Boolean(item.value);
|
||||||
const loadingKey =
|
const loadingKey =
|
||||||
item.type === 'builtin'
|
item.type === 'builtin'
|
||||||
@@ -342,10 +347,16 @@ const UserBindingManagementModal = ({
|
|||||||
: item.enabled
|
: item.enabled
|
||||||
? t('未绑定')
|
? t('未绑定')
|
||||||
: t('未启用');
|
: t('未启用');
|
||||||
|
const shouldSpanTwoColsOnDesktop =
|
||||||
|
visibleBindingItems.length % 2 === 1 &&
|
||||||
|
index === visibleBindingItems.length - 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card key={item.key} className='!rounded-xl'>
|
<Card
|
||||||
<div className='flex items-center justify-between gap-3'>
|
key={item.key}
|
||||||
|
className={`!rounded-xl ${shouldSpanTwoColsOnDesktop ? 'lg:col-span-2' : ''}`}
|
||||||
|
>
|
||||||
|
<div className='flex items-center justify-between gap-3 min-h-[92px]'>
|
||||||
<div className='flex items-center flex-1 min-w-0'>
|
<div className='flex items-center flex-1 min-w-0'>
|
||||||
<div className='w-10 h-10 rounded-full bg-slate-100 dark:bg-slate-700 flex items-center justify-center mr-3 flex-shrink-0'>
|
<div className='w-10 h-10 rounded-full bg-slate-100 dark:bg-slate-700 flex items-center justify-center mr-3 flex-shrink-0'>
|
||||||
{item.icon}
|
{item.icon}
|
||||||
@@ -354,7 +365,9 @@ const UserBindingManagementModal = ({
|
|||||||
<div className='font-medium text-gray-900 flex items-center gap-2'>
|
<div className='font-medium text-gray-900 flex items-center gap-2'>
|
||||||
<span>{item.name}</span>
|
<span>{item.name}</span>
|
||||||
<Tag size='small' color='white'>
|
<Tag size='small' color='white'>
|
||||||
{item.type === 'builtin' ? 'Built-in' : 'Custom'}
|
{item.type === 'builtin'
|
||||||
|
? t('内置')
|
||||||
|
: t('自定义')}
|
||||||
</Tag>
|
</Tag>
|
||||||
</div>
|
</div>
|
||||||
<div className='text-sm text-gray-500 truncate'>
|
<div className='text-sm text-gray-500 truncate'>
|
||||||
@@ -388,6 +401,7 @@ const UserBindingManagementModal = ({
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</Spin>
|
</Spin>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user