feat(captcha): add provider configuration tabs
This commit is contained in:
1 parent
ff4ec1eb43
commit
0985d0cb0a
4 files changed
+57
-41
No files matched your search
@@ -53,7 +53,7 @@
|
|||||||
:collapsible="!!group.collapsible"
|
:collapsible="!!group.collapsible"
|
||||||
:default-collapsed="resolveGroupCollapsed(group)"
|
: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-for="field in group.fields" :key="field.key">
|
||||||
<template v-if="isFieldVisible(field)">
|
<template v-if="isFieldVisible(field)">
|
||||||
<slot
|
<slot
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
* - Dependency-based field visibility
|
* - Dependency-based field visibility
|
||||||
* - Loading and error states
|
* - Loading and error states
|
||||||
*/
|
*/
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed, useSlots } from 'vue'
|
||||||
import FaIcon from './FaIcon.vue'
|
import FaIcon from './FaIcon.vue'
|
||||||
import AksFieldGroup from './AksFieldGroup.vue'
|
import AksFieldGroup from './AksFieldGroup.vue'
|
||||||
import AksField from './AksField.vue'
|
import AksField from './AksField.vue'
|
||||||
@@ -162,6 +162,7 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['update:formData', 'save', 'reload'])
|
const emit = defineEmits(['update:formData', 'save', 'reload'])
|
||||||
|
const slots = useSlots()
|
||||||
|
|
||||||
const searchQuery = ref('')
|
const searchQuery = ref('')
|
||||||
|
|
||||||
@@ -202,6 +203,10 @@ function matchDependsOn(dependsOn) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasGroupSlot(group) {
|
||||||
|
return !!slots[`group-${group.key}`]
|
||||||
|
}
|
||||||
|
|
||||||
/** Check if a field should be visible based on its dependsOn rule */
|
/** Check if a field should be visible based on its dependsOn rule */
|
||||||
function isFieldVisible(field) {
|
function isFieldVisible(field) {
|
||||||
return matchDependsOn(field.dependsOn)
|
return matchDependsOn(field.dependsOn)
|
||||||
|
|||||||
@@ -17,30 +17,29 @@
|
|||||||
>
|
>
|
||||||
<template
|
<template
|
||||||
v-if="isPermissionsLogin"
|
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)" />
|
<n-tabs
|
||||||
</template>
|
v-model:value="captchaProviderTab"
|
||||||
|
type="segment"
|
||||||
<template
|
animated
|
||||||
v-if="isPermissionsLogin"
|
class="aksrt-tabs aksrt-captcha-provider-tabs"
|
||||||
#field-aliyun="{ value, setValue }"
|
>
|
||||||
>
|
<n-tab-pane
|
||||||
<AliyunConfig :model-value="value || {}" @update:model-value="setValue" @clear-secret="markCaptchaSecretForClear('aliyun', $event)" @restore-secret="unmarkCaptchaSecretForClear('aliyun', $event)" />
|
v-for="provider in captchaProviders"
|
||||||
</template>
|
:key="provider.key"
|
||||||
|
:name="provider.key"
|
||||||
<template
|
:tab="provider.label"
|
||||||
v-if="isPermissionsLogin"
|
>
|
||||||
#field-cloudflare="{ value, setValue }"
|
<component
|
||||||
>
|
:is="provider.component"
|
||||||
<CloudflareConfig :model-value="value || {}" @update:model-value="setValue" @clear-secret="markCaptchaSecretForClear('cloudflare', $event)" @restore-secret="unmarkCaptchaSecretForClear('cloudflare', $event)" />
|
:model-value="getCaptchaProviderConfig(provider.key)"
|
||||||
</template>
|
@update:model-value="setCaptchaProviderConfig(provider.key, $event)"
|
||||||
|
@clear-secret="markCaptchaSecretForClear(provider.key, $event)"
|
||||||
<template
|
@restore-secret="unmarkCaptchaSecretForClear(provider.key, $event)"
|
||||||
v-if="isPermissionsLogin"
|
/>
|
||||||
#field-geetest="{ value, setValue }"
|
</n-tab-pane>
|
||||||
>
|
</n-tabs>
|
||||||
<GeetestConfig :model-value="value || {}" @update:model-value="setValue" @clear-secret="markCaptchaSecretForClear('geetest', $event)" @restore-secret="unmarkCaptchaSecretForClear('geetest', $event)" />
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template
|
<template
|
||||||
@@ -132,6 +131,13 @@ const testLoading = ref(false)
|
|||||||
const testResult = ref(null)
|
const testResult = ref(null)
|
||||||
const serverSchema = ref(null)
|
const serverSchema = ref(null)
|
||||||
const captchaSecretsToClear = ref(new Set())
|
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) */
|
/** 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)))
|
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()
|
closeTestModal()
|
||||||
captchaSecretsToClear.value = new Set()
|
captchaSecretsToClear.value = new Set()
|
||||||
reload()
|
await reload()
|
||||||
|
syncCaptchaProviderTab()
|
||||||
message.info('已放弃更改')
|
message.info('已放弃更改')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,6 +262,15 @@ function unmarkCaptchaSecretForClear(provider, field) {
|
|||||||
captchaSecretsToClear.value = next
|
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) {
|
function getCaptchaProviderConfig(provider) {
|
||||||
if (!formData.value || !provider) return {}
|
if (!formData.value || !provider) return {}
|
||||||
const cfg = formData.value[provider]
|
const cfg = formData.value[provider]
|
||||||
@@ -452,6 +468,8 @@ function closeTestModal() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watch(() => formData.value?.active_provider, syncCaptchaProviderTab)
|
||||||
|
|
||||||
watch([resolvedDomain, resolvedSection], ([nextDomain, nextSection], [prevDomain, prevSection]) => {
|
watch([resolvedDomain, resolvedSection], ([nextDomain, nextSection], [prevDomain, prevSection]) => {
|
||||||
if (nextDomain !== prevDomain || nextSection !== prevSection) {
|
if (nextDomain !== prevDomain || nextSection !== prevSection) {
|
||||||
closeTestModal()
|
closeTestModal()
|
||||||
|
|||||||
@@ -68,20 +68,13 @@ export const permissionsSchemas = {
|
|||||||
key: 'providers',
|
key: 'providers',
|
||||||
title: '服务商配置',
|
title: '服务商配置',
|
||||||
icon: 'fa fas fa-server',
|
icon: 'fa fas fa-server',
|
||||||
description: '按当前选择的服务商填写对应密钥。',
|
description: '切换标签页填写各服务商配置;实际启用的服务商由“当前服务商”决定。',
|
||||||
|
dependsOn: { key: 'enabled', value: true },
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{ key: 'tencent', label: '腾讯云配置', type: 'custom', default: { secret_id: '', secret_key: '', app_id: '', app_secret_key: '' }, secretFields: ['secret_key', 'app_secret_key'] },
|
||||||
key: 'provider_hint',
|
{ key: 'aliyun', label: '阿里云配置', type: 'custom', default: { region: 'cn', prefix: '', scene_id: '', access_key_id: '', access_key_secret: '' }, secretFields: ['access_key_secret'] },
|
||||||
label: '配置说明',
|
{ key: 'cloudflare', label: 'Cloudflare 配置', type: 'custom', default: { site_key: '', secret_key: '', theme: 'auto', size: 'normal' }, secretFields: ['secret_key'] },
|
||||||
type: 'notice',
|
{ key: 'geetest', label: '极验配置', type: 'custom', default: { captcha_id: '', captcha_key: '', product: 'bind' }, secretFields: ['captcha_key'] },
|
||||||
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' } },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class SchemaPermissions {
|
|||||||
['label' => '前台投稿', 'value' => 'front_submit'],
|
['label' => '前台投稿', 'value' => 'front_submit'],
|
||||||
]],
|
]],
|
||||||
]],
|
]],
|
||||||
['key' => 'providers', 'title' => '服务商配置', 'fields' => $provider_fields],
|
['key' => 'providers', 'title' => '服务商配置', 'dependsOn' => ['key' => 'enabled', 'value' => true], 'fields' => $provider_fields],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'attachment' => [
|
'attachment' => [
|
||||||
|
|||||||
Reference in new issue
Block a user