style: 优化表单样式并提升表单性能
This commit is contained in:
@@ -5,9 +5,7 @@
|
||||
.itstudio-otp-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.itstudio-otp-group {
|
||||
@@ -69,9 +67,9 @@
|
||||
}
|
||||
|
||||
.itstudio-otp-send.is-countdown {
|
||||
background: rgba(56, 139, 253, 0.12) !important;
|
||||
border: 1px solid rgba(56, 139, 253, 0.3) !important;
|
||||
color: #58a6ff !important;
|
||||
background: #6b7280 !important;
|
||||
border: 1px solid #7c8490 !important;
|
||||
color: #e5e7eb !important;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: 600;
|
||||
}
|
||||
@@ -82,28 +80,7 @@
|
||||
}
|
||||
|
||||
.itstudio-otp-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 0.84rem;
|
||||
line-height: 1.45;
|
||||
padding: 2px 4px;
|
||||
color: var(--text-secondary);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.itstudio-otp-status.is-success {
|
||||
color: #3fb950;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.itstudio-otp-status.is-error {
|
||||
color: #f85149;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.itstudio-otp-status.is-info {
|
||||
color: #58a6ff;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 浅色模式适配 */
|
||||
@@ -118,21 +95,9 @@
|
||||
}
|
||||
|
||||
[data-theme="light"] .itstudio-otp-send.is-countdown {
|
||||
background: rgba(9, 105, 218, 0.08) !important;
|
||||
border: 1px solid rgba(9, 105, 218, 0.25) !important;
|
||||
color: #0969da !important;
|
||||
}
|
||||
|
||||
[data-theme="light"] .itstudio-otp-status.is-success {
|
||||
color: #1a7f37;
|
||||
}
|
||||
|
||||
[data-theme="light"] .itstudio-otp-status.is-error {
|
||||
color: #cf222e;
|
||||
}
|
||||
|
||||
[data-theme="light"] .itstudio-otp-status.is-info {
|
||||
color: #0969da;
|
||||
background: #9ca3af !important;
|
||||
border: 1px solid #9ca3af !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
@@ -142,4 +107,4 @@
|
||||
.itstudio-otp-send {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const OTP_COOLDOWN_SECONDS = 60;
|
||||
|
||||
function getLanguage() {
|
||||
return document.documentElement.lang === 'en' ? 'en' : 'cn';
|
||||
}
|
||||
@@ -41,12 +43,18 @@
|
||||
}
|
||||
status.style.display = 'flex';
|
||||
status.textContent = message;
|
||||
status.className = 'itstudio-otp-status' + (tone ? ' is-' + tone : '');
|
||||
status.className = 'itstudio-otp-status ' + (tone === 'error' ? 'frm_error_style' : 'frm_message');
|
||||
}
|
||||
|
||||
function startCooldown(button, seconds) {
|
||||
let remaining = Math.max(1, Number(seconds) || Number(config.cooldown) || 60);
|
||||
let remaining = Math.max(1, Number(seconds) || OTP_COOLDOWN_SECONDS);
|
||||
if (button.itstudioOtpTimer) {
|
||||
window.clearInterval(button.itstudioOtpTimer);
|
||||
}
|
||||
|
||||
button.disabled = true;
|
||||
button.dataset.otpLocked = '1';
|
||||
button.setAttribute('aria-disabled', 'true');
|
||||
button.classList.add('is-countdown');
|
||||
|
||||
const update = function () {
|
||||
@@ -55,6 +63,8 @@
|
||||
if (remaining < 0) {
|
||||
window.clearInterval(button.itstudioOtpTimer);
|
||||
button.itstudioOtpTimer = null;
|
||||
button.dataset.otpLocked = '0';
|
||||
button.setAttribute('aria-disabled', 'false');
|
||||
button.classList.remove('is-countdown');
|
||||
button.textContent = config.labels.send || '发送验证码';
|
||||
button.disabled = false;
|
||||
@@ -65,7 +75,36 @@
|
||||
button.itstudioOtpTimer = window.setInterval(update, 1000);
|
||||
}
|
||||
|
||||
async function readAjaxResult(response) {
|
||||
const raw = await response.text();
|
||||
const normalized = raw.replace(/^\uFEFF/, '').trim();
|
||||
if (!normalized) {
|
||||
throw new Error('Empty AJAX response');
|
||||
}
|
||||
|
||||
const result = JSON.parse(normalized);
|
||||
if (result && typeof result === 'object' && typeof result.success !== 'undefined') {
|
||||
return {
|
||||
success: result.success === true || result.success === 1 || result.success === '1',
|
||||
payload: result.data && typeof result.data === 'object' ? result.data : {}
|
||||
};
|
||||
}
|
||||
|
||||
if (result && typeof result === 'object' && (result.message_cn || result.message_en)) {
|
||||
return {
|
||||
success: true,
|
||||
payload: result
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error('Unexpected AJAX response');
|
||||
}
|
||||
|
||||
async function sendCode(form, button, status) {
|
||||
if (button.dataset.otpLocked === '1' || button.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const emailInput = findField(form, config.emailFieldId);
|
||||
const email = emailInput ? emailInput.value.trim() : '';
|
||||
if (!email) {
|
||||
@@ -79,7 +118,10 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Block rapid duplicate clicks while the request is in flight.
|
||||
button.disabled = true;
|
||||
button.dataset.otpLocked = '1';
|
||||
button.setAttribute('aria-disabled', 'true');
|
||||
button.classList.add('is-loading');
|
||||
button.textContent = config.labels.sending || '发送中…';
|
||||
setStatus(status, config.labels.sending || '正在发送验证码…', 'info');
|
||||
@@ -95,26 +137,26 @@
|
||||
nonce: config.nonce
|
||||
}).toString()
|
||||
});
|
||||
const result = await response.json();
|
||||
const payload = result && result.data ? result.data : {};
|
||||
const result = await readAjaxResult(response);
|
||||
const payload = result.payload || {};
|
||||
button.classList.remove('is-loading');
|
||||
|
||||
if (!result.success) {
|
||||
setStatus(status, getMessage(payload, config.labels.networkError || '发送失败,请稍后重试。'), 'error');
|
||||
if (payload.wait) {
|
||||
startCooldown(button, payload.wait);
|
||||
} else {
|
||||
button.textContent = config.labels.send || '发送验证码';
|
||||
button.disabled = false;
|
||||
}
|
||||
button.dataset.otpLocked = '0';
|
||||
button.setAttribute('aria-disabled', 'false');
|
||||
button.textContent = config.labels.send || '发送验证码';
|
||||
button.disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus(status, getMessage(payload, '验证码已发送,请检查邮箱及垃圾箱。'), 'success');
|
||||
startCooldown(button, payload.cooldown || config.cooldown);
|
||||
startCooldown(button, payload.cooldown || OTP_COOLDOWN_SECONDS);
|
||||
} catch (error) {
|
||||
button.classList.remove('is-loading');
|
||||
setStatus(status, config.labels.networkError || '网络错误,请稍后重试。', 'error');
|
||||
button.dataset.otpLocked = '0';
|
||||
button.setAttribute('aria-disabled', 'false');
|
||||
button.textContent = config.labels.send || '发送验证码';
|
||||
button.disabled = false;
|
||||
}
|
||||
@@ -150,6 +192,8 @@
|
||||
button.type = 'button';
|
||||
button.className = 'itstudio-otp-send';
|
||||
button.textContent = config.labels.send || '发送验证码';
|
||||
button.dataset.otpLocked = '0';
|
||||
button.setAttribute('aria-disabled', 'false');
|
||||
|
||||
const status = document.createElement('div');
|
||||
status.className = 'itstudio-otp-status';
|
||||
@@ -171,7 +215,34 @@
|
||||
});
|
||||
}
|
||||
|
||||
let mountQueued = false;
|
||||
const scheduleMount = function () {
|
||||
if (mountQueued) {
|
||||
return;
|
||||
}
|
||||
|
||||
mountQueued = true;
|
||||
window.requestAnimationFrame(function () {
|
||||
mountQueued = false;
|
||||
mount();
|
||||
});
|
||||
};
|
||||
|
||||
mount();
|
||||
const observer = new MutationObserver(mount);
|
||||
observer.observe(document.body, {childList: true, subtree: true});
|
||||
})();
|
||||
const observerRoot = document.querySelector('.join-form-content') || document.body;
|
||||
const observer = new MutationObserver(function (mutations) {
|
||||
const formStructureChanged = mutations.some(function (mutation) {
|
||||
return Array.from(mutation.addedNodes).concat(Array.from(mutation.removedNodes)).some(function (node) {
|
||||
return node.nodeType === 1 && (
|
||||
node.matches('.frm_forms, form') ||
|
||||
Boolean(node.querySelector && node.querySelector('.frm_forms, form'))
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
if (formStructureChanged) {
|
||||
scheduleMount();
|
||||
}
|
||||
});
|
||||
observer.observe(observerRoot, {childList: true, subtree: true});
|
||||
})();
|
||||
|
||||
@@ -57,6 +57,36 @@ function itstudio_join_otp_random_code() {
|
||||
return (string) wp_rand(100000, 999999);
|
||||
}
|
||||
|
||||
function itstudio_join_otp_email_exists_in_form($form_id, $field_id, $email) {
|
||||
global $wpdb;
|
||||
|
||||
$form_id = absint($form_id);
|
||||
$field_id = absint($field_id);
|
||||
$email = itstudio_join_otp_normalize_email($email);
|
||||
if ($form_id <= 0 || $field_id <= 0 || $email === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$items_table = $wpdb->prefix . 'frm_items';
|
||||
$metas_table = $wpdb->prefix . 'frm_item_metas';
|
||||
$count = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(1)
|
||||
FROM {$metas_table} AS meta
|
||||
INNER JOIN {$items_table} AS item ON item.id = meta.item_id
|
||||
WHERE item.form_id = %d
|
||||
AND item.is_draft = 0
|
||||
AND meta.field_id = %d
|
||||
AND LOWER(TRIM(meta.meta_value)) = %s",
|
||||
$form_id,
|
||||
$field_id,
|
||||
$email
|
||||
)
|
||||
);
|
||||
|
||||
return (int) $count > 0;
|
||||
}
|
||||
|
||||
function itstudio_join_otp_json_error($message_cn, $message_en, $status = 400, $extra = array()) {
|
||||
wp_send_json_error(
|
||||
array_merge(
|
||||
@@ -85,6 +115,10 @@ function itstudio_join_otp_send_ajax() {
|
||||
itstudio_join_otp_json_error('请输入有效的邮箱地址。', 'Please enter a valid email address.', 400);
|
||||
}
|
||||
|
||||
if (itstudio_join_otp_email_exists_in_form($config['form_id'], $config['email_field_id'], $email)) {
|
||||
itstudio_join_otp_json_error('该邮箱已经报名,无需重复提交。', 'This email has already been used to apply.', 409);
|
||||
}
|
||||
|
||||
$ip = itstudio_join_otp_get_client_ip();
|
||||
$otp_key = itstudio_join_otp_key($email, $config['form_id']);
|
||||
$existing = get_transient($otp_key);
|
||||
|
||||
Reference in New Issue
Block a user