Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
030da8c2
Commit
030da8c2
authored
Apr 21, 2026
by
IanShaw027
Browse files
fix: close admin settings review gaps
parent
55e8dd55
Changes
8
Show whitespace changes
Inline
Side-by-side
frontend/src/api/__tests__/settings.paymentVisibleMethods.spec.ts
View file @
030da8c2
...
@@ -27,8 +27,8 @@ describe('admin settings payment visible method helpers', () => {
...
@@ -27,8 +27,8 @@ describe('admin settings payment visible method helpers', () => {
expect
(
getPaymentVisibleMethodSourceOptions
(
'
alipay
'
)).
toEqual
([
expect
(
getPaymentVisibleMethodSourceOptions
(
'
alipay
'
)).
toEqual
([
{
{
value
:
''
,
value
:
''
,
labelZh
:
'
自动路由
'
,
labelZh
:
'
未配置
'
,
labelEn
:
'
Automatic routing
'
,
labelEn
:
'
Not configured
'
,
},
},
{
{
value
:
'
official_alipay
'
,
value
:
'
official_alipay
'
,
...
@@ -45,8 +45,8 @@ describe('admin settings payment visible method helpers', () => {
...
@@ -45,8 +45,8 @@ describe('admin settings payment visible method helpers', () => {
expect
(
getPaymentVisibleMethodSourceOptions
(
'
wxpay
'
)).
toEqual
([
expect
(
getPaymentVisibleMethodSourceOptions
(
'
wxpay
'
)).
toEqual
([
{
{
value
:
''
,
value
:
''
,
labelZh
:
'
自动路由
'
,
labelZh
:
'
未配置
'
,
labelEn
:
'
Automatic routing
'
,
labelEn
:
'
Not configured
'
,
},
},
{
{
value
:
'
official_wxpay
'
,
value
:
'
official_wxpay
'
,
...
...
frontend/src/api/admin/settings.ts
View file @
030da8c2
...
@@ -44,12 +44,12 @@ const PAYMENT_VISIBLE_METHOD_SOURCE_OPTIONS: Record<
...
@@ -44,12 +44,12 @@ const PAYMENT_VISIBLE_METHOD_SOURCE_OPTIONS: Record<
PaymentVisibleMethodSourceOption
[]
PaymentVisibleMethodSourceOption
[]
>
=
{
>
=
{
alipay
:
[
alipay
:
[
{
value
:
''
,
labelZh
:
'
自动路由
'
,
labelEn
:
'
Automatic routing
'
},
{
value
:
''
,
labelZh
:
'
未配置
'
,
labelEn
:
'
Not configured
'
},
{
value
:
'
official_alipay
'
,
labelZh
:
'
支付宝官方
'
,
labelEn
:
'
Official Alipay
'
},
{
value
:
'
official_alipay
'
,
labelZh
:
'
支付宝官方
'
,
labelEn
:
'
Official Alipay
'
},
{
value
:
'
easypay_alipay
'
,
labelZh
:
'
易支付支付宝
'
,
labelEn
:
'
EasyPay Alipay
'
},
{
value
:
'
easypay_alipay
'
,
labelZh
:
'
易支付支付宝
'
,
labelEn
:
'
EasyPay Alipay
'
},
],
],
wxpay
:
[
wxpay
:
[
{
value
:
''
,
labelZh
:
'
自动路由
'
,
labelEn
:
'
Automatic routing
'
},
{
value
:
''
,
labelZh
:
'
未配置
'
,
labelEn
:
'
Not configured
'
},
{
value
:
'
official_wxpay
'
,
labelZh
:
'
微信官方
'
,
labelEn
:
'
Official WeChat Pay
'
},
{
value
:
'
official_wxpay
'
,
labelZh
:
'
微信官方
'
,
labelEn
:
'
Official WeChat Pay
'
},
{
value
:
'
easypay_wxpay
'
,
labelZh
:
'
易支付微信
'
,
labelEn
:
'
EasyPay WeChat Pay
'
},
{
value
:
'
easypay_wxpay
'
,
labelZh
:
'
易支付微信
'
,
labelEn
:
'
EasyPay WeChat Pay
'
},
],
],
...
...
frontend/src/components/layout/AppSidebar.vue
View file @
030da8c2
...
@@ -663,6 +663,12 @@ const adminNavItems = computed((): NavItem[] => {
...
@@ -663,6 +663,12 @@ const adminNavItems = computed((): NavItem[] => {
?
[{
path
:
'
/admin/ops
'
,
label
:
t
(
'
nav.ops
'
),
icon
:
ChartIcon
}]
?
[{
path
:
'
/admin/ops
'
,
label
:
t
(
'
nav.ops
'
),
icon
:
ChartIcon
}]
:
[]),
:
[]),
{
path
:
'
/admin/users
'
,
label
:
t
(
'
nav.users
'
),
icon
:
UsersIcon
,
hideInSimpleMode
:
true
},
{
path
:
'
/admin/users
'
,
label
:
t
(
'
nav.users
'
),
icon
:
UsersIcon
,
hideInSimpleMode
:
true
},
{
path
:
'
/admin/users/auth-identity-migration-reports
'
,
label
:
'
Migration Reports
'
,
icon
:
UsersIcon
,
hideInSimpleMode
:
true
},
{
path
:
'
/admin/groups
'
,
label
:
t
(
'
nav.groups
'
),
icon
:
FolderIcon
,
hideInSimpleMode
:
true
},
{
path
:
'
/admin/groups
'
,
label
:
t
(
'
nav.groups
'
),
icon
:
FolderIcon
,
hideInSimpleMode
:
true
},
{
path
:
'
/admin/channels
'
,
label
:
t
(
'
nav.channels
'
,
'
渠道管理
'
),
icon
:
ChannelIcon
,
hideInSimpleMode
:
true
},
{
path
:
'
/admin/channels
'
,
label
:
t
(
'
nav.channels
'
,
'
渠道管理
'
),
icon
:
ChannelIcon
,
hideInSimpleMode
:
true
},
{
path
:
'
/admin/subscriptions
'
,
label
:
t
(
'
nav.subscriptions
'
),
icon
:
CreditCardIcon
,
hideInSimpleMode
:
true
},
{
path
:
'
/admin/subscriptions
'
,
label
:
t
(
'
nav.subscriptions
'
),
icon
:
CreditCardIcon
,
hideInSimpleMode
:
true
},
...
...
frontend/src/components/layout/__tests__/AppSidebar.spec.ts
View file @
030da8c2
...
@@ -30,3 +30,9 @@ describe('AppSidebar header styles', () => {
...
@@ -30,3 +30,9 @@ describe('AppSidebar header styles', () => {
expect
(
sidebarBrandBlockMatch
?.[
0
]).
not
.
toContain
(
'
overflow: hidden;
'
)
expect
(
sidebarBrandBlockMatch
?.[
0
]).
not
.
toContain
(
'
overflow: hidden;
'
)
})
})
})
})
describe
(
'
AppSidebar admin navigation
'
,
()
=>
{
it
(
'
includes a visible entry for auth identity migration reports
'
,
()
=>
{
expect
(
componentSource
).
toContain
(
"
'/admin/users/auth-identity-migration-reports'
"
)
})
})
frontend/src/views/admin/AuthIdentityMigrationReportsView.vue
View file @
030da8c2
...
@@ -318,6 +318,7 @@ const pagination = reactive({
...
@@ -318,6 +318,7 @@ const pagination = reactive({
pageSize
:
20
,
pageSize
:
20
,
total
:
0
,
total
:
0
,
})
})
const
knownReportTypes
=
ref
<
string
[]
>
([])
const
columns
:
Column
[]
=
[
const
columns
:
Column
[]
=
[
{
key
:
'
status
'
,
label
:
text
(
'
状态
'
,
'
Status
'
)
},
{
key
:
'
status
'
,
label
:
text
(
'
状态
'
,
'
Status
'
)
},
...
@@ -330,12 +331,16 @@ const columns: Column[] = [
...
@@ -330,12 +331,16 @@ const columns: Column[] = [
]
]
const
reportTypeOptions
=
computed
(()
=>
const
reportTypeOptions
=
computed
(()
=>
Object
.
entries
(
summary
.
value
.
by_type
)
knownReportTypes
.
value
.
sort
(([
left
],
[
right
])
=>
left
.
localeCompare
(
right
))
.
slice
()
.
map
(([
value
,
count
])
=>
({
.
sort
((
left
,
right
)
=>
left
.
localeCompare
(
right
))
.
map
((
value
)
=>
{
const
count
=
summary
.
value
.
by_type
[
value
]
return
{
value
,
value
,
label
:
`
${
value
}
(
${
count
}
)`
,
label
:
count
===
undefined
?
value
:
`
${
value
}
(
${
count
}
)`
,
}))
}
})
)
)
const
canResolve
=
computed
(()
=>
const
canResolve
=
computed
(()
=>
...
@@ -347,10 +352,22 @@ const canResolve = computed(() =>
...
@@ -347,10 +352,22 @@ const canResolve = computed(() =>
)
)
)
)
const
mergeKnownReportTypes
=
(...
values
:
Array
<
string
|
null
|
undefined
>
)
=>
{
const
merged
=
new
Set
(
knownReportTypes
.
value
)
for
(
const
value
of
values
)
{
const
normalized
=
value
?.
trim
()
if
(
normalized
)
{
merged
.
add
(
normalized
)
}
}
knownReportTypes
.
value
=
Array
.
from
(
merged
)
}
const
loadSummary
=
async
()
=>
{
const
loadSummary
=
async
()
=>
{
summaryLoading
.
value
=
true
summaryLoading
.
value
=
true
try
{
try
{
summary
.
value
=
await
adminAPI
.
users
.
getAuthIdentityMigrationReportSummary
()
summary
.
value
=
await
adminAPI
.
users
.
getAuthIdentityMigrationReportSummary
()
mergeKnownReportTypes
(...
Object
.
keys
(
summary
.
value
.
by_type
))
}
catch
(
error
)
{
}
catch
(
error
)
{
console
.
error
(
'
Failed to load auth identity migration report summary:
'
,
error
)
console
.
error
(
'
Failed to load auth identity migration report summary:
'
,
error
)
appStore
.
showError
(
text
(
'
加载 migration reports 汇总失败
'
,
'
Failed to load migration report summary
'
))
appStore
.
showError
(
text
(
'
加载 migration reports 汇总失败
'
,
'
Failed to load migration report summary
'
))
...
@@ -370,6 +387,7 @@ const loadReports = async () => {
...
@@ -370,6 +387,7 @@ const loadReports = async () => {
reports
.
value
=
response
.
items
reports
.
value
=
response
.
items
pagination
.
total
=
response
.
total
pagination
.
total
=
response
.
total
mergeKnownReportTypes
(
filters
.
reportType
,
...
response
.
items
.
map
((
report
)
=>
report
.
report_type
))
if
(
selectedReport
.
value
)
{
if
(
selectedReport
.
value
)
{
const
refreshed
=
response
.
items
.
find
((
report
)
=>
report
.
id
===
selectedReport
.
value
?.
id
)
??
null
const
refreshed
=
response
.
items
.
find
((
report
)
=>
report
.
id
===
selectedReport
.
value
?.
id
)
??
null
...
...
frontend/src/views/admin/SettingsView.vue
View file @
030da8c2
...
@@ -2728,8 +2728,8 @@
...
@@ -2728,8 +2728,8 @@
<
p
class
=
"
mt-1.5 text-xs text-gray-400
"
>
<
p
class
=
"
mt-1.5 text-xs text-gray-400
"
>
{{
{{
localText
(
localText
(
'
留空表示自动路由;仅允许当前系统支持的官方或易支付来源
。
'
,
'
启用后必须明确选择一个来源;未配置状态不会对外展示该支付方式
。
'
,
'
Leave blank for automatic routing. Only supported official or EasyPay sources are allow
ed.
'
'
Choose an explicit source before enabling the method. Not configured methods are not expos
ed.
'
)
)
}}
}}
<
/p
>
<
/p
>
...
@@ -3450,6 +3450,28 @@ function setPaymentVisibleMethodSource(
...
@@ -3450,6 +3450,28 @@ function setPaymentVisibleMethodSource(
form
.
payment_visible_method_wxpay_source
=
normalized
form
.
payment_visible_method_wxpay_source
=
normalized
}
}
function
validatePaymentVisibleMethodSelections
():
boolean
{
for
(
const
visibleMethod
of
paymentVisibleMethodCards
.
value
)
{
if
(
!
getPaymentVisibleMethodEnabled
(
visibleMethod
.
key
))
{
continue
}
if
(
getPaymentVisibleMethodSource
(
visibleMethod
.
key
))
{
continue
}
appStore
.
showError
(
localText
(
`${visibleMethod.title
}
已启用,请先选择支付来源`
,
`Select a payment source before enabling ${visibleMethod.title
}
`
)
)
return
false
}
return
true
}
// Proxies for web search emulation ProxySelector
// Proxies for web search emulation ProxySelector
const
webSearchProxies
=
ref
<
Proxy
[]
>
([])
const
webSearchProxies
=
ref
<
Proxy
[]
>
([])
...
@@ -3979,6 +4001,10 @@ async function saveSettings() {
...
@@ -3979,6 +4001,10 @@ async function saveSettings() {
}
}
}
}
if
(
!
validatePaymentVisibleMethodSelections
())
{
return
}
// Validate URL fields — novalidate disables browser-native checks, so we validate here
// Validate URL fields — novalidate disables browser-native checks, so we validate here
const
isValidHttpUrl
=
(
url
:
string
):
boolean
=>
{
const
isValidHttpUrl
=
(
url
:
string
):
boolean
=>
{
if
(
!
url
)
return
true
if
(
!
url
)
return
true
...
...
frontend/src/views/admin/__tests__/AuthIdentityMigrationReportsView.spec.ts
View file @
030da8c2
...
@@ -240,4 +240,21 @@ describe('AuthIdentityMigrationReportsView', () => {
...
@@ -240,4 +240,21 @@ describe('AuthIdentityMigrationReportsView', () => {
reportType
:
''
,
reportType
:
''
,
})
})
})
})
it
(
'
keeps report type filter options available from list data when summary fails
'
,
async
()
=>
{
getAuthIdentityMigrationReportSummary
.
mockRejectedValueOnce
(
new
Error
(
'
summary failed
'
))
listAuthIdentityMigrationReports
.
mockResolvedValueOnce
(
listResponse
)
const
wrapper
=
mountView
()
await
flushPromises
()
const
options
=
wrapper
.
get
(
'
[data-test="report-type-filter"]
'
)
.
findAll
(
'
option
'
)
.
map
((
node
)
=>
node
.
element
.
value
)
expect
(
showError
).
toHaveBeenCalled
()
expect
(
options
).
toContain
(
'
oidc_synthetic_email_requires_manual_recovery
'
)
})
})
})
frontend/src/views/admin/__tests__/SettingsView.spec.ts
View file @
030da8c2
...
@@ -449,4 +449,27 @@ describe('admin SettingsView payment visible method controls', () => {
...
@@ -449,4 +449,27 @@ describe('admin SettingsView payment visible method controls', () => {
})
})
)
)
})
})
it
(
'
blocks saving when a visible payment method is enabled without a source
'
,
async
()
=>
{
const
wrapper
=
mountView
()
await
flushPromises
()
await
openPaymentTab
(
wrapper
)
const
paymentSourceSelects
=
wrapper
.
findAll
(
'
select.select-stub
'
)
.
filter
((
node
)
=>
[
'
alipay
'
,
'
wxpay
'
].
includes
(
node
.
attributes
(
'
data-placeholder
'
)))
const
alipaySelect
=
paymentSourceSelects
.
find
(
(
node
)
=>
node
.
attributes
(
'
data-placeholder
'
)
===
'
alipay
'
)
await
alipaySelect
?.
setValue
(
''
)
await
wrapper
.
find
(
'
form
'
).
trigger
(
'
submit.prevent
'
)
await
flushPromises
()
expect
(
updateSettings
).
not
.
toHaveBeenCalled
()
expect
(
showError
).
toHaveBeenCalled
()
expect
(
String
(
showError
.
mock
.
calls
.
at
(
-
1
)?.[
0
]
??
''
)).
toContain
(
'
支付来源
'
)
})
})
})
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