refactor: extract binding modal and polish binding management UX

This commit is contained in:
Seefs
2026-02-23 15:16:22 +08:00
parent 9a5f8222bd
commit 2f4d38fefd
2 changed files with 96 additions and 82 deletions

View File

@@ -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>

View File

@@ -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>
); );