feat: 接入腾讯云与阿里云实人认证

This commit is contained in:
2026-08-01 13:21:21 +08:00
parent 92e445dbd4
commit 4de67311cc
37 changed files with 2410 additions and 1158 deletions
+17 -6
View File
@@ -34,6 +34,11 @@
<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-number v-model:value="verifyForm.user_verify_personal_fee" :min="0" :step="0.01" /></n-form-item>
<n-form-item label="企业认证费用"><n-input-number v-model:value="verifyForm.user_verify_enterprise_fee" :min="0" :step="0.01" /></n-form-item>
<n-form-item label="个人认证徽标文案">
<n-input v-model:value="verifyForm.user_verify_personal_badge_text" placeholder="个人认证" />
</n-form-item>
@@ -61,7 +66,7 @@
</n-form-item>
<n-form-item label="回调地址">
<n-input-group>
<n-input :value="callbackUrl" readonly />
<n-input :value="callbackUrlForCurrentProvider" readonly />
<n-button @click="copyCallback">复制</n-button>
</n-input-group>
<template #feedback>将此地址填入服务商控制台需要的回调地址配置。</template>
@@ -140,6 +145,11 @@ 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,
user_verify_enterprise_fee: 0,
user_verify_personal_badge_text: '个人认证',
user_verify_enterprise_badge_text: '企业认证',
user_verify_disable_desc: '由于系统正在维护,暂时无法申请身份认证\n详情请联系管理员',
@@ -150,7 +160,8 @@ const loading = ref(true)
const saving = ref(false)
const testing = ref('')
const activeTab = ref('aliyun')
const callbackUrl = ref('')
const callbackUrls = reactive({ aliyun: '', tencent: '' })
const callbackUrlForCurrentProvider = computed(() => callbackUrls[identityForm.provider] || '')
const initialSnapshot = ref('')
const states = reactive({ aliyun: {}, tencent: {} })
const verifyForm = reactive({ ...verifyDefaults })
@@ -185,7 +196,7 @@ async function load() {
Object.assign(verifyForm, verifyDefaults, extractData(verifyResponse))
const identity = extractData(identityResponse)
identityForm.provider = providerKeys.includes(identity.provider) ? identity.provider : 'aliyun'
callbackUrl.value = identity.callback_url || ''
Object.assign(callbackUrls, identity.callback_url || {})
providerKeys.forEach(key => {
states[key] = identity.providers?.[key] || {}
credentials[key] = {}
@@ -230,7 +241,7 @@ async function persist() {
credentials[key] = {}
clearCredentials[key] = false
})
callbackUrl.value = identity.callback_url || callbackUrl.value
Object.assign(callbackUrls, identity.callback_url || callbackUrls)
initialSnapshot.value = snapshot.value
}
@@ -270,9 +281,9 @@ async function testProvider(provider) {
}
async function copyCallback() {
if (!callbackUrl.value) return message.warning('回调地址暂不可用')
if (!callbackUrlForCurrentProvider.value) return message.warning('回调地址暂不可用')
try {
await navigator.clipboard.writeText(callbackUrl.value)
await navigator.clipboard.writeText(callbackUrlForCurrentProvider.value)
message.success('回调地址已复制')
} catch {
message.error('复制失败,请手动复制')
+133
View File
@@ -0,0 +1,133 @@
(function () {
'use strict';
function parseConfig(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(url, config, options) {
options = options || {};
options.credentials = 'same-origin';
options.headers = Object.assign({ 'X-WP-Nonce': config.nonce || '' }, options.headers || {});
return fetch(url, options).then(function (response) {
return response.json().then(function (payload) {
if (!response.ok || !payload || payload.success === false) {
throw new Error((payload && payload.message) || '请求失败');
}
return payload.data;
});
});
}
function init(root) {
if (!root || root.dataset.identityBound === '1') return;
root.dataset.identityBound = '1';
var config = parseConfig(root);
var modal = root.querySelector('#uc-auth-modal');
if (!modal) return;
var currentUuid = config.currentUuid || '';
var pollTimer = null;
var shouldPoll = false;
function open() { modal.classList.add('is-open'); modal.setAttribute('aria-hidden', 'false'); document.body.style.overflow = 'hidden'; }
function close() { modal.classList.remove('is-open'); modal.setAttribute('aria-hidden', 'true'); document.body.style.overflow = ''; }
root.querySelectorAll('[data-auth-modal-open]').forEach(function (button) { button.addEventListener('click', open); });
modal.querySelectorAll('[data-auth-modal-close]').forEach(function (button) { button.addEventListener('click', close); });
var statusBox = modal.querySelector('[data-auth-status]');
var createForm = modal.querySelector('[data-auth-create]');
var manualForm = modal.querySelector('[data-auth-manual]');
var stageNode = modal.querySelector('[data-auth-stage]');
var messageNode = modal.querySelector('[data-auth-status-message]');
var continueLink = modal.querySelector('[data-auth-continue]');
var refreshButton = modal.querySelector('[data-auth-refresh]');
var manualButton = modal.querySelector('[data-auth-manual-open]');
function render(data) {
if (!data) return;
currentUuid = data.uuid || currentUuid;
if (statusBox) statusBox.hidden = false;
if (createForm) createForm.hidden = true;
if (stageNode) stageNode.textContent = data.stage || data.status || '认证处理中';
if (messageNode) messageNode.textContent = data.message || '认证结果以站点服务端主动查询为准。';
if (continueLink) {
continueLink.hidden = !data.redirect_url;
if (data.redirect_url) continueLink.href = data.redirect_url;
}
if (manualButton) manualButton.hidden = data.next_action !== 'manual_or_retry';
shouldPoll = data.next_action === 'poll';
if (data.status === 'verified') {
shouldPoll = false;
stopPolling();
window.setTimeout(function () { window.location.reload(); }, 500);
} else if (shouldPoll) {
schedulePoll(data.poll_interval || 5);
} else {
stopPolling();
}
}
function refresh() {
if (!currentUuid) return Promise.resolve();
return request(config.restBase + '/' + encodeURIComponent(currentUuid) + '/refresh', config, { method: 'POST' }).then(render);
}
function schedulePoll(seconds) { stopPolling(); shouldPoll = true; pollTimer = window.setTimeout(function () { refresh().catch(function () {}).finally(function () { if (shouldPoll && currentUuid) schedulePoll(seconds); }); }, Math.max(3, seconds) * 1000); }
function stopPolling() { shouldPoll = false; if (pollTimer) window.clearTimeout(pollTimer); pollTimer = null; }
if (refreshButton) refreshButton.addEventListener('click', function () { refresh().catch(function (error) { if (messageNode) messageNode.textContent = error.message; }); });
document.addEventListener('visibilitychange', function () { if (document.visibilityState === 'visible' && currentUuid) refresh().catch(function () {}); });
if (createForm) {
var typeSelect = createForm.querySelector('[name="type"]');
var enterprise = createForm.querySelector('[data-enterprise-fields]');
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 (typeSelect) typeSelect.addEventListener('change', syncEnterprise);
syncEnterprise();
createForm.addEventListener('submit', function (event) {
event.preventDefault();
var form = new FormData(createForm);
var payload = {};
form.forEach(function (value, key) { payload[key] = value; });
payload.consent = payload.consent === '1';
payload.client_request_id = uuid();
var button = createForm.querySelector('[type="submit"]');
if (button) button.disabled = true;
request(config.restBase, config, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
}).then(render).catch(function (error) {
var node = createForm.querySelector('[data-auth-message]');
if (node) node.textContent = error.message;
if (button) button.disabled = false;
});
});
}
if (manualButton) manualButton.addEventListener('click', function () { if (manualForm) manualForm.hidden = 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) return window.alert('证明材料最多上传 3 张');
var body = new FormData();
Array.prototype.forEach.call((files && files.files) || [], function (file) { body.append('evidence[]', file); });
request(config.restBase + '/' + encodeURIComponent(currentUuid) + '/manual', config, { method: 'POST', body: body }).then(render).catch(function (error) { window.alert(error.message); });
});
if (currentUuid && ['provider_pending', 'provider_processing'].indexOf(config.currentStage) !== -1) schedulePoll(5);
}
window.AKSRTUserCenterAuth = { init: init };
})();
+3 -88
View File
@@ -194,95 +194,10 @@
}
function initAuthForm() {
var modal = document.getElementById('uc-auth-modal');
if (!modal || modal.dataset.authModalBound === '1') return;
modal.dataset.authModalBound = '1';
function openModal() {
modal.classList.add('is-open');
modal.setAttribute('aria-hidden', 'false');
document.body.style.overflow = 'hidden';
var first = modal.querySelector('input:not([type="hidden"]), select, textarea, button');
if (first) window.setTimeout(function() { first.focus(); }, 80);
var root = document.querySelector('[data-identity-root]');
if (root && window.AKSRTUserCenterAuth && typeof window.AKSRTUserCenterAuth.init === 'function') {
window.AKSRTUserCenterAuth.init(root);
}
function closeModal() {
modal.classList.remove('is-open');
modal.setAttribute('aria-hidden', 'true');
document.body.style.overflow = '';
}
document.querySelectorAll('[data-auth-modal-open]').forEach(function(button) {
button.addEventListener('click', openModal);
});
modal.querySelectorAll('[data-auth-modal-close]').forEach(function(button) {
button.addEventListener('click', closeModal);
});
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape' && modal.classList.contains('is-open')) closeModal();
});
var fileInput = modal.querySelector('[data-auth-files]');
var previews = modal.querySelector('[data-auth-previews]');
if (fileInput && previews) {
fileInput.addEventListener('change', function() {
previews.innerHTML = '';
var files = Array.prototype.slice.call(fileInput.files || []);
if (files.length > 3) {
showToast('证明材料最多上传 3 张');
fileInput.value = '';
return;
}
for (var index = 0; index < files.length; index++) {
if (files[index].size > 2 * 1024 * 1024) {
showToast('单张图片不能超过 2MB');
fileInput.value = '';
previews.innerHTML = '';
return;
}
var image = document.createElement('img');
image.src = URL.createObjectURL(files[index]);
image.alt = '';
image.onload = function() { URL.revokeObjectURL(this.src); };
previews.appendChild(image);
}
});
}
var form = modal.querySelector('.aksrt-user-auth-form');
if (!form) return;
form.addEventListener('submit', function(e) {
e.preventDefault();
var button = form.querySelector('[type="submit"]');
var message = form.querySelector('[data-auth-message]');
var original = button ? button.textContent : '';
if (button) {
button.disabled = true;
button.textContent = i18n.submitting || '提交中…';
}
if (message) message.textContent = '';
fetch(ajaxUrl, { method: 'POST', body: new FormData(form), credentials: 'same-origin' })
.then(function(response) {
return response.json().then(function(payload) {
if (!response.ok || !payload || !payload.success) {
throw new Error((payload && payload.data && payload.data.message) || '提交失败');
}
return payload.data || {};
});
})
.then(function(data) {
showToast(data.message || '提交成功');
window.setTimeout(function() { window.location.assign(tabUrls.auth || '?tab=auth'); }, 500);
})
.catch(function(error) {
if (message) message.textContent = error.message || '提交失败';
showToast(error.message || '提交失败');
if (button) {
button.disabled = false;
button.textContent = original;
}
});
});
}
/* ==================== 列表内取消收藏 ==================== */