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

@@ -27,8 +27,8 @@ describe('admin settings payment visible method helpers', () => {
expect(getPaymentVisibleMethodSourceOptions('alipay')).toEqual([
{
value: '',
labelZh: '自动路由',
labelEn: 'Automatic routing',
labelZh: '未配置',
labelEn: 'Not configured',
},
{
value: 'official_alipay',
@@ -45,8 +45,8 @@ describe('admin settings payment visible method helpers', () => {
expect(getPaymentVisibleMethodSourceOptions('wxpay')).toEqual([
{
value: '',
labelZh: '自动路由',
labelEn: 'Automatic routing',
labelZh: '未配置',
labelEn: 'Not configured',
},
{
value: 'official_wxpay',

View File

@@ -44,12 +44,12 @@ const PAYMENT_VISIBLE_METHOD_SOURCE_OPTIONS: Record<
PaymentVisibleMethodSourceOption[]
> = {
alipay: [
{ value: '', labelZh: '自动路由', labelEn: 'Automatic routing' },
{ value: '', labelZh: '未配置', labelEn: 'Not configured' },
{ value: 'official_alipay', labelZh: '支付宝官方', labelEn: 'Official Alipay' },
{ value: 'easypay_alipay', labelZh: '易支付支付宝', labelEn: 'EasyPay Alipay' },
],
wxpay: [
{ value: '', labelZh: '自动路由', labelEn: 'Automatic routing' },
{ value: '', labelZh: '未配置', labelEn: 'Not configured' },
{ value: 'official_wxpay', labelZh: '微信官方', labelEn: 'Official WeChat Pay' },
{ value: 'easypay_wxpay', labelZh: '易支付微信', labelEn: 'EasyPay WeChat Pay' },
],

View File

@@ -663,6 +663,12 @@ const adminNavItems = computed((): NavItem[] => {
? [{ path: '/admin/ops', label: t('nav.ops'), icon: ChartIcon }]
: []),
{ 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/channels', label: t('nav.channels', '渠道管理'), icon: ChannelIcon, hideInSimpleMode: true },
{ path: '/admin/subscriptions', label: t('nav.subscriptions'), icon: CreditCardIcon, hideInSimpleMode: true },

View File

@@ -30,3 +30,9 @@ describe('AppSidebar header styles', () => {
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'")
})
})

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