feat(captcha): add provider configuration tabs
This commit is contained in:
@@ -53,7 +53,7 @@
|
||||
:collapsible="!!group.collapsible"
|
||||
:default-collapsed="resolveGroupCollapsed(group)"
|
||||
>
|
||||
<template v-if="group.fields">
|
||||
<template v-if="group.fields && !hasGroupSlot(group)">
|
||||
<template v-for="field in group.fields" :key="field.key">
|
||||
<template v-if="isFieldVisible(field)">
|
||||
<slot
|
||||
@@ -127,7 +127,7 @@
|
||||
* - Dependency-based field visibility
|
||||
* - Loading and error states
|
||||
*/
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, useSlots } from 'vue'
|
||||
import FaIcon from './FaIcon.vue'
|
||||
import AksFieldGroup from './AksFieldGroup.vue'
|
||||
import AksField from './AksField.vue'
|
||||
@@ -162,6 +162,7 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:formData', 'save', 'reload'])
|
||||
const slots = useSlots()
|
||||
|
||||
const searchQuery = ref('')
|
||||
|
||||
@@ -202,6 +203,10 @@ function matchDependsOn(dependsOn) {
|
||||
return true
|
||||
}
|
||||
|
||||
function hasGroupSlot(group) {
|
||||
return !!slots[`group-${group.key}`]
|
||||
}
|
||||
|
||||
/** Check if a field should be visible based on its dependsOn rule */
|
||||
function isFieldVisible(field) {
|
||||
return matchDependsOn(field.dependsOn)
|
||||
|
||||
@@ -17,30 +17,29 @@
|
||||
>
|
||||
<template
|
||||
v-if="isPermissionsLogin"
|
||||
#field-tencent="{ value, setValue }"
|
||||
#group-providers
|
||||
>
|
||||
<TencentConfig :model-value="value || {}" @update:model-value="setValue" @clear-secret="markCaptchaSecretForClear('tencent', $event)" @restore-secret="unmarkCaptchaSecretForClear('tencent', $event)" />
|
||||
</template>
|
||||
|
||||
<template
|
||||
v-if="isPermissionsLogin"
|
||||
#field-aliyun="{ value, setValue }"
|
||||
>
|
||||
<AliyunConfig :model-value="value || {}" @update:model-value="setValue" @clear-secret="markCaptchaSecretForClear('aliyun', $event)" @restore-secret="unmarkCaptchaSecretForClear('aliyun', $event)" />
|
||||
</template>
|
||||
|
||||
<template
|
||||
v-if="isPermissionsLogin"
|
||||
#field-cloudflare="{ value, setValue }"
|
||||
>
|
||||
<CloudflareConfig :model-value="value || {}" @update:model-value="setValue" @clear-secret="markCaptchaSecretForClear('cloudflare', $event)" @restore-secret="unmarkCaptchaSecretForClear('cloudflare', $event)" />
|
||||
</template>
|
||||
|
||||
<template
|
||||
v-if="isPermissionsLogin"
|
||||
#field-geetest="{ value, setValue }"
|
||||
>
|
||||
<GeetestConfig :model-value="value || {}" @update:model-value="setValue" @clear-secret="markCaptchaSecretForClear('geetest', $event)" @restore-secret="unmarkCaptchaSecretForClear('geetest', $event)" />
|
||||
<n-tabs
|
||||
v-model:value="captchaProviderTab"
|
||||
type="segment"
|
||||
animated
|
||||
class="aksrt-tabs aksrt-captcha-provider-tabs"
|
||||
>
|
||||
<n-tab-pane
|
||||
v-for="provider in captchaProviders"
|
||||
:key="provider.key"
|
||||
:name="provider.key"
|
||||
:tab="provider.label"
|
||||
>
|
||||
<component
|
||||
:is="provider.component"
|
||||
:model-value="getCaptchaProviderConfig(provider.key)"
|
||||
@update:model-value="setCaptchaProviderConfig(provider.key, $event)"
|
||||
@clear-secret="markCaptchaSecretForClear(provider.key, $event)"
|
||||
@restore-secret="unmarkCaptchaSecretForClear(provider.key, $event)"
|
||||
/>
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</template>
|
||||
|
||||
<template
|
||||
@@ -132,6 +131,13 @@ const testLoading = ref(false)
|
||||
const testResult = ref(null)
|
||||
const serverSchema = ref(null)
|
||||
const captchaSecretsToClear = ref(new Set())
|
||||
const captchaProviders = [
|
||||
{ key: 'tencent', label: '腾讯云 Captcha', component: TencentConfig },
|
||||
{ key: 'aliyun', label: '阿里云 CAPTCHA2.0', component: AliyunConfig },
|
||||
{ key: 'cloudflare', label: 'Cloudflare Turnstile', component: CloudflareConfig },
|
||||
{ key: 'geetest', label: '极验 Geetest', component: GeetestConfig },
|
||||
]
|
||||
const captchaProviderTab = ref('tencent')
|
||||
|
||||
/** Resolve domain/section from props or route meta (canonicalDomain + section, with legacy fallback) */
|
||||
const resolvedDomain = computed(() => normalizeSchemaDomain(props.domain || route.meta?.canonicalDomain || route.meta?.schemaDomain || resolveDomainFromPath(route.path)))
|
||||
@@ -233,10 +239,11 @@ async function handleSave() {
|
||||
}
|
||||
}
|
||||
|
||||
function handleReload() {
|
||||
async function handleReload() {
|
||||
closeTestModal()
|
||||
captchaSecretsToClear.value = new Set()
|
||||
reload()
|
||||
await reload()
|
||||
syncCaptchaProviderTab()
|
||||
message.info('已放弃更改')
|
||||
}
|
||||
|
||||
@@ -255,6 +262,15 @@ function unmarkCaptchaSecretForClear(provider, field) {
|
||||
captchaSecretsToClear.value = next
|
||||
}
|
||||
|
||||
function setCaptchaProviderConfig(provider, value) {
|
||||
formData.value = { ...formData.value, [provider]: value || {} }
|
||||
}
|
||||
|
||||
function syncCaptchaProviderTab() {
|
||||
const provider = formData.value?.active_provider
|
||||
captchaProviderTab.value = captchaProviders.some(({ key }) => key === provider) ? provider : 'tencent'
|
||||
}
|
||||
|
||||
function getCaptchaProviderConfig(provider) {
|
||||
if (!formData.value || !provider) return {}
|
||||
const cfg = formData.value[provider]
|
||||
@@ -452,6 +468,8 @@ function closeTestModal() {
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => formData.value?.active_provider, syncCaptchaProviderTab)
|
||||
|
||||
watch([resolvedDomain, resolvedSection], ([nextDomain, nextSection], [prevDomain, prevSection]) => {
|
||||
if (nextDomain !== prevDomain || nextSection !== prevSection) {
|
||||
closeTestModal()
|
||||
|
||||
@@ -68,20 +68,13 @@ export const permissionsSchemas = {
|
||||
key: 'providers',
|
||||
title: '服务商配置',
|
||||
icon: 'fa fas fa-server',
|
||||
description: '按当前选择的服务商填写对应密钥。',
|
||||
description: '切换标签页填写各服务商配置;实际启用的服务商由“当前服务商”决定。',
|
||||
dependsOn: { key: 'enabled', value: true },
|
||||
fields: [
|
||||
{
|
||||
key: 'provider_hint',
|
||||
label: '配置说明',
|
||||
type: 'notice',
|
||||
alertType: 'info',
|
||||
alertContent: '下方会渲染当前验证码服务商的专属配置面板。保存仍通过统一 schema contract 进行。',
|
||||
dependsOn: { key: 'enabled', value: true },
|
||||
},
|
||||
{ key: 'tencent', label: '腾讯云配置', type: 'custom', default: { secret_id: '', secret_key: '', app_id: '', app_secret_key: '' }, secretFields: ['secret_key', 'app_secret_key'], dependsOn: { key: 'active_provider', value: 'tencent' } },
|
||||
{ key: 'aliyun', label: '阿里云配置', type: 'custom', default: { region: 'cn', prefix: '', scene_id: '', access_key_id: '', access_key_secret: '' }, secretFields: ['access_key_secret'], dependsOn: { key: 'active_provider', value: 'aliyun' } },
|
||||
{ key: 'cloudflare', label: 'Cloudflare 配置', type: 'custom', default: { site_key: '', secret_key: '', theme: 'auto', size: 'normal' }, secretFields: ['secret_key'], dependsOn: { key: 'active_provider', value: 'cloudflare' } },
|
||||
{ key: 'geetest', label: '极验配置', type: 'custom', default: { captcha_id: '', captcha_key: '', product: 'bind' }, secretFields: ['captcha_key'], dependsOn: { key: 'active_provider', value: 'geetest' } },
|
||||
{ key: 'tencent', label: '腾讯云配置', type: 'custom', default: { secret_id: '', secret_key: '', app_id: '', app_secret_key: '' }, secretFields: ['secret_key', 'app_secret_key'] },
|
||||
{ key: 'aliyun', label: '阿里云配置', type: 'custom', default: { region: 'cn', prefix: '', scene_id: '', access_key_id: '', access_key_secret: '' }, secretFields: ['access_key_secret'] },
|
||||
{ key: 'cloudflare', label: 'Cloudflare 配置', type: 'custom', default: { site_key: '', secret_key: '', theme: 'auto', size: 'normal' }, secretFields: ['secret_key'] },
|
||||
{ key: 'geetest', label: '极验配置', type: 'custom', default: { captcha_id: '', captcha_key: '', product: 'bind' }, secretFields: ['captcha_key'] },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -54,7 +54,7 @@ class SchemaPermissions {
|
||||
['label' => '前台投稿', 'value' => 'front_submit'],
|
||||
]],
|
||||
]],
|
||||
['key' => 'providers', 'title' => '服务商配置', 'fields' => $provider_fields],
|
||||
['key' => 'providers', 'title' => '服务商配置', 'dependsOn' => ['key' => 'enabled', 'value' => true], 'fields' => $provider_fields],
|
||||
],
|
||||
],
|
||||
'attachment' => [
|
||||
|
||||
Reference in New Issue
Block a user