feat(identity): align auth flow with reference theme
This commit is contained in:
1 parent
5a17b399a0
commit
59901b9e10
34 files changed
+753
-3871
No files matched your search
@@ -17,14 +17,6 @@ export const usersApiMethods = {
|
||||
deleteNotification(id) { return this.request(`/notifications/${id}`, { method: 'DELETE' }) },
|
||||
runNotificationCleanup() { return this.request('/notifications/cleanup', { method: 'POST', body: JSON.stringify({}) }) },
|
||||
|
||||
getIdentitySettings() { return this.request('/identity/settings') },
|
||||
saveIdentitySettings(data) {
|
||||
return this.request('/identity/settings', { method: 'POST', body: JSON.stringify(data) })
|
||||
},
|
||||
testIdentitySettings(data = {}) {
|
||||
return this.request('/identity/settings/test', { method: 'POST', body: JSON.stringify(data) })
|
||||
},
|
||||
|
||||
getCreditSettings() { return this.request('/assets/settings') },
|
||||
saveCreditSettings(data) { return this.request('/assets/settings', { method: 'POST', body: JSON.stringify(data) }) },
|
||||
listCreditUsers(params = {}) {
|
||||
|
||||
@@ -60,7 +60,7 @@ export const usersSection = {
|
||||
route: '/users/verify',
|
||||
component: 'UsersVerify',
|
||||
title: '身份认证',
|
||||
desc: '配置认证展示、申请类型与申请门槛;审核、驳回和取消认证统一在 WordPress 用户菜单处理。',
|
||||
desc: '配置通用认证申请规则;申请审核和认证资料管理位于 WordPress 用户菜单。',
|
||||
canonicalDomain: 'users',
|
||||
section: 'verify',
|
||||
}),
|
||||
|
||||
@@ -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="verifyForm.user_verify_enterprise_badge_text" placeholder="企业认证" />
|
||||
<n-form-item v-if="form.user_auth_apply_s" label="申请最低用户等级">
|
||||
<n-input-number v-model:value="form.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="verifyForm.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="form.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: 12px;
|
||||
}
|
||||
.aksrt-section-card { margin-top: 16px; }
|
||||
.aksrt-provider-tabs { margin-top: 16px; }
|
||||
.aksrt-form-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -2,83 +2,51 @@ export const verifySchema = {
|
||||
key: 'users.verify',
|
||||
title: '身份认证',
|
||||
icon: 'fa fas fa-id-card',
|
||||
description: '配置公开认证徽标、申请类型与申请门槛;审核、驳回和取消认证请前往 WordPress 用户 → 身份认证。',
|
||||
description: '设置通用身份认证的申请条件;用户提交资料后由管理员审核。',
|
||||
defaults: {
|
||||
user_verify_enabled: true,
|
||||
user_verify_public_display: true,
|
||||
user_verify_personal_badge_text: '个人认证',
|
||||
user_verify_personal_fee: '0.00',
|
||||
user_verify_personal_free_applications: 0,
|
||||
user_verify_personal_require_real_name: false,
|
||||
user_verify_enterprise_badge_text: '企业认证',
|
||||
user_verify_enterprise_fee: '0.00',
|
||||
user_verify_enterprise_free_applications: 0,
|
||||
user_verify_enterprise_require_real_name: false,
|
||||
user_verify_allow_apply: true,
|
||||
user_verify_disable_desc:
|
||||
'由于系统正在维护,暂时无法申请身份认证\n详情请联系管理员',
|
||||
user_verify_desc:
|
||||
'认证用户会在昵称后显示认证徽章,同时享受多种优先待遇。申请请确保信息属实。',
|
||||
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: '请填写认证身份、简介和申请说明,提交后由管理员审核。',
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'display',
|
||||
title: '公开展示',
|
||||
icon: 'fa fas fa-eye',
|
||||
fields: [
|
||||
{ key: 'user_verify_enabled', label: '启用身份认证', type: 'switch', default: true },
|
||||
{
|
||||
key: 'user_verify_public_display',
|
||||
label: '公开页显示认证徽标',
|
||||
type: 'switch',
|
||||
default: true,
|
||||
dependsOn: { key: 'user_verify_enabled', value: true },
|
||||
},
|
||||
{
|
||||
key: 'user_verify_personal_badge_text', label: '个人认证徽标文案', type: 'input', default: '个人认证',
|
||||
dependsOn: { key: 'user_verify_public_display', value: true },
|
||||
},
|
||||
{
|
||||
key: 'user_verify_enterprise_badge_text', label: '企业认证徽标文案', type: 'input', default: '企业认证',
|
||||
dependsOn: { key: 'user_verify_public_display', value: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'workflow', title: '认证方式', icon: 'fa fas fa-tasks',
|
||||
fields: [{ type: 'alert', content: '认证方式固定为个人认证和企业认证,申请人不可自行新增或修改类型。' }],
|
||||
},
|
||||
{
|
||||
key: 'apply',
|
||||
title: '申请设置',
|
||||
title: '认证申请',
|
||||
icon: 'fa fas fa-edit',
|
||||
fields: [
|
||||
{ key: 'user_auth_s', label: '启用身份认证', type: 'switch', default: true },
|
||||
{
|
||||
key: 'user_verify_allow_apply',
|
||||
key: 'user_auth_apply_s',
|
||||
label: '允许用户前台申请',
|
||||
type: 'switch',
|
||||
default: true,
|
||||
dependsOn: { key: 'user_verify_enabled', value: true },
|
||||
dependsOn: { key: 'user_auth_s', value: true },
|
||||
},
|
||||
{
|
||||
key: 'user_verify_disable_desc',
|
||||
key: 'user_auth_apply_disable_desc',
|
||||
label: '停用申请说明',
|
||||
type: 'textarea',
|
||||
default: '由于系统正在维护,暂时无法申请身份认证\n详情请联系管理员',
|
||||
dependsOn: { key: 'user_verify_allow_apply', value: false },
|
||||
dependsOn: { key: 'user_auth_apply_s', value: false },
|
||||
},
|
||||
{ key: 'user_verify_personal_fee', label: '个人认证每次扣余额(元)', type: 'input', default: '0.00', help: '仅接受最多两位小数的十进制字符串。', dependsOn: { key: 'user_verify_allow_apply', value: true } },
|
||||
{ key: 'user_verify_personal_free_applications', label: '个人认证赠送初始次数', type: 'number', default: 0, min: 0, max: 9999, help: '每位用户前 N 次个人认证申请免费,之后按单次费用扣余额。', dependsOn: { key: 'user_verify_allow_apply', value: true } },
|
||||
{ key: 'user_verify_personal_require_real_name', label: '个人认证强制填写真实姓名', type: 'switch', default: false, dependsOn: { key: 'user_verify_allow_apply', value: true } },
|
||||
{ key: 'user_verify_enterprise_fee', label: '企业认证每次扣余额(元)', type: 'input', default: '0.00', help: '仅接受最多两位小数的十进制字符串。', dependsOn: { key: 'user_verify_allow_apply', value: true } },
|
||||
{ key: 'user_verify_enterprise_free_applications', label: '企业认证赠送初始次数', type: 'number', default: 0, min: 0, max: 9999, help: '每位用户前 N 次企业认证申请免费,之后按单次费用扣余额。', dependsOn: { key: 'user_verify_allow_apply', value: true } },
|
||||
{ key: 'user_verify_enterprise_require_real_name', label: '企业认证强制填写真实姓名', type: 'switch', default: false, dependsOn: { key: 'user_verify_allow_apply', value: true } },
|
||||
{
|
||||
key: 'user_verify_desc',
|
||||
key: 'user_auth_apply_limit_level',
|
||||
label: '申请最低用户等级',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
min: 0,
|
||||
max: 30,
|
||||
help: '设置为 0 时不限制用户等级。',
|
||||
dependsOn: { key: 'user_auth_apply_s', value: true },
|
||||
},
|
||||
{
|
||||
key: 'user_auth_apply_desc',
|
||||
label: '申请介绍',
|
||||
type: 'textarea',
|
||||
default: '认证用户会在昵称后显示认证徽章,同时享受多种优先待遇。申请请确保信息属实。',
|
||||
dependsOn: { key: 'user_verify_allow_apply', value: true },
|
||||
default: '请填写认证身份、简介和申请说明,提交后由管理员审核。',
|
||||
dependsOn: { key: 'user_auth_apply_s', value: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -528,20 +528,11 @@
|
||||
|
||||
.uc-auth-form { display: grid; gap: 17px; }
|
||||
.uc-auth-field { display: grid; gap: 7px; color: var(--color-ink-2); font-size: 13px; font-weight: 600; }
|
||||
.uc-auth-enterprise-fields { display: grid; gap: 17px; }
|
||||
.uc-auth-enterprise-fields[hidden] { display: none; }
|
||||
.uc-auth-form .form-control { width: 100%; min-height: 42px; padding: 9px 11px; border: 1px solid var(--color-line-soft, rgba(15, 23, 42, 0.1)); border-radius: 9px; background: var(--surface-1); color: var(--color-ink); font: inherit; font-weight: 400; box-sizing: border-box; }
|
||||
.uc-auth-form textarea.form-control { resize: vertical; }
|
||||
.uc-auth-consent { display: flex; align-items: flex-start; gap: 8px; color: var(--color-ink-3); font-size: 13px; font-weight: 400; line-height: 1.6; }
|
||||
.uc-auth-consent input { flex: 0 0 auto; margin-top: 3px; }
|
||||
.uc-auth-submit { display: inline-flex; align-items: center; justify-content: center; gap: 7px; min-height: 44px; }
|
||||
.uc-auth-form__message { min-height: 18px; color: var(--color-error, #dc2626); font-size: 12px; text-align: center; }
|
||||
|
||||
.uc-auth-pending { text-align: center; padding: 4px 0 18px; }
|
||||
.uc-auth-pending > span { display: grid; place-items: center; width: 58px; height: 58px; margin: 0 auto 12px; border-radius: 18px; background: rgba(245, 158, 11, 0.14); color: #a66e0b; font-size: 24px; }
|
||||
.uc-auth-pending h3 { margin: 0 0 7px; }
|
||||
.uc-auth-pending p { margin: 0; color: var(--color-ink-4); font-size: 13px; }
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.uc-auth-modal__dialog { position: fixed; inset: auto 0 0; width: 100%; max-width: none; max-height: 88vh; margin: 0; border-radius: 18px 18px 0 0; }
|
||||
}
|
||||
@@ -1,364 +1,113 @@
|
||||
(function (window, document) {
|
||||
'use strict';
|
||||
|
||||
function parseConfig(root) {
|
||||
function configFor(root) {
|
||||
try { return JSON.parse(root.getAttribute('data-identity-config') || '{}'); } catch (_) { return {}; }
|
||||
}
|
||||
|
||||
function uuid() {
|
||||
if (window.crypto && crypto.randomUUID) return crypto.randomUUID();
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
var r = Math.random() * 16 | 0;
|
||||
return (c === 'x' ? r : (r & 3 | 8)).toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
function request(path, config, options) {
|
||||
options = Object.assign({}, options || {});
|
||||
options.credentials = 'same-origin';
|
||||
options.headers = Object.assign({ 'X-WP-Nonce': config.nonce || '' }, options.headers || {});
|
||||
return window.AksrtRequest.request(path, options).then(function (response) {
|
||||
return response.data !== undefined ? response.data : response.json;
|
||||
});
|
||||
function parseResponse(response) {
|
||||
var payload = response && (response.data !== undefined ? response.data : response.json);
|
||||
return payload && payload.data !== undefined ? payload.data : payload;
|
||||
}
|
||||
|
||||
function errorMessage(error, fallback) {
|
||||
if (error && error.json) {
|
||||
return error.json.message || (error.json.data && error.json.data.message) || fallback;
|
||||
}
|
||||
if (error && error.json) return error.json.message || fallback;
|
||||
return error && error.message ? error.message : fallback;
|
||||
}
|
||||
|
||||
function loadScript(src) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var existing = document.querySelector('script[src="' + src + '"]');
|
||||
if (existing) {
|
||||
if (existing.dataset.loaded === '1') return resolve();
|
||||
existing.addEventListener('load', resolve, { once: true });
|
||||
existing.addEventListener('error', reject, { once: true });
|
||||
return;
|
||||
}
|
||||
var script = document.createElement('script');
|
||||
script.src = src;
|
||||
script.async = true;
|
||||
script.onload = function () { script.dataset.loaded = '1'; resolve(); };
|
||||
script.onerror = reject;
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
function aliyunMetaInfo(config) {
|
||||
if (config.provider !== 'aliyun') return Promise.resolve('');
|
||||
return loadScript('https://o.alicdn.com/yd-cloudauth/cloudauth-cdn/jsvm_all.js').then(function () {
|
||||
if (typeof window.getMetaInfo !== 'function') throw new Error('阿里云 MetaInfo 组件未加载成功');
|
||||
var meta = window.getMetaInfo();
|
||||
return typeof meta === 'string' ? meta : JSON.stringify(meta || {});
|
||||
});
|
||||
}
|
||||
|
||||
function init(root) {
|
||||
if (!root || root.dataset.identityBound === '1') return;
|
||||
root.dataset.identityBound = '1';
|
||||
|
||||
var config = parseConfig(root);
|
||||
var config = configFor(root);
|
||||
var template = root.querySelector('[data-auth-modal-template]');
|
||||
var runtime = window.AksrtModalRuntime;
|
||||
if (!template) return;
|
||||
|
||||
var activeShell = null;
|
||||
var currentUuid = config.currentUuid || '';
|
||||
var currentStage = config.currentStage || '';
|
||||
var pollTimer = null;
|
||||
var pollBackoff = 0;
|
||||
var currentController = null;
|
||||
var refreshController = null;
|
||||
var refreshTimeout = null;
|
||||
var refreshInFlight = false;
|
||||
var openSequence = 0;
|
||||
var pendingCreateKey = '';
|
||||
var pendingCreateSignature = '';
|
||||
var pendingRefreshKey = '';
|
||||
var pendingManualKey = '';
|
||||
var pendingManualSignature = '';
|
||||
|
||||
function isOpen(shell) {
|
||||
return !!shell && shell.classList.contains('is-open') && document.contains(shell);
|
||||
}
|
||||
|
||||
function stopPolling() {
|
||||
if (pollTimer) window.clearTimeout(pollTimer);
|
||||
pollTimer = null;
|
||||
}
|
||||
|
||||
function abortCurrent() {
|
||||
if (currentController) {
|
||||
try { currentController.abort(); } catch (_) {}
|
||||
}
|
||||
currentController = null;
|
||||
}
|
||||
|
||||
function abortRefresh() {
|
||||
if (refreshTimeout) window.clearTimeout(refreshTimeout);
|
||||
refreshTimeout = null;
|
||||
if (refreshController) {
|
||||
try { refreshController.abort(); } catch (_) {}
|
||||
}
|
||||
refreshController = null;
|
||||
refreshInFlight = false;
|
||||
}
|
||||
|
||||
function setMessage(shell, message) {
|
||||
var node = shell && shell.querySelector('[data-auth-status-message]');
|
||||
if (node) node.textContent = message || '';
|
||||
}
|
||||
|
||||
function updateSummary(data) {
|
||||
if (!data || data.status !== 'verified') return;
|
||||
var trigger = root.querySelector('[data-auth-modal-open]');
|
||||
if (trigger) {
|
||||
trigger.disabled = true;
|
||||
trigger.textContent = '已认证';
|
||||
}
|
||||
runtime.invalidate(['identity']);
|
||||
document.dispatchEvent(new CustomEvent('aksrt:identity:changed', { detail: { status: data.status } }));
|
||||
}
|
||||
|
||||
function schedulePoll(delay) {
|
||||
stopPolling();
|
||||
if (!currentUuid || !isOpen(activeShell) || document.visibilityState !== 'visible') return;
|
||||
var base = Math.max(5, Number(delay || config.pollInterval || 5));
|
||||
var seconds = Math.min(60, base * Math.pow(2, Math.min(4, pollBackoff)));
|
||||
pollTimer = window.setTimeout(function () {
|
||||
pollTimer = null;
|
||||
refresh(false);
|
||||
}, seconds * 1000);
|
||||
}
|
||||
|
||||
function render(data, shell) {
|
||||
if (!shell || shell !== activeShell || !isOpen(shell)) return;
|
||||
data = data || {};
|
||||
var previousStage = currentStage;
|
||||
currentUuid = data.uuid || currentUuid;
|
||||
currentStage = data.stage || currentStage;
|
||||
if (data.stage && data.stage !== previousStage) runtime.invalidate(['identity']);
|
||||
var lock = ['created', 'provider_pending', 'provider_processing', 'manual_pending'].indexOf(currentStage) !== -1;
|
||||
var retryable = data.next_action === 'manual_or_retry';
|
||||
var statusBox = shell.querySelector('[data-auth-status]');
|
||||
var createForm = shell.querySelector('[data-auth-create]');
|
||||
var stageNode = shell.querySelector('[data-auth-stage]');
|
||||
var messageNode = shell.querySelector('[data-auth-status-message]');
|
||||
var continueLink = shell.querySelector('[data-auth-continue]');
|
||||
var manualButton = shell.querySelector('[data-auth-manual-open]');
|
||||
|
||||
if (statusBox) statusBox.hidden = !data.uuid && !retryable;
|
||||
if (createForm) createForm.hidden = lock || data.status === 'verified';
|
||||
if (stageNode) stageNode.textContent = data.stage || data.status || '准备认证';
|
||||
if (messageNode) messageNode.textContent = data.message || '认证结果以站点服务端主动查询为准。';
|
||||
if (continueLink) {
|
||||
var canContinue = data.next_action === 'poll' && data.redirect_url;
|
||||
continueLink.hidden = !canContinue;
|
||||
if (canContinue) continueLink.href = data.redirect_url;
|
||||
}
|
||||
if (manualButton) manualButton.hidden = !retryable;
|
||||
|
||||
if (data.status === 'verified' || data.next_action !== 'poll') {
|
||||
pollBackoff = 0;
|
||||
stopPolling();
|
||||
} else if (isOpen(shell) && document.visibilityState === 'visible') {
|
||||
schedulePoll(data.poll_interval || config.pollInterval);
|
||||
}
|
||||
updateSummary(data);
|
||||
}
|
||||
|
||||
function refresh(explicit) {
|
||||
if (!currentUuid || !isOpen(activeShell) || document.visibilityState !== 'visible' || refreshInFlight) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
stopPolling();
|
||||
abortRefresh();
|
||||
refreshInFlight = true;
|
||||
refreshController = window.AbortController ? new window.AbortController() : null;
|
||||
var timedOut = false;
|
||||
var shell = activeShell;
|
||||
var timeoutMs = Math.max(5000, Number(config.refreshTimeout || 15000));
|
||||
if (!pendingRefreshKey) pendingRefreshKey = uuid();
|
||||
refreshTimeout = window.setTimeout(function () {
|
||||
timedOut = true;
|
||||
if (refreshController) {
|
||||
try { refreshController.abort(); } catch (_) {}
|
||||
}
|
||||
}, timeoutMs);
|
||||
|
||||
return request(config.restBase + '/' + encodeURIComponent(currentUuid) + '/refresh', config, {
|
||||
method: 'POST',
|
||||
headers: { 'Idempotency-Key': pendingRefreshKey },
|
||||
signal: refreshController ? refreshController.signal : undefined,
|
||||
}).then(function (data) {
|
||||
pendingRefreshKey = '';
|
||||
if (shell !== activeShell || !isOpen(shell)) return data;
|
||||
pollBackoff = explicit ? 0 : Math.min(4, pollBackoff + 1);
|
||||
render(data, shell);
|
||||
return data;
|
||||
}).catch(function (error) {
|
||||
var aborted = error && (error.name === 'AbortError' || error.code === 'ABORT_ERR');
|
||||
if (shell !== activeShell || !isOpen(shell) || (aborted && !timedOut)) return null;
|
||||
setMessage(shell, timedOut ? '云端刷新超时,请稍后重试。' : errorMessage(error, '刷新失败,请稍后重试。'));
|
||||
pollBackoff = Math.min(4, pollBackoff + 1);
|
||||
schedulePoll(config.pollInterval || 5);
|
||||
return null;
|
||||
}).finally(function () {
|
||||
if (refreshTimeout) window.clearTimeout(refreshTimeout);
|
||||
refreshTimeout = null;
|
||||
refreshController = null;
|
||||
refreshInFlight = false;
|
||||
});
|
||||
}
|
||||
|
||||
function loadCurrent(shell, sequence) {
|
||||
abortCurrent();
|
||||
currentController = window.AbortController ? new window.AbortController() : null;
|
||||
var controller = currentController;
|
||||
return request(config.restBase + '/current', config, {
|
||||
method: 'GET',
|
||||
signal: controller ? controller.signal : undefined,
|
||||
}).then(function (snapshot) {
|
||||
if (sequence !== openSequence || shell !== activeShell || !isOpen(shell)) return;
|
||||
var data = snapshot && (snapshot.current || snapshot.active || snapshot.latest);
|
||||
if (!data) {
|
||||
currentUuid = '';
|
||||
currentStage = '';
|
||||
var statusBox = shell.querySelector('[data-auth-status]');
|
||||
var createForm = shell.querySelector('[data-auth-create]');
|
||||
if (statusBox) statusBox.hidden = true;
|
||||
if (createForm) createForm.hidden = false;
|
||||
stopPolling();
|
||||
return;
|
||||
}
|
||||
render(data, shell);
|
||||
}).catch(function (error) {
|
||||
var aborted = error && (error.name === 'AbortError' || error.code === 'ABORT_ERR');
|
||||
if (aborted || sequence !== openSequence || shell !== activeShell || !isOpen(shell)) return;
|
||||
setMessage(shell, errorMessage(error, '状态加载失败,请稍后重试。'));
|
||||
}).finally(function () {
|
||||
if (currentController === controller) currentController = null;
|
||||
});
|
||||
}
|
||||
|
||||
function bindForm(shell) {
|
||||
var createForm = shell.querySelector('[data-auth-create]');
|
||||
var manualForm = shell.querySelector('[data-auth-manual]');
|
||||
var statusBox = shell.querySelector('[data-auth-status]');
|
||||
var refreshButton = shell.querySelector('[data-auth-refresh]');
|
||||
var manualButton = shell.querySelector('[data-auth-manual-open]');
|
||||
var typeSelect = createForm && createForm.querySelector('[name="type"]');
|
||||
var enterprise = createForm && createForm.querySelector('[data-enterprise-fields]');
|
||||
var personLabel = createForm && createForm.querySelector('[data-auth-person-label]');
|
||||
var idLabel = createForm && createForm.querySelector('[data-auth-id-label]');
|
||||
|
||||
function syncEnterprise() {
|
||||
var enabled = typeSelect && typeSelect.value === 'enterprise';
|
||||
if (enterprise) enterprise.hidden = !enabled;
|
||||
if (enterprise) enterprise.querySelectorAll('input').forEach(function (input) { input.required = enabled; });
|
||||
if (personLabel) personLabel.textContent = enabled ? '法定代表人姓名' : '真实姓名';
|
||||
if (idLabel) idLabel.textContent = enabled ? '法定代表人身份证号' : '身份证号码';
|
||||
}
|
||||
|
||||
if (typeSelect) {
|
||||
typeSelect.addEventListener('change', syncEnterprise);
|
||||
syncEnterprise();
|
||||
}
|
||||
if (refreshButton) refreshButton.addEventListener('click', function () { refresh(true); });
|
||||
if (manualButton) manualButton.addEventListener('click', function () { if (manualForm) manualForm.hidden = false; });
|
||||
|
||||
if (createForm) createForm.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
var button = createForm.querySelector('[type="submit"]');
|
||||
var message = createForm.querySelector('[data-auth-message]');
|
||||
var form = new FormData(createForm);
|
||||
var payload = {};
|
||||
form.forEach(function (value, key) { payload[key] = value; });
|
||||
payload.consent = payload.consent === '1';
|
||||
var signature = JSON.stringify(payload);
|
||||
if (!pendingCreateKey || pendingCreateSignature !== signature) {
|
||||
pendingCreateKey = uuid();
|
||||
pendingCreateSignature = signature;
|
||||
}
|
||||
if (button) button.disabled = true;
|
||||
if (message) message.textContent = '';
|
||||
aliyunMetaInfo(config).then(function (metaInfo) {
|
||||
if (metaInfo) payload.meta_info = metaInfo;
|
||||
return request(config.restBase, config, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'Idempotency-Key': pendingCreateKey },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}).then(function (data) {
|
||||
pendingCreateKey = '';
|
||||
pendingCreateSignature = '';
|
||||
render(data, shell);
|
||||
}).catch(function (error) {
|
||||
if (message) message.textContent = errorMessage(error, '创建认证会话失败,请重试。');
|
||||
}).finally(function () {
|
||||
if (button) button.disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
if (manualForm) manualForm.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
if (!currentUuid) return;
|
||||
var files = manualForm.querySelector('[name="evidence[]"]');
|
||||
if (files && files.files.length > 3) {
|
||||
setMessage(shell, '证明材料最多上传 3 张');
|
||||
return;
|
||||
}
|
||||
var button = manualForm.querySelector('[type="submit"]');
|
||||
var body = new FormData();
|
||||
Array.prototype.forEach.call((files && files.files) || [], function (file) { body.append('evidence[]', file); });
|
||||
var signature = Array.prototype.map.call((files && files.files) || [], function (file) {
|
||||
return [file.name, file.size, file.lastModified].join(':');
|
||||
}).join('|');
|
||||
if (!pendingManualKey || pendingManualSignature !== signature) {
|
||||
pendingManualKey = uuid();
|
||||
pendingManualSignature = signature;
|
||||
}
|
||||
if (button) button.disabled = true;
|
||||
request(config.restBase + '/' + encodeURIComponent(currentUuid) + '/manual', config, {
|
||||
method: 'POST',
|
||||
headers: { 'Idempotency-Key': pendingManualKey },
|
||||
body: body,
|
||||
}).then(function (data) {
|
||||
pendingManualKey = '';
|
||||
pendingManualSignature = '';
|
||||
manualForm.hidden = true;
|
||||
render(data, shell);
|
||||
}).catch(function (error) {
|
||||
setMessage(shell, errorMessage(error, '人工审核提交失败,请重试。'));
|
||||
}).finally(function () {
|
||||
if (button) button.disabled = false;
|
||||
});
|
||||
});
|
||||
|
||||
if (statusBox && statusBox.hidden && createForm) createForm.hidden = false;
|
||||
}
|
||||
if (!template || !runtime || !window.AksrtRequest) return;
|
||||
|
||||
function open() {
|
||||
stopPolling();
|
||||
abortCurrent();
|
||||
abortRefresh();
|
||||
openSequence += 1;
|
||||
var sequence = openSequence;
|
||||
var shell = runtime.open({
|
||||
runtime.open({
|
||||
html: template.innerHTML,
|
||||
kind: 'identity',
|
||||
className: 'aksrt-identity-modal-shell',
|
||||
mobileBottom: true,
|
||||
onContent: function (_, session) {
|
||||
activeShell = session.shell;
|
||||
bindForm(session.shell);
|
||||
var shell = session && session.shell;
|
||||
var form = shell && shell.querySelector('[data-auth-create]');
|
||||
if (!form) return;
|
||||
form.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
var input = form.querySelector('[name="evidence[]"]');
|
||||
var message = form.querySelector('[data-auth-message]');
|
||||
var button = form.querySelector('[type="submit"]');
|
||||
var files = input ? Array.prototype.slice.call(input.files || []) : [];
|
||||
if (files.length > 3) {
|
||||
if (message) message.textContent = '证明材料最多上传 3 张';
|
||||
return;
|
||||
}
|
||||
if (files.some(function (file) { return file.size > 2 * 1024 * 1024; })) {
|
||||
if (message) message.textContent = '单张证明材料不能超过 2MB';
|
||||
return;
|
||||
}
|
||||
|
||||
if (button) button.disabled = true;
|
||||
if (message) message.textContent = '';
|
||||
window.AksrtRequest.request(config.restBase, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'X-WP-Nonce': config.nonce || '' },
|
||||
body: new FormData(form),
|
||||
}).then(function (response) {
|
||||
var result = parseResponse(response);
|
||||
if (!result || result.success === false) throw new Error((result && result.message) || '申请提交失败,请稍后重试');
|
||||
if (message) message.textContent = '申请已提交,请耐心等待管理员审核。';
|
||||
var summary = root.querySelector('[data-auth-summary]');
|
||||
if (summary) {
|
||||
summary.replaceChildren();
|
||||
var pending = document.createElement('div');
|
||||
pending.className = 'c-red mb10';
|
||||
pending.textContent = '您的申请正在审核中,请耐心等待';
|
||||
var name = document.createElement('div');
|
||||
name.textContent = result.name || '';
|
||||
summary.appendChild(pending);
|
||||
summary.appendChild(name);
|
||||
if (result.description) {
|
||||
var description = document.createElement('div');
|
||||
description.className = 'muted-2-color mt10';
|
||||
description.textContent = result.description;
|
||||
summary.appendChild(description);
|
||||
}
|
||||
if (result.message) {
|
||||
var applicationMessage = document.createElement('div');
|
||||
applicationMessage.className = 'muted-2-color mt10';
|
||||
applicationMessage.textContent = '申请说明:' + result.message;
|
||||
summary.appendChild(applicationMessage);
|
||||
}
|
||||
(Array.isArray(result.evidence) ? result.evidence : []).forEach(function (item) {
|
||||
if (!item || !item.url || !item.thumbnail_url) return;
|
||||
var link = document.createElement('a');
|
||||
link.href = item.url;
|
||||
link.target = '_blank';
|
||||
link.rel = 'noopener noreferrer';
|
||||
var image = document.createElement('img');
|
||||
image.src = item.thumbnail_url;
|
||||
image.alt = '身份认证证明材料';
|
||||
image.width = 64;
|
||||
image.height = 64;
|
||||
image.style.objectFit = 'cover';
|
||||
image.style.borderRadius = '4px';
|
||||
link.appendChild(image);
|
||||
summary.appendChild(link);
|
||||
});
|
||||
}
|
||||
if (runtime.getActive && runtime.getActive() === shell) runtime.close('submit');
|
||||
}).catch(function (error) {
|
||||
if (message) message.textContent = errorMessage(error, '申请提交失败,请稍后重试');
|
||||
}).finally(function () {
|
||||
if (button) button.disabled = false;
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
activeShell = shell;
|
||||
loadCurrent(shell, sequence);
|
||||
}
|
||||
|
||||
root.querySelectorAll('[data-auth-modal-open]').forEach(function (button) {
|
||||
@@ -367,27 +116,6 @@
|
||||
open();
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('visibilitychange', function () {
|
||||
if (!isOpen(activeShell)) return;
|
||||
if (document.visibilityState !== 'visible') {
|
||||
stopPolling();
|
||||
abortCurrent();
|
||||
abortRefresh();
|
||||
return;
|
||||
}
|
||||
if (currentUuid && ['provider_pending', 'provider_processing'].indexOf(currentStage) !== -1) {
|
||||
schedulePoll(config.pollInterval || 5);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('aksrt:modal:close', function (event) {
|
||||
if (!activeShell || !event.detail || event.detail.shell !== activeShell) return;
|
||||
stopPolling();
|
||||
abortCurrent();
|
||||
abortRefresh();
|
||||
activeShell = null;
|
||||
});
|
||||
}
|
||||
|
||||
window.AKSRTUserCenterAuth = { init: init };
|
||||
|
||||
Reference in new issue
Block a user