@@ -1,220 +1,52 @@
< template >
< div class = "aksrt-page-container" >
< PageHeader
title = "身份认证"
desc = "统一管理认证申请规则与云服务商凭证;审核、驳回和取消认证仍在 WordPress 用户菜单处理。"
/ >
< PageHeader title = "身份认证" desc = "配置通用认证申请规则;申请审核和用户认证资料管理位于 WordPress 用户菜单。" / >
< n-spin :show = "loading" style = "width: 100%; min-height: 240px" >
< n-grid :cols = "3" :x-gap = "12" :y-gap = "12 " responsive = "screen" item -responsive >
< n-grid-item span = "3 m:1" >
< n-card class = "aksrt-status-card" size = "small" >
< n-statistic label = "身份认证功能" >
< template # default > { { verifyForm . user _verify _enabled ? '已启用' : '已停用' } } < / template >
< / n-statistic >
< n-tag : type = "verifyForm.user_verify_enabled ? 'success' : 'default'" round size = "small" >
{ { verifyForm . user _verify _enabled ? '运行中' : '未运行' } }
< / n-tag >
< / n-card >
< / n-grid-item >
< n-grid-item span = "3 m:1" v-for = "key in providerKeys" :key="key" >
< n -card class = "aksrt-status-card" size = "small" >
< n-statistic : label = "providerLabel(key) + '凭证'" >
< template # default > { { providerState ( key ) . configured ? '已配置' : '未配置' } } < / template >
< / n-statistic >
< n-tag : type = "providerState(key).configured ? 'success' : 'warning'" round size = "small" >
{ { identityForm . provider === key ? '当前服务商' : '备用服务商' } }
< / n-tag >
< / n-card >
< / n-grid-item >
< / n-grid >
< n-card class = "aksrt-section-card" title = "通用认证设置" >
< n-form :model = "verifyForm" label -placement = " left " label -width = " 190 " >
< n-form-item label = "启用身份认证" > < n-switch v -model :value = "verifyForm.user_verify_enabled" / > < / n-form-item >
< n-form-item label = "公开页显示认证徽标" > < n-switch v -model :value = "verifyForm.user_verify_public_display" / > < / n-form-item >
< n-form-item label = "允许用户前台申请" > < n-switch v -model :value = "verifyForm.user_verify_allow_apply" / > < / n-form-item >
< n-form-item label = "云失败转人工" > < n-switch v -model :value = "verifyForm.user_verify_allow_manual_fallback" / > < / n-form-item >
< n-form-item label = "认证会话有效期(分钟)" > < n-input-number v -model :value = "verifyForm.user_verify_session_ttl_minutes" :min = "5" :max = "1440" / > < / n-form-item >
< n-form-item label = "结果轮询间隔(秒)" > < n-input-number v -model :value = "verifyForm.user_verify_poll_interval_seconds" :min = "3" :max = "60" / > < / n-form-item >
< n-form-item label = "个人认证费用" > < n-input v -model :value = "verifyForm.user_verify_personal_fee" placeholder = "0.00" / > < / n-form-item >
< n-form-item label = "企业认证费用" > < n-input v -model :value = "verifyForm.user_verify_enterprise_fee" placeholder = "0.00" / > < / n-form-item >
< n-form-item label = "个人认证徽标文案" >
< n-input v -model :value = "verifyForm.user_verify_personal_badge_text" placeholder = "个人认证" / >
< n-card class = "aksrt-section-card " title = "认证申请设置" >
< n-form :model = "form" label -placement = " left " label -width = " 190 " >
< n-form-item label = "启用身份认证" > < n-switch v -model :value = "form.user_auth_s" / > < / n-form-item >
< n-form-item label = "允许用户前台申请" > < n-switch v -model :value = "form.user_auth_apply_s" :disabled = "!form.user_auth_s" / > < / n-form-item >
< n-form-item v-if = "!form.user_auth_apply_s" label="停用申请说明" >
< n -input v -model :value = "form.user_auth_apply_disable_desc" type = "textarea" :rows = "2" / >
< / n-form-item >
< n-form-item label = "企业认证徽标文案 " >
< n-input v -model :value = "verifyF orm.user_verify_enterprise_badge_text" placeholder = "企业认证 " / >
< n-form-item v-if = "form.user_auth_apply_s" label="申请最低用户等级 " >
< n -input -number v -model :value = "f orm.user_auth_apply_limit_level" :min = "0" :max = "30 " / >
< template # feedback > 设置为 0 时不限制用户等级 。 < / template >
< / n-form-item >
< n-form-item label = "停用申请说明 " >
< n-input v -model :value = "verifyF orm.user_verify_disable _desc" type = "textarea" :rows = "2 " / >
< / n-form-item >
< n-form-item label = "申请介绍" >
< n-input v -model :value = "verifyForm.user_verify_desc" type = "textarea" :rows = "3" / >
< n-form-item v-if = "form.user_auth_apply_s" label="申请介绍 " >
< n -input v -model :value = "f orm.user_auth_apply _desc" type = "textarea" :rows = "3 " / >
< / n-form-item >
< / n-form >
< / n-card >
< n-card class = "aksrt-section-card" title = "云服务商" >
< n-form :model = "identityForm" label -placement = " left " label -width = " 190 " >
< n-form-item label = "当前服务商" >
< n-radio-group v -model :value = "identityForm.provider" >
< n-space >
< n-radio-button value = "aliyun" > 阿里云 < / n-radio-button >
< n-radio-button value = "tencent" > 腾讯云 < / n-radio-button >
< / n-space >
< / n-radio-group >
< / n-form-item >
< n-form-item label = "回调地址" >
< n-input-group >
< n-input :value = "callbackUrlForCurrentProvider" readonly / >
< n-button @click ="copyCallback" > 复制 < / n -button >
< / n-input-group >
< template # feedback > 将此地址填入服务商控制台需要的回调地址配置 。 < / template >
< / n-form-item >
< / n-form >
< / n-card >
< n-tabs v -model :value = "activeTab" type = "segment" animated class = "aksrt-provider-tabs" >
< n-tab-pane v-for = "key in providerKeys" :key="key" :name="key" :tab="providerLabel(key)" >
< n -card class = "aksrt-section-card" >
< template # header >
< n-space align = "center" >
< span > { { providerLabel ( key ) } } 凭证 < / span >
< n-tag : type = "providerState(key).configured ? 'success' : 'warning'" size = "small" round >
{ { providerState ( key ) . configured ? '已配置' : '未配置' } }
< / n-tag >
< / n-space >
< / template >
< n-alert type = "info" :show-icon = "false" style = "margin-bottom: 18px" >
已保存凭证不会回显 。 留空表示保留原值 ; 输入新值并保存会替换对应凭证 。
< / n-alert >
< n-form label -placement = " left " label -width = " 190 " >
< n-form-item v-if = "key === 'aliyun'" label="Region" >
< n -input v -model :value = "verifyForm.user_verify_aliyun_region" placeholder = "cn-hangzhou" / >
< / n-form-item >
< n-form-item v-if = "key === 'aliyun'" label="个人认证 SceneId" >
< n -input v -model :value = "verifyForm.user_verify_aliyun_personal_flow" placeholder = "从阿里云实人认证控制台获取 SceneId" / >
< / n-form-item >
< n-form-item v-if = "key === 'aliyun'" label="个人认证 ProductCode" >
< n -select v -model :value = "verifyForm.user_verify_aliyun_product_code" :options = "aliyunProductOptions" / >
< / n-form-item >
< n-form-item v-if = "key === 'aliyun'" label="企业认证业务标识" >
< n -input v -model :value = "verifyForm.user_verify_aliyun_enterprise_flow" placeholder = "可留空,默认复用个人认证 SceneId" / >
< / n-form-item >
< n-form-item v-if = "key === 'tencent'" label="Region" >
< n -input v -model :value = "verifyForm.user_verify_tencent_region" placeholder = "ap-guangzhou" / >
< / n-form-item >
< n-form-item v-if = "key === 'tencent'" label="个人认证 RuleId" >
< n -input v -model :value = "verifyForm.user_verify_tencent_personal_flow" placeholder = "从腾讯云实名核身控制台获取 RuleId" / >
< / n-form-item >
< n-form-item v-if = "key === 'tencent'" label="企业认证流程标识" >
< n -input v -model :value = "verifyForm.user_verify_tencent_enterprise_flow" placeholder = "可留空,企业四要素后复用个人认证 RuleId" / >
< / n-form-item >
< n-form-item v-for = "field in credentialFields[key]" :key="field.key" :label="field.label" >
< n -input
v -model :value = "credentials[key][field.key]"
type = "password"
show -password -on = " click "
: placeholder = "credentialPlaceholder(key, field.key)"
autocomplete = "new-password"
/ >
< / n-form-item >
< n-form-item label = "显式清除凭证" >
< n-checkbox v -model :checked = "clearCredentials[key]" >
保存时删除 { { providerLabel ( key ) } } 全部凭证
< / n-checkbox >
< template # feedback > 清除后该服务商立即变为未配置 ; 此操作不会因输入框留空而触发 。 < / template >
< / n-form-item >
< n-form-item label = "配置测试" >
< n-button : loading = "testing === key" :disabled = "saving" @click ="testProvider(key)" >
测试 {{ providerLabel ( key ) }} 配置
< / n -button >
< / n-form-item >
< / n-form >
< / n-card >
< / n-tab-pane >
< / n-tabs >
< div class = "aksrt-form-actions" >
< n-button :disabled = "!dirty" @click ="load" > 放弃更改 < / n -button >
< n-button type = "primary" :loading = "saving" @click ="save" > 保存全部配 置 < / n -button >
< n-button : disabled = "!dirty || saving " @click ="load" > 放弃更改 < / n -button >
< n-button type = "primary" :disabled = "!dirty" :loading = "saving" @click ="save" > 保存设 置 < / n -button >
< / div >
< / n-spin >
< / div >
< / template >
< script setup >
import { computed , onMounted , reactive , ref , watch } from 'vue'
import { useDialog , useMessage } from 'naive-ui'
import { computed , onMounted , reactive , ref } from 'vue'
import { useMessage } from 'naive-ui'
import PageHeader from '@/components/PageHeader.vue'
import api from '@/api'
const defaults = {
user _auth _s : true ,
user _auth _apply _s : true ,
user _auth _apply _disable _desc : '由于系统正在维护,暂时无法申请身份认证\n详情请联系管理员' ,
user _auth _apply _limit _level : 0 ,
user _auth _apply _desc : '请填写认证身份、简介和申请说明,提交后由管理员审核。' ,
}
const message = useMessage ( )
const dialog = useDialog ( )
const providerKeys = [ 'aliyun' , 'tencent ']
const aliyunProductOptions = [
{ label : '实人认证(ID_PRO) ' , value : 'ID_PRO' } ,
{ label : '活体人脸验证(PV_FV) ' , value : 'PV_FV' } ,
{ label : '活体检测(LR_FR) ' , value : 'LR_FR' } ,
]
const credentialFields = {
aliyun : [
{ key : 'access_key_id' , label : 'AccessKey ID' } ,
{ key : 'access_key_secret' , label : 'AccessKey Secret' } ,
] ,
tencent : [
{ key : 'secret_id' , label : 'SecretId' } ,
{ key : 'secret_key' , label : 'SecretKey' } ,
] ,
}
const verifyDefaults = {
user _verify _enabled : true ,
user _verify _public _display : true ,
user _verify _allow _apply : true ,
user _verify _allow _manual _fallback : true ,
user _verify _session _ttl _minutes : 30 ,
user _verify _poll _interval _seconds : 5 ,
user _verify _personal _fee : '0.00' ,
user _verify _enterprise _fee : '0.00' ,
user _verify _personal _badge _text : '个人认证' ,
user _verify _enterprise _badge _text : '企业认证' ,
user _verify _disable _desc : '由于系统正在维护,暂时无法申请身份认证\n详情请联系管理员' ,
user _verify _desc : '认证用户会在昵称后显示认证徽章,同时享受多种优先待遇。申请请确保信息属实。' ,
user _verify _aliyun _region : 'cn-hangzhou' ,
user _verify _aliyun _personal _flow : '' ,
user _verify _aliyun _product _code : 'ID_PRO' ,
user _verify _aliyun _enterprise _flow : '' ,
user _verify _tencent _region : 'ap-guangzhou' ,
user _verify _tencent _personal _flow : '' ,
user _verify _tencent _enterprise _flow : '' ,
}
const form = reactive ( { ... defaults } )
const initial = ref ( ' ')
const loading = ref ( true )
const saving = ref ( false )
const testing = ref ( '' )
const activeTab = ref ( 'aliyun' )
const callbackUrls = reactive ( { aliyun : '' , tencent : '' } )
const callbackUrlForCurrentProvider = computed ( ( ) => callbackUrls [ identityForm . provider ] || '' )
const initialSnapshot = ref ( '' )
const states = reactive ( { aliyun : { } , tencent : { } } )
const verifyForm = reactive ( { ... verifyDefaults } )
const identityForm = reactive ( { provider : 'aliyun' } )
const credentials = reactive ( { aliyun : { } , tencent : { } } )
const clearCredentials = reactive ( { aliyun : false , tencent : false } )
const snapshot = computed ( ( ) => JSON . stringify ( form ) )
const dirty = computed ( ( ) => initial . value !== '' && snapshot . value !== initial . value )
const snapshot = computed ( ( ) => JSON . stringify ( { verifyForm , identityForm , credentials , clearCredentials } ) )
const dirty = computed ( ( ) => initialSnapshot . value !== '' && snapshot . value !== initialSnapshot . value )
watch ( ( ) => identityForm . provider , value => { activeTab . value = value } )
function providerLabel ( key ) {
return key === 'aliyun' ? '阿里云' : '腾讯云'
}
function providerState ( key ) {
return states [ key ] || { }
}
function credentialPlaceholder ( provider , field ) {
return providerState ( provider ) . configured _fields ? . [ field ] ? '已配置,留空则保留' : '尚未配置'
}
function extractData ( response ) {
return response ? . data ? ? response ? ? { }
}
@@ -222,123 +54,27 @@ function extractData(response) {
async function load ( ) {
loading . value = true
try {
const [ verifyResponse , identityResponse ] = await Promise . all ( [
api . getSchemaPage ( 'users' , 'verify' ) ,
api . getIdentitySettings ( ) ,
] )
Object . assign ( verifyForm , verifyDefaults , extractData ( verifyResponse ) )
const identity = extractData ( identityResponse )
identityForm . provider = providerKeys . includes ( identity . provider ) ? identity . provider : 'aliyun'
Object . assign ( callbackUrls , identity . callback _url || { } )
providerKeys . forEach ( key => {
states [ key ] = identity . providers ? . [ key ] || { }
credentials [ key ] = { }
clearCredentials [ key ] = false
} )
activeTab . value = identityForm . provider
initialSnapshot . value = snapshot . value
Object . assign ( form , defaults , extractData ( await api . getSchemaPage ( 'users' , 'verify' ) ) )
initial . value = snapshot . value
} catch ( error ) {
message . error ( error . message || '加载身份认证配 置失败' )
message . error ( error . message || '加载身份认证设 置失败' )
} finally {
loading . value = false
}
}
function buildProviderPayload ( key ) {
const replacement = { }
credentialFields [ key ] . forEach ( ( { key : field } ) => {
const value = String ( credentials [ key ] ? . [ field ] || '' ) . trim ( )
if ( value ) replacement [ field ] = value
} )
return {
credentials : replacement ,
clear _credentials : clearCredentials [ key ] ,
}
}
async function persist ( ) {
const [ verifyResponse , identityResponse ] = await Promise . all ( [
api . saveSchemaPage ( 'users' , 'verify' , { ... verifyForm } ) ,
api . saveIdentitySettings ( {
provider : identityForm . provider ,
providers : {
aliyun : buildProviderPayload ( 'aliyun' ) ,
tencent : buildProviderPayload ( 'tencent' ) ,
} ,
} ) ,
] )
const identity = extractData ( identityResponse )
Object . assign ( verifyForm , extractData ( verifyResponse ) )
providerKeys . forEach ( key => {
states [ key ] = identity . providers ? . [ key ] || states [ key ]
credentials [ key ] = { }
clearCredentials [ key ] = false
} )
Object . assign ( callbackUrls , identity . callback _url || callbackUrls )
initialSnapshot . value = snapshot . value
}
async function save ( ) {
const hasClear = providerKeys . some ( key => clearCredentials [ key ] )
const run = async ( ) => {
saving . value = true
try {
await persist ( )
message . success ( '身份认证配置已保存' )
} catch ( error ) {
message . error ( error . message || '保存失败' )
} finally {
saving . value = false
}
}
if ( ! hasClear ) return run ( )
dialog . warning ( {
title : '确认清除凭证' ,
content : '已勾选的服务商凭证将被永久删除,是否继续保存?' ,
positiveText : '清除并保存' ,
negativeText : '取消' ,
onPositiveClick : run ,
} )
}
async function testProvider ( provider ) {
testing . value = provider
saving . value = true
try {
const response = await api . testIdentitySettings ( { provider } )
message . success ( response ? . message || ` ${ providerLabel ( provider ) } 配置测试通过 ` )
Object . assign ( form , defaults , extractData ( await api . saveSchemaPage ( 'users' , 'verify' , { ... form } ) ) )
initial . value = snapshot . value
message . success ( '身份认证设置已保存' )
} catch ( error ) {
message . error ( error . message || ` ${ providerLabel ( provider ) } 配置测试失败 ` )
message . error ( error . message || '保存身份认证设置失败' )
} finally {
testing . value = ''
}
}
async function copyCallback ( ) {
if ( ! callbackUrlForCurrentProvider . value ) return message . warning ( '回调地址暂不可用' )
try {
await navigator . clipboard . writeText ( callbackUrlForCurrentProvider . value )
message . success ( '回调地址已复制' )
} catch {
message . error ( '复制失败,请手动复制' )
saving . value = false
}
}
onMounted ( load )
< / script >
< style scoped >
. aksrt - status - card : deep ( . n - card _ _content ) {
display : flex ;
align - items : flex - end ;
justify - content : space - between ;
gap : 12 px ;
}
. aksrt - section - card { margin - top : 16 px ; }
. aksrt - provider - tabs { margin - top : 16 px ; }
. aksrt - form - actions {
display : flex ;
justify - content : flex - end ;
gap : 12 px ;
margin - top : 20 px ;
}
< / style >