fix: close admin settings review gaps

This commit is contained in:
IanShaw027
2026-04-21 00:41:29 +08:00
parent 55e8dd550a
commit 030da8c2f6
8 changed files with 110 additions and 14 deletions

View File

@@ -318,6 +318,7 @@ const pagination = reactive({
pageSize: 20,
total: 0,
})
const knownReportTypes = ref<string[]>([])
const columns: Column[] = [
{ key: 'status', label: text('状态', 'Status') },
@@ -330,12 +331,16 @@ const columns: Column[] = [
]
const reportTypeOptions = computed(() =>
Object.entries(summary.value.by_type)
.sort(([left], [right]) => left.localeCompare(right))
.map(([value, count]) => ({
value,
label: `${value} (${count})`,
}))
knownReportTypes.value
.slice()
.sort((left, right) => left.localeCompare(right))
.map((value) => {
const count = summary.value.by_type[value]
return {
value,
label: count === undefined ? value : `${value} (${count})`,
}
})
)
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 () => {
summaryLoading.value = true
try {
summary.value = await adminAPI.users.getAuthIdentityMigrationReportSummary()
mergeKnownReportTypes(...Object.keys(summary.value.by_type))
} catch (error) {
console.error('Failed to load auth identity migration report summary:', error)
appStore.showError(text('加载 migration reports 汇总失败', 'Failed to load migration report summary'))
@@ -370,6 +387,7 @@ const loadReports = async () => {
reports.value = response.items
pagination.total = response.total
mergeKnownReportTypes(filters.reportType, ...response.items.map((report) => report.report_type))
if (selectedReport.value) {
const refreshed = response.items.find((report) => report.id === selectedReport.value?.id) ?? null

View File

@@ -2728,8 +2728,8 @@
<p class="mt-1.5 text-xs text-gray-400">
{{
localText(
'留空表示自动路由;仅允许当前系统支持的官方或易支付来源。',
'Leave blank for automatic routing. Only supported official or EasyPay sources are allowed.'
'启用后必须明确选择一个来源;未配置状态不会对外展示该支付方式。',
'Choose an explicit source before enabling the method. Not configured methods are not exposed.'
)
}}
</p>
@@ -3450,6 +3450,28 @@ function setPaymentVisibleMethodSource(
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
const webSearchProxies = ref<Proxy[]>([])
@@ -3979,6 +4001,10 @@ async function saveSettings() {
}
}
if (!validatePaymentVisibleMethodSelections()) {
return
}
// Validate URL fields — novalidate disables browser-native checks, so we validate here
const isValidHttpUrl = (url: string): boolean => {
if (!url) return true

View File

@@ -240,4 +240,21 @@ describe('AuthIdentityMigrationReportsView', () => {
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')
})
})

View File

@@ -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('支付来源')
})
})