Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
3a954e1e
authored
Feb 23, 2026
by
Seefs
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor: extract binding modal and polish binding management UX
parent
62856666
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
91 additions
and
77 deletions
+91
-77
web/src/components/table/users/modals/EditUserModal.jsx
+3
-3
web/src/components/table/users/modals/UserBindingManagementModal.jsx
+88
-74
No files found.
web/src/components/table/users/modals/EditUserModal.jsx
View file @
3a954e1e
...
...
@@ -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
>
...
...
web/src/components/table/users/modals/UserBindingManagementModal.jsx
View file @
3a954e1e
...
...
@@ -53,7 +53,7 @@ const UserBindingManagementModal = ({
})
=>
{
const
{
t
}
=
useTranslation
();
const
[
bindingLoading
,
setBindingLoading
]
=
React
.
useState
(
false
);
const
[
show
UnboundOnly
,
setShowUnboundOnly
]
=
React
.
useState
(
fals
e
);
const
[
show
BoundOnly
,
setShowBoundOnly
]
=
React
.
useState
(
tru
e
);
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
;
setShow
UnboundOnly
(
fals
e
);
setShow
BoundOnly
(
tru
e
);
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,86 +312,96 @@ const UserBindingManagementModal = ({
title=
{
<
div
className=
'flex items-center'
>
<
IconLink
className=
'mr-2'
/>
{
t
(
'
绑定信息
'
)
}
{
t
(
'
账户绑定管理
'
)
}
</
div
>
}
>
<
Spin
spinning=
{
bindingLoading
}
>
<
div
className=
'flex items-center justify-between mb-4 gap-3 flex-wrap'
>
<
Checkbox
checked=
{
showUnboundOnly
}
onChange=
{
(
e
)
=>
setShowUnboundOnly
(
Boolean
(
e
.
target
.
checked
))
}
>
{
`${t('筛选')} ${t('未绑定')}`
}
</
Checkbox
>
<
Text
type=
'tertiary'
>
{
t
(
'筛选'
)
}
·
{
visibleBindingItems
.
length
}
</
Text
>
</
div
>
<
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=
{
showBoundOnly
}
onChange=
{
(
e
)
=>
setShowBoundOnly
(
Boolean
(
e
.
target
.
checked
))
}
>
{
t
(
'仅显示已绑定'
)
}
</
Checkbox
>
<
Text
type=
'tertiary'
>
{
t
(
'已绑定'
)
}
{
boundCount
}
/
{
allBindingItems
.
length
}
</
Text
>
</
div
>
{
visibleBindingItems
.
length
===
0
?
(
<
Card
className=
'!rounded-xl border-dashed'
>
<
Text
type=
'tertiary'
>
{
t
(
'暂无自定义 OAuth 提供商'
)
}
</
Text
>
</
Card
>
)
:
(
<
div
className=
'grid grid-cols-1 lg:grid-cols-2 gap-3'
>
{
visibleBindingItems
.
map
((
item
)
=>
{
const
isBound
=
Boolean
(
item
.
value
);
const
loadingKey
=
item
.
type
===
'builtin'
?
`builtin-${item.key}`
:
`custom-${item.providerId}`
;
const
statusText
=
isBound
?
item
.
value
:
item
.
enabled
?
t
(
'未绑定'
)
:
t
(
'未启用'
);
{
visibleBindingItems
.
length
===
0
?
(
<
Card
className=
'!rounded-xl border-dashed'
>
<
Text
type=
'tertiary'
>
{
t
(
'暂无已绑定项'
)
}
</
Text
>
</
Card
>
)
:
(
<
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'
?
`builtin-${item.key}`
:
`custom-${item.providerId}`
;
const
statusText
=
isBound
?
item
.
value
:
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'
>
<
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
}
</
div
>
<
div
className=
'min-w-0 flex-1'
>
<
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'
}
</
Tag
>
return
(
<
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
}
</
div
>
<
div
className=
'text-sm text-gray-500 truncate'
>
{
statusText
}
<
div
className=
'min-w-0 flex-1'
>
<
div
className=
'font-medium text-gray-900 flex items-center gap-2'
>
<
span
>
{
item
.
name
}
</
span
>
<
Tag
size=
'small'
color=
'white'
>
{
item
.
type
===
'builtin'
?
t
(
'内置'
)
:
t
(
'自定义'
)
}
</
Tag
>
</
div
>
<
div
className=
'text-sm text-gray-500 truncate'
>
{
statusText
}
</
div
>
</
div
>
</
div
>
<
Button
type=
'danger'
theme=
'borderless'
icon=
{
<
IconDelete
/>
}
size=
'small'
disabled=
{
!
isBound
}
loading=
{
Boolean
(
bindingActionLoading
[
loadingKey
])
}
onClick=
{
()
=>
{
if
(
item
.
type
===
'builtin'
)
{
handleUnbindBuiltInAccount
(
item
);
return
;
}
handleUnbindCustomOAuthAccount
({
id
:
item
.
providerId
,
name
:
item
.
name
,
});
}
}
>
{
t
(
'解绑'
)
}
</
Button
>
</
div
>
<
Button
type=
'danger'
theme=
'borderless'
icon=
{
<
IconDelete
/>
}
size=
'small'
disabled=
{
!
isBound
}
loading=
{
Boolean
(
bindingActionLoading
[
loadingKey
])
}
onClick=
{
()
=>
{
if
(
item
.
type
===
'builtin'
)
{
handleUnbindBuiltInAccount
(
item
);
return
;
}
handleUnbindCustomOAuthAccount
({
id
:
item
.
providerId
,
name
:
item
.
name
,
});
}
}
>
{
t
(
'解绑'
)
}
</
Button
>
</
div
>
</
Card
>
);
})
}
</
div
>
)
}
</
Card
>
);
})
}
</
div
>
)
}
</
div
>
</
Spin
>
</
Modal
>
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment