style: otp 验证码样式
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
/* =========================================
|
||||
ITStudio 邮箱 OTP 验证码组件样式 (Modern Geek Style)
|
||||
========================================= */
|
||||
|
||||
.itstudio-otp-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.itstudio-otp-group {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.itstudio-otp-group .itstudio-otp-input {
|
||||
flex: 1 1 auto !important;
|
||||
min-width: 0 !important;
|
||||
letter-spacing: 0.1em !important;
|
||||
font-family: var(--font-mono, ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace) !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
.itstudio-otp-send {
|
||||
flex: 0 0 auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 44px;
|
||||
padding: 0 20px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.16);
|
||||
background: linear-gradient(135deg, #1f6feb 0%, #1158c7 100%);
|
||||
color: #ffffff;
|
||||
font-family: inherit;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 8px rgba(31, 111, 235, 0.35);
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.itstudio-otp-send:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
filter: brightness(1.08);
|
||||
box-shadow: 0 4px 14px rgba(31, 111, 235, 0.45);
|
||||
}
|
||||
|
||||
.itstudio-otp-send:active:not(:disabled) {
|
||||
transform: translateY(0);
|
||||
filter: brightness(0.96);
|
||||
box-shadow: 0 1px 4px rgba(31, 111, 235, 0.3);
|
||||
}
|
||||
|
||||
.itstudio-otp-send:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.7;
|
||||
transform: none !important;
|
||||
box-shadow: none !important;
|
||||
filter: none !important;
|
||||
}
|
||||
|
||||
.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;
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.itstudio-otp-send.is-loading {
|
||||
opacity: 0.85;
|
||||
cursor: wait;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
/* 浅色模式适配 */
|
||||
[data-theme="light"] .itstudio-otp-send {
|
||||
background: linear-gradient(135deg, #0969da 0%, #0550ae 100%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||
box-shadow: 0 2px 8px rgba(9, 105, 218, 0.28);
|
||||
}
|
||||
|
||||
[data-theme="light"] .itstudio-otp-send:hover:not(:disabled) {
|
||||
box-shadow: 0 4px 14px rgba(9, 105, 218, 0.38);
|
||||
}
|
||||
|
||||
[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;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.itstudio-otp-group {
|
||||
flex-direction: column;
|
||||
}
|
||||
.itstudio-otp-send {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const config = window.itstudioJoinOtp;
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
|
||||
function getLanguage() {
|
||||
return document.documentElement.lang === 'en' ? 'en' : 'cn';
|
||||
}
|
||||
|
||||
function getMessage(payload, fallback) {
|
||||
const key = getLanguage() === 'en' ? 'message_en' : 'message_cn';
|
||||
return payload && payload[key] ? payload[key] : fallback;
|
||||
}
|
||||
|
||||
function findField(form, fieldId) {
|
||||
return form.querySelector('[name="item_meta[' + fieldId + ']"]');
|
||||
}
|
||||
|
||||
function findTargetForm() {
|
||||
const candidates = document.querySelectorAll('.join-form-content .frm_forms form');
|
||||
for (const form of candidates) {
|
||||
const formIdInput = form.querySelector('input[name="form_id"]');
|
||||
if (!formIdInput || Number(formIdInput.value) === Number(config.formId)) {
|
||||
if (findField(form, config.emailFieldId) && findField(form, config.codeFieldId)) {
|
||||
return form;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function setStatus(status, message, tone) {
|
||||
if (!message) {
|
||||
status.textContent = '';
|
||||
status.className = 'itstudio-otp-status';
|
||||
status.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
status.style.display = 'flex';
|
||||
status.textContent = message;
|
||||
status.className = 'itstudio-otp-status' + (tone ? ' is-' + tone : '');
|
||||
}
|
||||
|
||||
function startCooldown(button, seconds) {
|
||||
let remaining = Math.max(1, Number(seconds) || Number(config.cooldown) || 60);
|
||||
button.disabled = true;
|
||||
button.classList.add('is-countdown');
|
||||
|
||||
const update = function () {
|
||||
button.textContent = String(config.labels.resend || '重新发送 (%s秒)').replace('%s', remaining);
|
||||
remaining -= 1;
|
||||
if (remaining < 0) {
|
||||
window.clearInterval(button.itstudioOtpTimer);
|
||||
button.itstudioOtpTimer = null;
|
||||
button.classList.remove('is-countdown');
|
||||
button.textContent = config.labels.send || '发送验证码';
|
||||
button.disabled = false;
|
||||
}
|
||||
};
|
||||
|
||||
update();
|
||||
button.itstudioOtpTimer = window.setInterval(update, 1000);
|
||||
}
|
||||
|
||||
async function sendCode(form, button, status) {
|
||||
const emailInput = findField(form, config.emailFieldId);
|
||||
const email = emailInput ? emailInput.value.trim() : '';
|
||||
if (!email) {
|
||||
setStatus(status, config.labels.emailRequired || '请先填写邮箱地址。', 'error');
|
||||
emailInput?.focus();
|
||||
return;
|
||||
}
|
||||
if (emailInput && !emailInput.checkValidity()) {
|
||||
setStatus(status, config.labels.invalidEmail || '请输入有效的邮箱地址。', 'error');
|
||||
emailInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
button.disabled = true;
|
||||
button.classList.add('is-loading');
|
||||
button.textContent = config.labels.sending || '发送中…';
|
||||
setStatus(status, config.labels.sending || '正在发送验证码…', 'info');
|
||||
|
||||
try {
|
||||
const response = await window.fetch(config.ajaxUrl, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'},
|
||||
body: new URLSearchParams({
|
||||
action: 'itstudio_join_otp_send',
|
||||
email: email,
|
||||
nonce: config.nonce
|
||||
}).toString()
|
||||
});
|
||||
const result = await response.json();
|
||||
const payload = result && result.data ? result.data : {};
|
||||
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;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus(status, getMessage(payload, '验证码已发送,请检查邮箱及垃圾箱。'), 'success');
|
||||
startCooldown(button, payload.cooldown || config.cooldown);
|
||||
} catch (error) {
|
||||
button.classList.remove('is-loading');
|
||||
setStatus(status, config.labels.networkError || '网络错误,请稍后重试。', 'error');
|
||||
button.textContent = config.labels.send || '发送验证码';
|
||||
button.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function mount() {
|
||||
const form = findTargetForm();
|
||||
if (!form || form.querySelector('.itstudio-otp-wrap')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const codeInput = findField(form, config.codeFieldId);
|
||||
if (!codeInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
codeInput.setAttribute('inputmode', 'numeric');
|
||||
codeInput.setAttribute('pattern', '[0-9]{6}');
|
||||
codeInput.setAttribute('maxlength', '6');
|
||||
codeInput.setAttribute('autocomplete', 'one-time-code');
|
||||
if (!codeInput.getAttribute('placeholder')) {
|
||||
codeInput.setAttribute('placeholder', '请输入 6 位验证码');
|
||||
}
|
||||
codeInput.classList.add('itstudio-otp-input');
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'itstudio-otp-wrap';
|
||||
|
||||
const group = document.createElement('div');
|
||||
group.className = 'itstudio-otp-group';
|
||||
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.className = 'itstudio-otp-send';
|
||||
button.textContent = config.labels.send || '发送验证码';
|
||||
|
||||
const status = document.createElement('div');
|
||||
status.className = 'itstudio-otp-status';
|
||||
status.setAttribute('role', 'status');
|
||||
status.setAttribute('aria-live', 'polite');
|
||||
status.style.display = 'none';
|
||||
|
||||
if (codeInput.parentNode) {
|
||||
codeInput.parentNode.insertBefore(wrapper, codeInput);
|
||||
group.appendChild(codeInput);
|
||||
group.appendChild(button);
|
||||
wrapper.appendChild(group);
|
||||
wrapper.appendChild(status);
|
||||
}
|
||||
|
||||
button.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
sendCode(form, button, status);
|
||||
});
|
||||
}
|
||||
|
||||
mount();
|
||||
const observer = new MutationObserver(mount);
|
||||
observer.observe(document.body, {childList: true, subtree: true});
|
||||
})();
|
||||
Reference in New Issue
Block a user