feat: 注册准则阅读控制 + Footer 合规信息
准则阅读(三层控制): - registration.show_guidelines — 弹窗/复选框模式 - registration.force_guidelines — 是否强制倒计时+滚动 - registration.guidelines_timer — 倒计时秒数 Footer 合规(8 字段,空值不显示,链接纯手写): - site.operator_name / site.icp_number / site.icp_license - site.police_number / site.wenwangwen / site.algorithm_filing - site.privacy_url / site.terms_url - 新增 safeHTML 模板函数输出不转义 HTML
This commit is contained in:
@ -38,7 +38,16 @@
|
||||
</label>
|
||||
<span class="remember-hint">非共享设备推荐</span>
|
||||
</div>
|
||||
{{if .ShowGuidelinesModal}}
|
||||
<p class="guidelines-link">点击注册即表示同意 <a href="#" id="openGuidelines">《{{.SiteName}} 社区用户准则》</a></p>
|
||||
{{else}}
|
||||
<div class="form-group guidelines-checkbox-group">
|
||||
<label class="guidelines-checkbox-label">
|
||||
<input type="checkbox" id="guidelinesAgreed">
|
||||
我已阅读并同意<a href="#" id="openGuidelines">《{{.SiteName}} 社区用户准则》</a>
|
||||
</label>
|
||||
</div>
|
||||
{{end}}
|
||||
<button type="submit" class="submit-btn" id="submitBtn">注 册</button>
|
||||
</form>
|
||||
{{else}}
|
||||
@ -56,7 +65,7 @@
|
||||
</main>
|
||||
|
||||
<!-- 社区准则模态框 -->
|
||||
<div class="modal-overlay" id="modalOverlay">
|
||||
<div class="modal-overlay" id="modalOverlay" data-force-guidelines="{{.ForceGuidelines}}" data-guidelines-timer="{{.GuidelinesTimer}}">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<h3>{{.SiteName}} 社区用户准则</h3>
|
||||
@ -67,15 +76,21 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div id="modalEnforce">
|
||||
{{if .ForceGuidelines}}
|
||||
<div class="timer" id="timer">
|
||||
<span id="timerLabel">剩余阅读时间</span>
|
||||
<span class="count" id="timerCount">60</span>
|
||||
<span class="count" id="timerCount">{{.GuidelinesTimer}}</span>
|
||||
<span id="timerUnit">秒</span>
|
||||
<span id="timerDone" style="display:none">✓ 阅读时间已满足</span>
|
||||
</div>
|
||||
<span class="scroll-hint" id="scrollHint">▼ 请滚动至底部完成阅读</span>
|
||||
{{end}}
|
||||
</div>
|
||||
{{if .ForceGuidelines}}
|
||||
<button class="confirm-btn" id="confirmBtn" disabled>我已完整阅读并同意</button>
|
||||
{{else}}
|
||||
<button class="confirm-btn" id="confirmBtn">我已阅读并同意</button>
|
||||
{{end}}
|
||||
<button class="confirm-btn" id="previewCloseBtn" style="display:none">关 闭</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,19 @@
|
||||
<footer>
|
||||
<div class="container footer-content">
|
||||
<p class="copyright">© {{.CopyrightStart}}-{{.CurrentYear}} {{.SiteName}}{{if .Framework}} | {{.Framework}}{{end}}{{if .CanAccessAdmin}} | <a href="/admin">管理面板</a>{{end}}</p>
|
||||
{{$hasCompliance := or .OperatorName .ICPNumber .ICPLicense .PoliceNumber .WenWangWen .AlgorithmFiling .PrivacyURL .TermsURL}}
|
||||
{{if $hasCompliance}}
|
||||
<p class="footer-compliance">
|
||||
{{if .PrivacyURL}}{{if ne .PrivacyURL ""}}{{safeHTML .PrivacyURL}}{{end}}{{end}}
|
||||
{{if .TermsURL}}{{if ne .TermsURL ""}} | {{safeHTML .TermsURL}}{{end}}{{end}}
|
||||
{{if .OperatorName}}{{if ne .OperatorName ""}} | {{.OperatorName}}{{end}}{{end}}
|
||||
{{if .ICPNumber}}{{if ne .ICPNumber ""}} | {{safeHTML .ICPNumber}}{{end}}{{end}}
|
||||
{{if .ICPLicense}}{{if ne .ICPLicense ""}} | {{safeHTML .ICPLicense}}{{end}}{{end}}
|
||||
{{if .PoliceNumber}}{{if ne .PoliceNumber ""}} | {{safeHTML .PoliceNumber}}{{end}}{{end}}
|
||||
{{if .WenWangWen}}{{if ne .WenWangWen ""}} | {{safeHTML .WenWangWen}}{{end}}{{end}}
|
||||
{{if .AlgorithmFiling}}{{if ne .AlgorithmFiling ""}} | {{safeHTML .AlgorithmFiling}}{{end}}{{end}}
|
||||
</p>
|
||||
{{end}}
|
||||
</div>
|
||||
</footer>
|
||||
<script src="/static/js/common.js?v={{assetV "/static/js/common.js"}}" nonce="{{.CSPNonce}}"></script>
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
var passwordInput = document.getElementById('password');
|
||||
var confirmPasswordInput = document.getElementById('confirmPassword');
|
||||
var rememberMeCheckbox = document.getElementById('rememberMe');
|
||||
var guidelinesAgreedCheckbox = document.getElementById('guidelinesAgreed');
|
||||
var emailError = document.getElementById('emailError');
|
||||
var passwordError = document.getElementById('passwordError');
|
||||
var confirmPasswordError = document.getElementById('confirmPasswordError');
|
||||
@ -32,6 +33,11 @@
|
||||
// Guard: exit if not on auth page
|
||||
if (!registerForm) return;
|
||||
|
||||
// 准则配置(从模板 data 属性读取)
|
||||
var forceGuidelines = modalOverlay ? modalOverlay.getAttribute('data-force-guidelines') === 'true' : true;
|
||||
var guidelinesTimer = modalOverlay ? parseInt(modalOverlay.getAttribute('data-guidelines-timer')) || 60 : 60;
|
||||
var showModal = !!modalOverlay;
|
||||
|
||||
// 读取 redirect 参数(注册成功后跳转目标)
|
||||
function getRedirect() {
|
||||
var m = window.location.search.match(/[?&]redirect=([^&]+)/);
|
||||
@ -39,7 +45,7 @@
|
||||
return '/';
|
||||
}
|
||||
|
||||
var countdown = 60;
|
||||
var countdown = guidelinesTimer;
|
||||
var timerInterval = null;
|
||||
var reachedBottom = false;
|
||||
var agreed = false;
|
||||
@ -50,7 +56,7 @@
|
||||
function openModal(onAgreed) {
|
||||
isRegistrationMode = typeof onAgreed === 'function';
|
||||
clearInterval(timerInterval);
|
||||
countdown = 60;
|
||||
countdown = guidelinesTimer;
|
||||
reachedBottom = false;
|
||||
agreed = false;
|
||||
modalBody.scrollTop = 0;
|
||||
@ -61,18 +67,25 @@
|
||||
modalEnforce.style.display = '';
|
||||
confirmBtn.style.display = '';
|
||||
previewCloseBtn.style.display = 'none';
|
||||
timerCount.textContent = '60';
|
||||
timerLabel.style.display = '';
|
||||
timerCount.style.display = '';
|
||||
timerUnit.style.display = '';
|
||||
timerDone.style.display = 'none';
|
||||
timer.classList.remove('done');
|
||||
countdownPaused = false;
|
||||
confirmBtn.disabled = true;
|
||||
scrollHint.style.display = 'flex';
|
||||
scrollHint.textContent = '▼ 请滚动至底部完成阅读';
|
||||
scrollHint.style.color = 'var(--color-danger)';
|
||||
startCountdown();
|
||||
|
||||
if (forceGuidelines) {
|
||||
timerCount.textContent = guidelinesTimer;
|
||||
timerLabel.style.display = '';
|
||||
timerCount.style.display = '';
|
||||
timerUnit.style.display = '';
|
||||
timerDone.style.display = 'none';
|
||||
if (timer) timer.classList.remove('done');
|
||||
countdownPaused = false;
|
||||
confirmBtn.disabled = true;
|
||||
scrollHint.style.display = 'flex';
|
||||
scrollHint.textContent = '▼ 请滚动至底部完成阅读';
|
||||
scrollHint.style.color = 'var(--color-danger)';
|
||||
startCountdown();
|
||||
} else {
|
||||
// 非强制:隐藏倒计时和滚动提示,按钮直接可用
|
||||
modalEnforce.style.display = 'none';
|
||||
confirmBtn.disabled = false;
|
||||
}
|
||||
} else {
|
||||
modalEnforce.style.display = 'none';
|
||||
confirmBtn.style.display = 'none';
|
||||
@ -239,37 +252,46 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// 复选框模式:检查是否勾选准则
|
||||
if (guidelinesAgreedCheckbox && !guidelinesAgreedCheckbox.checked) {
|
||||
window.MetaLab.toast(toast, '请先阅读并同意社区准则', true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable submit to prevent double-click
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = '检查中...';
|
||||
|
||||
// Step 1: Check if email already registered (before showing 60s guidelines)
|
||||
// Step 1: Check if email already registered
|
||||
var checkXhr = new XMLHttpRequest();
|
||||
checkXhr.open('POST', '/api/auth/check-email', true);
|
||||
checkXhr.setRequestHeader('Content-Type', 'application/json');
|
||||
checkXhr.onreadystatechange = function () {
|
||||
if (checkXhr.readyState !== 4) return;
|
||||
|
||||
submitBtn.disabled = true; // will be re-enabled by checkFormValidity if needed
|
||||
submitBtn.disabled = true;
|
||||
|
||||
var resp;
|
||||
try { resp = JSON.parse(checkXhr.responseText); } catch (err) { resp = null; }
|
||||
|
||||
if (checkXhr.status === 200 && resp && resp.success) {
|
||||
if (resp.data && resp.data.exists) {
|
||||
// Already registered → notify user immediately, skip guidelines
|
||||
window.MetaLab.toast(toast, '该邮箱已注册,请直接登录', true);
|
||||
submitBtn.textContent = '注 册';
|
||||
submitBtn.disabled = false;
|
||||
} else {
|
||||
// Not registered → proceed to guidelines modal
|
||||
submitBtn.textContent = '注 册';
|
||||
openModal(function () {
|
||||
if (showModal) {
|
||||
// 弹窗模式:弹出准则阅读
|
||||
openModal(function () {
|
||||
doRegister(email, password, confirm);
|
||||
});
|
||||
} else {
|
||||
// 复选框模式:直接注册
|
||||
doRegister(email, password, confirm);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// API error → show message, don't block
|
||||
var msg = (resp && resp.message) ? resp.message : '网络异常,请稍后重试';
|
||||
window.MetaLab.toast(toast, msg, true);
|
||||
submitBtn.textContent = '注 册';
|
||||
|
||||
Reference in New Issue
Block a user