Skip to content

Commit 3a954e1

Browse files
committed
refactor: extract binding modal and polish binding management UX
1 parent 6285666 commit 3a954e1

File tree

2 files changed

+91
-77
lines changed

2 files changed

+91
-77
lines changed

web/src/components/table/users/modals/EditUserModal.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ const EditUserModal = (props) => {
207207
onSubmit={submit}
208208
>
209209
{({ values }) => (
210-
<div className='p-2'>
210+
<div className='p-2 space-y-3'>
211211
{/* 基本信息 */}
212212
<Card className='!rounded-2xl shadow-sm border-0'>
213213
<div className='flex items-center mb-2'>
@@ -344,7 +344,7 @@ const EditUserModal = (props) => {
344344
{t('绑定信息')}
345345
</Text>
346346
<div className='text-xs text-gray-600'>
347-
{t('第三方账户绑定状态(只读)')}
347+
{t('管理用户已绑定的第三方账户,支持筛选与解绑')}
348348
</div>
349349
</div>
350350
</div>
@@ -353,7 +353,7 @@ const EditUserModal = (props) => {
353353
theme='outline'
354354
onClick={openBindingModal}
355355
>
356-
{t('修改绑定')}
356+
{t('管理绑定')}
357357
</Button>
358358
</div>
359359
</Card>

web/src/components/table/users/modals/UserBindingManagementModal.jsx

Lines changed: 88 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ const UserBindingManagementModal = ({
5353
}) => {
5454
const { t } = useTranslation();
5555
const [bindingLoading, setBindingLoading] = React.useState(false);
56-
const [showUnboundOnly, setShowUnboundOnly] = React.useState(false);
56+
const [showBoundOnly, setShowBoundOnly] = React.useState(true);
5757
const [statusInfo, setStatusInfo] = React.useState({});
5858
const [customOAuthBindings, setCustomOAuthBindings] = React.useState([]);
5959
const [bindingActionLoading, setBindingActionLoading] = React.useState({});
@@ -90,7 +90,7 @@ const UserBindingManagementModal = ({
9090

9191
React.useEffect(() => {
9292
if (!visible) return;
93-
setShowUnboundOnly(false);
93+
setShowBoundOnly(true);
9494
setBindingActionLoading({});
9595
loadBindingData();
9696
}, [visible, loadBindingData]);
@@ -294,8 +294,12 @@ const UserBindingManagementModal = ({
294294
...customBindingItems.map((item) => ({ ...item, type: 'custom' })),
295295
];
296296

297-
const visibleBindingItems = showUnboundOnly
298-
? allBindingItems.filter((item) => !item.value)
297+
const boundCount = allBindingItems.filter((item) =>
298+
Boolean(item.value),
299+
).length;
300+
301+
const visibleBindingItems = showBoundOnly
302+
? allBindingItems.filter((item) => Boolean(item.value))
299303
: allBindingItems;
300304

301305
return (
@@ -308,86 +312,96 @@ const UserBindingManagementModal = ({
308312
title={
309313
<div className='flex items-center'>
310314
<IconLink className='mr-2' />
311-
{t('绑定信息')}
315+
{t('账户绑定管理')}
312316
</div>
313317
}
314318
>
315319
<Spin spinning={bindingLoading}>
316-
<div className='flex items-center justify-between mb-4 gap-3 flex-wrap'>
317-
<Checkbox
318-
checked={showUnboundOnly}
319-
onChange={(e) => setShowUnboundOnly(Boolean(e.target.checked))}
320-
>
321-
{`${t('筛选')} ${t('未绑定')}`}
322-
</Checkbox>
323-
<Text type='tertiary'>
324-
{t('筛选')} · {visibleBindingItems.length}
325-
</Text>
326-
</div>
320+
<div className='max-h-[68vh] overflow-y-auto pr-1 pb-2'>
321+
<div className='flex items-center justify-between mb-4 gap-3 flex-wrap'>
322+
<Checkbox
323+
checked={showBoundOnly}
324+
onChange={(e) => setShowBoundOnly(Boolean(e.target.checked))}
325+
>
326+
{t('仅显示已绑定')}
327+
</Checkbox>
328+
<Text type='tertiary'>
329+
{t('已绑定')} {boundCount} / {allBindingItems.length}
330+
</Text>
331+
</div>
327332

328-
{visibleBindingItems.length === 0 ? (
329-
<Card className='!rounded-xl border-dashed'>
330-
<Text type='tertiary'>{t('暂无自定义 OAuth 提供商')}</Text>
331-
</Card>
332-
) : (
333-
<div className='grid grid-cols-1 lg:grid-cols-2 gap-3'>
334-
{visibleBindingItems.map((item) => {
335-
const isBound = Boolean(item.value);
336-
const loadingKey =
337-
item.type === 'builtin'
338-
? `builtin-${item.key}`
339-
: `custom-${item.providerId}`;
340-
const statusText = isBound
341-
? item.value
342-
: item.enabled
343-
? t('未绑定')
344-
: t('未启用');
333+
{visibleBindingItems.length === 0 ? (
334+
<Card className='!rounded-xl border-dashed'>
335+
<Text type='tertiary'>{t('暂无已绑定项')}</Text>
336+
</Card>
337+
) : (
338+
<div className='grid grid-cols-1 lg:grid-cols-2 gap-4'>
339+
{visibleBindingItems.map((item, index) => {
340+
const isBound = Boolean(item.value);
341+
const loadingKey =
342+
item.type === 'builtin'
343+
? `builtin-${item.key}`
344+
: `custom-${item.providerId}`;
345+
const statusText = isBound
346+
? item.value
347+
: item.enabled
348+
? t('未绑定')
349+
: t('未启用');
350+
const shouldSpanTwoColsOnDesktop =
351+
visibleBindingItems.length % 2 === 1 &&
352+
index === visibleBindingItems.length - 1;
345353

346-
return (
347-
<Card key={item.key} className='!rounded-xl'>
348-
<div className='flex items-center justify-between gap-3'>
349-
<div className='flex items-center flex-1 min-w-0'>
350-
<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'>
351-
{item.icon}
352-
</div>
353-
<div className='min-w-0 flex-1'>
354-
<div className='font-medium text-gray-900 flex items-center gap-2'>
355-
<span>{item.name}</span>
356-
<Tag size='small' color='white'>
357-
{item.type === 'builtin' ? 'Built-in' : 'Custom'}
358-
</Tag>
354+
return (
355+
<Card
356+
key={item.key}
357+
className={`!rounded-xl ${shouldSpanTwoColsOnDesktop ? 'lg:col-span-2' : ''}`}
358+
>
359+
<div className='flex items-center justify-between gap-3 min-h-[92px]'>
360+
<div className='flex items-center flex-1 min-w-0'>
361+
<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'>
362+
{item.icon}
359363
</div>
360-
<div className='text-sm text-gray-500 truncate'>
361-
{statusText}
364+
<div className='min-w-0 flex-1'>
365+
<div className='font-medium text-gray-900 flex items-center gap-2'>
366+
<span>{item.name}</span>
367+
<Tag size='small' color='white'>
368+
{item.type === 'builtin'
369+
? t('内置')
370+
: t('自定义')}
371+
</Tag>
372+
</div>
373+
<div className='text-sm text-gray-500 truncate'>
374+
{statusText}
375+
</div>
362376
</div>
363377
</div>
378+
<Button
379+
type='danger'
380+
theme='borderless'
381+
icon={<IconDelete />}
382+
size='small'
383+
disabled={!isBound}
384+
loading={Boolean(bindingActionLoading[loadingKey])}
385+
onClick={() => {
386+
if (item.type === 'builtin') {
387+
handleUnbindBuiltInAccount(item);
388+
return;
389+
}
390+
handleUnbindCustomOAuthAccount({
391+
id: item.providerId,
392+
name: item.name,
393+
});
394+
}}
395+
>
396+
{t('解绑')}
397+
</Button>
364398
</div>
365-
<Button
366-
type='danger'
367-
theme='borderless'
368-
icon={<IconDelete />}
369-
size='small'
370-
disabled={!isBound}
371-
loading={Boolean(bindingActionLoading[loadingKey])}
372-
onClick={() => {
373-
if (item.type === 'builtin') {
374-
handleUnbindBuiltInAccount(item);
375-
return;
376-
}
377-
handleUnbindCustomOAuthAccount({
378-
id: item.providerId,
379-
name: item.name,
380-
});
381-
}}
382-
>
383-
{t('解绑')}
384-
</Button>
385-
</div>
386-
</Card>
387-
);
388-
})}
389-
</div>
390-
)}
399+
</Card>
400+
);
401+
})}
402+
</div>
403+
)}
404+
</div>
391405
</Spin>
392406
</Modal>
393407
);

0 commit comments

Comments
 (0)