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}
>
{({ values }) => (
<div className='p-2'>
<div className='p-2 space-y-3'>
{/* 基本信息 */}
<Card className='!rounded-2xl shadow-sm border-0'>
<div className='flex items-center mb-2'>
@@ -344,7 +344,7 @@ const EditUserModal = (props) => {
{t('绑定信息')}
</Text>
<div className='text-xs text-gray-600'>
{t('第三方账户绑定状态(只读)')}
{t('管理用户已绑定的第三方账户,支持筛选与解绑')}
</div>
</div>
</div>
@@ -353,7 +353,7 @@ const EditUserModal = (props) => {
theme='outline'
onClick={openBindingModal}
>
{t('修改绑定')}
{t('管理绑定')}
</Button>
</div>
</Card>

View File

@@ -53,7 +53,7 @@ const UserBindingManagementModal = ({
}) => {
const { t } = useTranslation();
const [bindingLoading, setBindingLoading] = React.useState(false);
const [showUnboundOnly, setShowUnboundOnly] = React.useState(false);
const [showBoundOnly, setShowBoundOnly] = React.useState(true);
const [statusInfo, setStatusInfo] = React.useState({});
const [customOAuthBindings, setCustomOAuthBindings] = React.useState([]);
const [bindingActionLoading, setBindingActionLoading] = React.useState({});
@@ -90,7 +90,7 @@ const UserBindingManagementModal = ({
React.useEffect(() => {
if (!visible) return;
setShowUnboundOnly(false);
setShowBoundOnly(true);
setBindingActionLoading({});
loadBindingData();
}, [visible, loadBindingData]);
@@ -294,8 +294,12 @@ const UserBindingManagementModal = ({
...customBindingItems.map((item) => ({ ...item, type: 'custom' })),
];
const visibleBindingItems = showUnboundOnly
? allBindingItems.filter((item) => !item.value)
const boundCount = allBindingItems.filter((item) =>
Boolean(item.value),
).length;
const visibleBindingItems = showBoundOnly
? allBindingItems.filter((item) => Boolean(item.value))
: allBindingItems;
return (
@@ -308,30 +312,31 @@ const UserBindingManagementModal = ({
title={
<div className='flex items-center'>
<IconLink className='mr-2' />
{t('绑定信息')}
{t('账户绑定管理')}
</div>
}
>
<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'>
<Checkbox
checked={showUnboundOnly}
onChange={(e) => setShowUnboundOnly(Boolean(e.target.checked))}
checked={showBoundOnly}
onChange={(e) => setShowBoundOnly(Boolean(e.target.checked))}
>
{`${t('筛选')} ${t('未绑定')}`}
{t('仅显示已绑定')}
</Checkbox>
<Text type='tertiary'>
{t('筛选')} · {visibleBindingItems.length}
{t('已绑定')} {boundCount} / {allBindingItems.length}
</Text>
</div>
{visibleBindingItems.length === 0 ? (
<Card className='!rounded-xl border-dashed'>
<Text type='tertiary'>{t('暂无自定义 OAuth 提供商')}</Text>
<Text type='tertiary'>{t('暂无已绑定项')}</Text>
</Card>
) : (
<div className='grid grid-cols-1 lg:grid-cols-2 gap-3'>
{visibleBindingItems.map((item) => {
<div className='grid grid-cols-1 lg:grid-cols-2 gap-4'>
{visibleBindingItems.map((item, index) => {
const isBound = Boolean(item.value);
const loadingKey =
item.type === 'builtin'
@@ -342,10 +347,16 @@ const UserBindingManagementModal = ({
: item.enabled
? t('未绑定')
: t('未启用');
const shouldSpanTwoColsOnDesktop =
visibleBindingItems.length % 2 === 1 &&
index === visibleBindingItems.length - 1;
return (
<Card key={item.key} className='!rounded-xl'>
<div className='flex items-center justify-between gap-3'>
<Card
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='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}
@@ -354,7 +365,9 @@ const UserBindingManagementModal = ({
<div className='font-medium text-gray-900 flex items-center gap-2'>
<span>{item.name}</span>
<Tag size='small' color='white'>
{item.type === 'builtin' ? 'Built-in' : 'Custom'}
{item.type === 'builtin'
? t('内置')
: t('自定义')}
</Tag>
</div>
<div className='text-sm text-gray-500 truncate'>
@@ -388,6 +401,7 @@ const UserBindingManagementModal = ({
})}
</div>
)}
</div>
</Spin>
</Modal>
);