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:
@ -175,5 +175,32 @@ func injectSiteInfo(c *gin.Context, data gin.H) {
|
|||||||
if copyrightStart, exists := c.Get("site_copyright_start"); exists {
|
if copyrightStart, exists := c.Get("site_copyright_start"); exists {
|
||||||
data["CopyrightStart"] = copyrightStart
|
data["CopyrightStart"] = copyrightStart
|
||||||
}
|
}
|
||||||
|
if operatorName, exists := c.Get("site_operator_name"); exists {
|
||||||
|
data["OperatorName"] = operatorName
|
||||||
|
}
|
||||||
|
if icpNumber, exists := c.Get("site_icp_number"); exists {
|
||||||
|
data["ICPNumber"] = icpNumber
|
||||||
|
}
|
||||||
|
if icpLicense, exists := c.Get("site_icp_license"); exists {
|
||||||
|
data["ICPLicense"] = icpLicense
|
||||||
|
}
|
||||||
|
if policeNumber, exists := c.Get("site_police_number"); exists {
|
||||||
|
data["PoliceNumber"] = policeNumber
|
||||||
|
}
|
||||||
|
if wenwangwen, exists := c.Get("site_wenwangwen"); exists {
|
||||||
|
data["WenWangWen"] = wenwangwen
|
||||||
|
}
|
||||||
|
if algorithmFiling, exists := c.Get("site_algorithm_filing"); exists {
|
||||||
|
data["AlgorithmFiling"] = algorithmFiling
|
||||||
|
}
|
||||||
|
if privacyURL, exists := c.Get("site_privacy_url"); exists {
|
||||||
|
data["PrivacyURL"] = privacyURL
|
||||||
|
}
|
||||||
|
if termsURL, exists := c.Get("site_terms_url"); exists {
|
||||||
|
data["TermsURL"] = termsURL
|
||||||
|
}
|
||||||
|
if guidelinesTimer, exists := c.Get("registration_guidelines_timer"); exists {
|
||||||
|
data["GuidelinesTimer"] = guidelinesTimer
|
||||||
|
}
|
||||||
data["CurrentYear"] = time.Now().Year()
|
data["CurrentYear"] = time.Now().Year()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -117,6 +117,14 @@ type SiteInfoDefaults struct {
|
|||||||
ContactEmail string // 联系/申诉邮箱,默认 "metazone@foxmail.com"
|
ContactEmail string // 联系/申诉邮箱,默认 "metazone@foxmail.com"
|
||||||
Framework string // 附加标识,默认 "Framework: MetaGenesis"
|
Framework string // 附加标识,默认 "Framework: MetaGenesis"
|
||||||
CopyrightStart string // 版权起始年份,默认 "2024"
|
CopyrightStart string // 版权起始年份,默认 "2024"
|
||||||
|
OperatorName string // 运营主体
|
||||||
|
ICPNumber string // ICP 备案(HTML,含链接)
|
||||||
|
ICPLicense string // ICP 经营许可证(HTML)
|
||||||
|
PoliceNumber string // 公安备案(HTML)
|
||||||
|
WenWangWen string // 文网文(HTML)
|
||||||
|
AlgorithmFiling string // 算法备案(HTML)
|
||||||
|
PrivacyURL string // 隐私政策(HTML)
|
||||||
|
TermsURL string // 服务条款(HTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SiteInfo 获取站点展示信息(优先 DB 设置,fallback 默认值)
|
// SiteInfo 获取站点展示信息(优先 DB 设置,fallback 默认值)
|
||||||
@ -127,6 +135,14 @@ func (s *SiteSettings) SiteInfo() SiteInfoDefaults {
|
|||||||
ContactEmail: s.Get("site.contact_email", "metazone@foxmail.com"),
|
ContactEmail: s.Get("site.contact_email", "metazone@foxmail.com"),
|
||||||
Framework: s.Get("site.framework", "Framework: MetaGenesis"),
|
Framework: s.Get("site.framework", "Framework: MetaGenesis"),
|
||||||
CopyrightStart: s.Get("site.copyright_start", "2024"),
|
CopyrightStart: s.Get("site.copyright_start", "2024"),
|
||||||
|
OperatorName: s.Get("site.operator_name", ""),
|
||||||
|
ICPNumber: s.Get("site.icp_number", ""),
|
||||||
|
ICPLicense: s.Get("site.icp_license", ""),
|
||||||
|
PoliceNumber: s.Get("site.police_number", ""),
|
||||||
|
WenWangWen: s.Get("site.wenwangwen", ""),
|
||||||
|
AlgorithmFiling: s.Get("site.algorithm_filing", ""),
|
||||||
|
PrivacyURL: s.Get("site.privacy_url", ""),
|
||||||
|
TermsURL: s.Get("site.terms_url", ""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,6 +156,26 @@ func (s *SiteSettings) IsRegistrationEnabled() bool {
|
|||||||
return s.GetBool("registration.enabled", true)
|
return s.GetBool("registration.enabled", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ShowGuidelinesModal 注册时是否弹窗显示社区准则
|
||||||
|
func (s *SiteSettings) ShowGuidelinesModal() bool {
|
||||||
|
return s.GetBool("registration.show_guidelines", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForceGuidelines 是否强制阅读准则(需要倒计时+滚动到底部)
|
||||||
|
func (s *SiteSettings) ForceGuidelines() bool {
|
||||||
|
return s.GetBool("registration.force_guidelines", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GuidelinesTimer 强制阅读倒计时秒数
|
||||||
|
func (s *SiteSettings) GuidelinesTimer() int {
|
||||||
|
val := s.Get("registration.guidelines_timer", "60")
|
||||||
|
n, err := strconv.Atoi(val)
|
||||||
|
if err != nil || n < 1 {
|
||||||
|
return 60
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
// IsMaintenanceEnabled 是否处于维护模式(维护期间仅站长可登录和访问)
|
// IsMaintenanceEnabled 是否处于维护模式(维护期间仅站长可登录和访问)
|
||||||
func (s *SiteSettings) IsMaintenanceEnabled() bool {
|
func (s *SiteSettings) IsMaintenanceEnabled() bool {
|
||||||
return s.GetBool("maintenance.enabled", false)
|
return s.GetBool("maintenance.enabled", false)
|
||||||
|
|||||||
@ -57,6 +57,9 @@ func (ac *AuthController) RegisterPage(c *gin.Context) {
|
|||||||
"ExtraCSS": "/static/css/auth.css",
|
"ExtraCSS": "/static/css/auth.css",
|
||||||
"Guidelines": guidelines,
|
"Guidelines": guidelines,
|
||||||
"RegistrationEnabled": ac.authService.IsRegistrationEnabled(),
|
"RegistrationEnabled": ac.authService.IsRegistrationEnabled(),
|
||||||
|
"ShowGuidelinesModal": ac.siteSettings.ShowGuidelinesModal(),
|
||||||
|
"ForceGuidelines": ac.siteSettings.ForceGuidelines(),
|
||||||
|
"GuidelinesTimer": ac.siteSettings.GuidelinesTimer(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,16 @@ func InjectSiteInfo(siteSettings *config.SiteSettings) gin.HandlerFunc {
|
|||||||
c.Set("site_contact_email", info.ContactEmail)
|
c.Set("site_contact_email", info.ContactEmail)
|
||||||
c.Set("site_framework", info.Framework)
|
c.Set("site_framework", info.Framework)
|
||||||
c.Set("site_copyright_start", info.CopyrightStart)
|
c.Set("site_copyright_start", info.CopyrightStart)
|
||||||
|
c.Set("site_operator_name", info.OperatorName)
|
||||||
|
c.Set("site_icp_number", info.ICPNumber)
|
||||||
|
c.Set("site_icp_license", info.ICPLicense)
|
||||||
|
c.Set("site_police_number", info.PoliceNumber)
|
||||||
|
c.Set("site_wenwangwen", info.WenWangWen)
|
||||||
|
c.Set("site_algorithm_filing", info.AlgorithmFiling)
|
||||||
|
c.Set("site_privacy_url", info.PrivacyURL)
|
||||||
|
c.Set("site_terms_url", info.TermsURL)
|
||||||
c.Set("is_maintenance", siteSettings.IsMaintenanceEnabled())
|
c.Set("is_maintenance", siteSettings.IsMaintenanceEnabled())
|
||||||
|
c.Set("registration_guidelines_timer", siteSettings.GuidelinesTimer())
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,7 @@ func LoadTemplates(roots ...TemplateRoot) (*template.Template, error) {
|
|||||||
"formatCount": formatCount,
|
"formatCount": formatCount,
|
||||||
"str": str,
|
"str": str,
|
||||||
"assetV": common.AssetV,
|
"assetV": common.AssetV,
|
||||||
|
"safeHTML": func(s string) template.HTML { return template.HTML(s) },
|
||||||
"substr": func(s string, start, length int) string {
|
"substr": func(s string, start, length int) string {
|
||||||
runes := []rune(s)
|
runes := []rune(s)
|
||||||
if start >= len(runes) {
|
if start >= len(runes) {
|
||||||
|
|||||||
@ -38,7 +38,16 @@
|
|||||||
</label>
|
</label>
|
||||||
<span class="remember-hint">非共享设备推荐</span>
|
<span class="remember-hint">非共享设备推荐</span>
|
||||||
</div>
|
</div>
|
||||||
|
{{if .ShowGuidelinesModal}}
|
||||||
<p class="guidelines-link">点击注册即表示同意 <a href="#" id="openGuidelines">《{{.SiteName}} 社区用户准则》</a></p>
|
<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>
|
<button type="submit" class="submit-btn" id="submitBtn">注 册</button>
|
||||||
</form>
|
</form>
|
||||||
{{else}}
|
{{else}}
|
||||||
@ -56,7 +65,7 @@
|
|||||||
</main>
|
</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">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h3>{{.SiteName}} 社区用户准则</h3>
|
<h3>{{.SiteName}} 社区用户准则</h3>
|
||||||
@ -67,15 +76,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<div id="modalEnforce">
|
<div id="modalEnforce">
|
||||||
|
{{if .ForceGuidelines}}
|
||||||
<div class="timer" id="timer">
|
<div class="timer" id="timer">
|
||||||
<span id="timerLabel">剩余阅读时间</span>
|
<span id="timerLabel">剩余阅读时间</span>
|
||||||
<span class="count" id="timerCount">60</span>
|
<span class="count" id="timerCount">{{.GuidelinesTimer}}</span>
|
||||||
<span id="timerUnit">秒</span>
|
<span id="timerUnit">秒</span>
|
||||||
<span id="timerDone" style="display:none">✓ 阅读时间已满足</span>
|
<span id="timerDone" style="display:none">✓ 阅读时间已满足</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="scroll-hint" id="scrollHint">▼ 请滚动至底部完成阅读</span>
|
<span class="scroll-hint" id="scrollHint">▼ 请滚动至底部完成阅读</span>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
{{if .ForceGuidelines}}
|
||||||
<button class="confirm-btn" id="confirmBtn" disabled>我已完整阅读并同意</button>
|
<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>
|
<button class="confirm-btn" id="previewCloseBtn" style="display:none">关 闭</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,19 @@
|
|||||||
<footer>
|
<footer>
|
||||||
<div class="container footer-content">
|
<div class="container footer-content">
|
||||||
<p class="copyright">© {{.CopyrightStart}}-{{.CurrentYear}} {{.SiteName}}{{if .Framework}} | {{.Framework}}{{end}}{{if .CanAccessAdmin}} | <a href="/admin">管理面板</a>{{end}}</p>
|
<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>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
<script src="/static/js/common.js?v={{assetV "/static/js/common.js"}}" nonce="{{.CSPNonce}}"></script>
|
<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 passwordInput = document.getElementById('password');
|
||||||
var confirmPasswordInput = document.getElementById('confirmPassword');
|
var confirmPasswordInput = document.getElementById('confirmPassword');
|
||||||
var rememberMeCheckbox = document.getElementById('rememberMe');
|
var rememberMeCheckbox = document.getElementById('rememberMe');
|
||||||
|
var guidelinesAgreedCheckbox = document.getElementById('guidelinesAgreed');
|
||||||
var emailError = document.getElementById('emailError');
|
var emailError = document.getElementById('emailError');
|
||||||
var passwordError = document.getElementById('passwordError');
|
var passwordError = document.getElementById('passwordError');
|
||||||
var confirmPasswordError = document.getElementById('confirmPasswordError');
|
var confirmPasswordError = document.getElementById('confirmPasswordError');
|
||||||
@ -32,6 +33,11 @@
|
|||||||
// Guard: exit if not on auth page
|
// Guard: exit if not on auth page
|
||||||
if (!registerForm) return;
|
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 参数(注册成功后跳转目标)
|
// 读取 redirect 参数(注册成功后跳转目标)
|
||||||
function getRedirect() {
|
function getRedirect() {
|
||||||
var m = window.location.search.match(/[?&]redirect=([^&]+)/);
|
var m = window.location.search.match(/[?&]redirect=([^&]+)/);
|
||||||
@ -39,7 +45,7 @@
|
|||||||
return '/';
|
return '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
var countdown = 60;
|
var countdown = guidelinesTimer;
|
||||||
var timerInterval = null;
|
var timerInterval = null;
|
||||||
var reachedBottom = false;
|
var reachedBottom = false;
|
||||||
var agreed = false;
|
var agreed = false;
|
||||||
@ -50,7 +56,7 @@
|
|||||||
function openModal(onAgreed) {
|
function openModal(onAgreed) {
|
||||||
isRegistrationMode = typeof onAgreed === 'function';
|
isRegistrationMode = typeof onAgreed === 'function';
|
||||||
clearInterval(timerInterval);
|
clearInterval(timerInterval);
|
||||||
countdown = 60;
|
countdown = guidelinesTimer;
|
||||||
reachedBottom = false;
|
reachedBottom = false;
|
||||||
agreed = false;
|
agreed = false;
|
||||||
modalBody.scrollTop = 0;
|
modalBody.scrollTop = 0;
|
||||||
@ -61,18 +67,25 @@
|
|||||||
modalEnforce.style.display = '';
|
modalEnforce.style.display = '';
|
||||||
confirmBtn.style.display = '';
|
confirmBtn.style.display = '';
|
||||||
previewCloseBtn.style.display = 'none';
|
previewCloseBtn.style.display = 'none';
|
||||||
timerCount.textContent = '60';
|
|
||||||
|
if (forceGuidelines) {
|
||||||
|
timerCount.textContent = guidelinesTimer;
|
||||||
timerLabel.style.display = '';
|
timerLabel.style.display = '';
|
||||||
timerCount.style.display = '';
|
timerCount.style.display = '';
|
||||||
timerUnit.style.display = '';
|
timerUnit.style.display = '';
|
||||||
timerDone.style.display = 'none';
|
timerDone.style.display = 'none';
|
||||||
timer.classList.remove('done');
|
if (timer) timer.classList.remove('done');
|
||||||
countdownPaused = false;
|
countdownPaused = false;
|
||||||
confirmBtn.disabled = true;
|
confirmBtn.disabled = true;
|
||||||
scrollHint.style.display = 'flex';
|
scrollHint.style.display = 'flex';
|
||||||
scrollHint.textContent = '▼ 请滚动至底部完成阅读';
|
scrollHint.textContent = '▼ 请滚动至底部完成阅读';
|
||||||
scrollHint.style.color = 'var(--color-danger)';
|
scrollHint.style.color = 'var(--color-danger)';
|
||||||
startCountdown();
|
startCountdown();
|
||||||
|
} else {
|
||||||
|
// 非强制:隐藏倒计时和滚动提示,按钮直接可用
|
||||||
|
modalEnforce.style.display = 'none';
|
||||||
|
confirmBtn.disabled = false;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
modalEnforce.style.display = 'none';
|
modalEnforce.style.display = 'none';
|
||||||
confirmBtn.style.display = 'none';
|
confirmBtn.style.display = 'none';
|
||||||
@ -239,37 +252,46 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 复选框模式:检查是否勾选准则
|
||||||
|
if (guidelinesAgreedCheckbox && !guidelinesAgreedCheckbox.checked) {
|
||||||
|
window.MetaLab.toast(toast, '请先阅读并同意社区准则', true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Disable submit to prevent double-click
|
// Disable submit to prevent double-click
|
||||||
submitBtn.disabled = true;
|
submitBtn.disabled = true;
|
||||||
submitBtn.textContent = '检查中...';
|
submitBtn.textContent = '检查中...';
|
||||||
|
|
||||||
// Step 1: Check if email already registered (before showing 60s guidelines)
|
// Step 1: Check if email already registered
|
||||||
var checkXhr = new XMLHttpRequest();
|
var checkXhr = new XMLHttpRequest();
|
||||||
checkXhr.open('POST', '/api/auth/check-email', true);
|
checkXhr.open('POST', '/api/auth/check-email', true);
|
||||||
checkXhr.setRequestHeader('Content-Type', 'application/json');
|
checkXhr.setRequestHeader('Content-Type', 'application/json');
|
||||||
checkXhr.onreadystatechange = function () {
|
checkXhr.onreadystatechange = function () {
|
||||||
if (checkXhr.readyState !== 4) return;
|
if (checkXhr.readyState !== 4) return;
|
||||||
|
|
||||||
submitBtn.disabled = true; // will be re-enabled by checkFormValidity if needed
|
submitBtn.disabled = true;
|
||||||
|
|
||||||
var resp;
|
var resp;
|
||||||
try { resp = JSON.parse(checkXhr.responseText); } catch (err) { resp = null; }
|
try { resp = JSON.parse(checkXhr.responseText); } catch (err) { resp = null; }
|
||||||
|
|
||||||
if (checkXhr.status === 200 && resp && resp.success) {
|
if (checkXhr.status === 200 && resp && resp.success) {
|
||||||
if (resp.data && resp.data.exists) {
|
if (resp.data && resp.data.exists) {
|
||||||
// Already registered → notify user immediately, skip guidelines
|
|
||||||
window.MetaLab.toast(toast, '该邮箱已注册,请直接登录', true);
|
window.MetaLab.toast(toast, '该邮箱已注册,请直接登录', true);
|
||||||
submitBtn.textContent = '注 册';
|
submitBtn.textContent = '注 册';
|
||||||
submitBtn.disabled = false;
|
submitBtn.disabled = false;
|
||||||
} else {
|
} else {
|
||||||
// Not registered → proceed to guidelines modal
|
|
||||||
submitBtn.textContent = '注 册';
|
submitBtn.textContent = '注 册';
|
||||||
|
if (showModal) {
|
||||||
|
// 弹窗模式:弹出准则阅读
|
||||||
openModal(function () {
|
openModal(function () {
|
||||||
doRegister(email, password, confirm);
|
doRegister(email, password, confirm);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
// 复选框模式:直接注册
|
||||||
|
doRegister(email, password, confirm);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// API error → show message, don't block
|
|
||||||
var msg = (resp && resp.message) ? resp.message : '网络异常,请稍后重试';
|
var msg = (resp && resp.message) ? resp.message : '网络异常,请稍后重试';
|
||||||
window.MetaLab.toast(toast, msg, true);
|
window.MetaLab.toast(toast, msg, true);
|
||||||
submitBtn.textContent = '注 册';
|
submitBtn.textContent = '注 册';
|
||||||
|
|||||||
@ -61,6 +61,93 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 合规信息 -->
|
||||||
|
<div class="settings-section">
|
||||||
|
<div class="section-title">合规信息</div>
|
||||||
|
<div class="section-body">
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-info">
|
||||||
|
<span class="setting-label">运营主体</span>
|
||||||
|
<span class="setting-desc">页脚展示的运营方名称</span>
|
||||||
|
</div>
|
||||||
|
<div class="setting-input-wrap">
|
||||||
|
<input type="text" id="setting-site-operator" class="setting-input" placeholder="留空则不显示" value="{{.OperatorName}}">
|
||||||
|
<button class="btn btn-sm btn-primary btn-save" data-key="site.operator_name" data-input="setting-site-operator">保存</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-info">
|
||||||
|
<span class="setting-label">隐私政策</span>
|
||||||
|
<span class="setting-desc">纯手写 HTML,如 <a href="/privacy">隐私政策</a></span>
|
||||||
|
</div>
|
||||||
|
<div class="setting-input-wrap">
|
||||||
|
<input type="text" id="setting-site-privacy" class="setting-input" placeholder="留空则不显示" value="{{.PrivacyURL}}">
|
||||||
|
<button class="btn btn-sm btn-primary btn-save" data-key="site.privacy_url" data-input="setting-site-privacy">保存</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-info">
|
||||||
|
<span class="setting-label">服务条款</span>
|
||||||
|
<span class="setting-desc">纯手写 HTML,如 <a href="/terms">服务条款</a></span>
|
||||||
|
</div>
|
||||||
|
<div class="setting-input-wrap">
|
||||||
|
<input type="text" id="setting-site-terms" class="setting-input" placeholder="留空则不显示" value="{{.TermsURL}}">
|
||||||
|
<button class="btn btn-sm btn-primary btn-save" data-key="site.terms_url" data-input="setting-site-terms">保存</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-info">
|
||||||
|
<span class="setting-label">ICP 备案</span>
|
||||||
|
<span class="setting-desc">纯手写 HTML,如 <a href="https://beian.miit.gov.cn">京ICP备XXXX号</a></span>
|
||||||
|
</div>
|
||||||
|
<div class="setting-input-wrap">
|
||||||
|
<input type="text" id="setting-site-icp" class="setting-input" placeholder="留空则不显示" value="{{.ICPNumber}}">
|
||||||
|
<button class="btn btn-sm btn-primary btn-save" data-key="site.icp_number" data-input="setting-site-icp">保存</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-info">
|
||||||
|
<span class="setting-label">ICP 经营许可证</span>
|
||||||
|
<span class="setting-desc">纯手写 HTML,留空则不显示</span>
|
||||||
|
</div>
|
||||||
|
<div class="setting-input-wrap">
|
||||||
|
<input type="text" id="setting-site-icp-license" class="setting-input" placeholder="留空则不显示" value="{{.ICPLicense}}">
|
||||||
|
<button class="btn btn-sm btn-primary btn-save" data-key="site.icp_license" data-input="setting-site-icp-license">保存</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-info">
|
||||||
|
<span class="setting-label">公安备案</span>
|
||||||
|
<span class="setting-desc">纯手写 HTML,留空则不显示</span>
|
||||||
|
</div>
|
||||||
|
<div class="setting-input-wrap">
|
||||||
|
<input type="text" id="setting-site-police" class="setting-input" placeholder="留空则不显示" value="{{.PoliceNumber}}">
|
||||||
|
<button class="btn btn-sm btn-primary btn-save" data-key="site.police_number" data-input="setting-site-police">保存</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-info">
|
||||||
|
<span class="setting-label">文网文</span>
|
||||||
|
<span class="setting-desc">纯手写 HTML,留空则不显示</span>
|
||||||
|
</div>
|
||||||
|
<div class="setting-input-wrap">
|
||||||
|
<input type="text" id="setting-site-wenwangwen" class="setting-input" placeholder="留空则不显示" value="{{.WenWangWen}}">
|
||||||
|
<button class="btn btn-sm btn-primary btn-save" data-key="site.wenwangwen" data-input="setting-site-wenwangwen">保存</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-info">
|
||||||
|
<span class="setting-label">算法备案</span>
|
||||||
|
<span class="setting-desc">纯手写 HTML,留空则不显示</span>
|
||||||
|
</div>
|
||||||
|
<div class="setting-input-wrap">
|
||||||
|
<input type="text" id="setting-site-algorithm-filing" class="setting-input" placeholder="留空则不显示" value="{{.AlgorithmFiling}}">
|
||||||
|
<button class="btn btn-sm btn-primary btn-save" data-key="site.algorithm_filing" data-input="setting-site-algorithm-filing">保存</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 维护设置 -->
|
<!-- 维护设置 -->
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<div class="section-title">维护设置</div>
|
<div class="section-title">维护设置</div>
|
||||||
@ -92,6 +179,36 @@
|
|||||||
<span class="toggle-slider"></span>
|
<span class="toggle-slider"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-info">
|
||||||
|
<span class="setting-label">弹窗显示准则</span>
|
||||||
|
<span class="setting-desc">注册时弹窗展示社区准则(关闭后仅显示复选框)</span>
|
||||||
|
</div>
|
||||||
|
<label class="toggle-switch">
|
||||||
|
<input type="checkbox" id="registration-show-guidelines" data-key="registration.show_guidelines">
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-info">
|
||||||
|
<span class="setting-label">强制阅读准则</span>
|
||||||
|
<span class="setting-desc">弹窗模式下要求倒计时+滚动到底部(关闭后弹窗可直接确认)</span>
|
||||||
|
</div>
|
||||||
|
<label class="toggle-switch">
|
||||||
|
<input type="checkbox" id="registration-force-guidelines" data-key="registration.force_guidelines">
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting-item">
|
||||||
|
<div class="setting-info">
|
||||||
|
<span class="setting-label">阅读倒计时</span>
|
||||||
|
<span class="setting-desc">强制阅读时的倒计时秒数</span>
|
||||||
|
</div>
|
||||||
|
<div class="setting-input-wrap">
|
||||||
|
<input type="number" id="setting-guidelines-timer" class="setting-input" placeholder="60" min="1" style="width:100px" value="{{.GuidelinesTimer}}">
|
||||||
|
<button class="btn btn-sm btn-primary btn-save" data-key="registration.guidelines_timer" data-input="setting-guidelines-timer">保存</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user