633 lines
32 KiB
Smarty
633 lines
32 KiB
Smarty
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<meta name="robots" content="noindex, nofollow">
|
||
<title>统一认证中心 - 注册</title>
|
||
<style>
|
||
:root {
|
||
--primary: #3973ff;
|
||
--primary-dark: #2560a9;
|
||
--text: #1f2b48;
|
||
--text-sub: #5b6780;
|
||
--muted: #8a94ad;
|
||
--border: #e3e9f5;
|
||
--radius: 12px;
|
||
}
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
html, body { height: 100%; }
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
||
color: var(--text);
|
||
background: linear-gradient(120deg, #eef4ff 0%, #f7fbff 55%, #eaf3ff 100%);
|
||
min-height: 100vh;
|
||
display: flex; align-items: center; justify-content: center;
|
||
padding: 32px 20px;
|
||
position: relative; overflow-x: hidden;
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
.glow { position: fixed; border-radius: 50%; filter: blur(60px); pointer-events: none; z-index: 0; }
|
||
.glow-1 { width: 380px; height: 380px; top: -120px; left: -100px; background: rgba(82, 168, 255, 0.28); }
|
||
.glow-2 { width: 320px; height: 320px; bottom: -120px; right: -80px; background: rgba(57, 115, 255, 0.20); }
|
||
.auth-card {
|
||
position: relative; z-index: 1;
|
||
width: 100%; max-width: 860px;
|
||
background: #fff; border-radius: 22px;
|
||
box-shadow: 0 18px 50px 0 rgba(37, 96, 169, 0.14), 0 2px 6px 0 rgba(31, 43, 72, 0.04);
|
||
padding: 40px 48px 32px;
|
||
}
|
||
.brand { text-align: center; margin-bottom: 22px; }
|
||
.brand h1 { font-size: 22px; font-weight: 700; letter-spacing: 1px; }
|
||
.brand p { color: var(--muted); font-size: 13px; margin-top: 6px; }
|
||
|
||
/* 步骤条 */
|
||
.steps { display: flex; align-items: center; justify-content: center; gap: 10px; margin-bottom: 28px; }
|
||
.step { display: flex; align-items: center; gap: 8px; color: var(--muted); font-size: 14px; }
|
||
.step .dot {
|
||
width: 26px; height: 26px; border-radius: 50%;
|
||
display: flex; align-items: center; justify-content: center;
|
||
background: #eef2fb; color: var(--muted); font-size: 13px; font-weight: 600;
|
||
transition: all .25s;
|
||
}
|
||
.step.active { color: var(--text); font-weight: 600; }
|
||
.step.active .dot { background: var(--primary); color: #fff; }
|
||
.step.done .dot { background: #34b96f; color: #fff; }
|
||
.step-line { width: 70px; height: 2px; background: var(--border); border-radius: 2px; }
|
||
|
||
.panel { display: none; }
|
||
.panel.active { display: block; animation: fade .25s ease; }
|
||
@keyframes fade { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
|
||
|
||
/* 第一步:方式切换 */
|
||
.mode-tabs { display: flex; background: #f2f5fb; border-radius: 12px; padding: 5px; margin-bottom: 22px; }
|
||
.mode-tab {
|
||
flex: 1; text-align: center; padding: 10px 0; border-radius: 9px;
|
||
font-size: 14px; color: var(--text-sub); cursor: pointer; user-select: none;
|
||
transition: all .2s;
|
||
}
|
||
.mode-tab.on { background: #fff; color: var(--primary); font-weight: 600; box-shadow: 0 2px 8px rgba(31,43,72,.08); }
|
||
.mode-pane { display: none; }
|
||
.mode-pane.on { display: block; }
|
||
|
||
.field { margin-bottom: 16px; }
|
||
.field label { display: block; font-size: 13px; color: var(--text-sub); margin-bottom: 6px; }
|
||
.field label .req { color: #e5484d; margin-left: 2px; }
|
||
.input-row { display: flex; gap: 10px; }
|
||
.ipt {
|
||
flex: 1; width: 100%; height: 44px; padding: 0 14px;
|
||
border: 1px solid var(--border); border-radius: 10px;
|
||
font-size: 14px; color: var(--text); outline: none; background: #fbfcff;
|
||
transition: border-color .2s, box-shadow .2s;
|
||
}
|
||
.ipt:focus { border-color: var(--primary); box-shadow: 0 0 0 3px rgba(57,115,255,.12); background: #fff; }
|
||
.btn {
|
||
height: 44px; padding: 0 20px; border: none; border-radius: 10px;
|
||
font-size: 14px; cursor: pointer; transition: all .2s;
|
||
background: #eef2fb; color: var(--text-sub);
|
||
}
|
||
.btn:hover { background: #e2e9f8; }
|
||
.btn:disabled { opacity: .55; cursor: not-allowed; }
|
||
.btn-primary {
|
||
width: 100%; height: 46px; margin-top: 6px;
|
||
background: linear-gradient(135deg, #3973ff, #5a8bff);
|
||
color: #fff; font-size: 15px; font-weight: 600;
|
||
box-shadow: 0 8px 20px -6px rgba(57,115,255,.5);
|
||
}
|
||
.btn-primary:hover { filter: brightness(1.05); }
|
||
.btn-ghost { background: #fff; border: 1px solid var(--border); color: var(--text-sub); }
|
||
.btn-ghost:hover { border-color: var(--primary); color: var(--primary); background: #fff; }
|
||
.code-btn { min-width: 118px; }
|
||
.hint { font-size: 12px; color: var(--muted); margin-top: 4px; line-height: 1.6; }
|
||
|
||
/* 账号注册二维码区 */
|
||
.wechat-box { display: none; text-align: center; margin-bottom: 16px; padding: 16px; border: 1px dashed var(--border); border-radius: 12px; background: #fbfcff; }
|
||
.wechat-box.show { display: block; }
|
||
.wechat-box img { width: 168px; height: 168px; object-fit: contain; }
|
||
.wechat-box .scan-tip { font-size: 13px; color: var(--text-sub); margin-top: 8px; }
|
||
.wechat-box .scan-tip.ok { color: #34b96f; font-weight: 600; }
|
||
|
||
/* 第二步:两个卡片 */
|
||
.cards { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
|
||
@media (max-width: 760px) { .cards { grid-template-columns: 1fr; } .auth-card { padding: 28px 20px; } }
|
||
.choice-card {
|
||
border: 1px solid var(--border); border-radius: 14px; padding: 20px;
|
||
background: #fbfcff; transition: border-color .2s, box-shadow .2s;
|
||
}
|
||
.choice-card:hover { border-color: rgba(57,115,255,.45); box-shadow: 0 8px 24px -10px rgba(57,115,255,.25); }
|
||
.choice-card h3 { font-size: 15px; display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
|
||
.choice-card .desc { font-size: 12px; color: var(--muted); margin-bottom: 14px; line-height: 1.6; }
|
||
.choice-card .field { margin-bottom: 12px; }
|
||
.search-list { max-height: 168px; overflow-y: auto; border: 1px solid var(--border); border-radius: 10px; margin-top: 8px; background: #fff; }
|
||
.search-item { padding: 10px 12px; font-size: 13px; cursor: pointer; border-bottom: 1px solid #f0f3fa; display: flex; justify-content: space-between; gap: 8px; }
|
||
.search-item:last-child { border-bottom: none; }
|
||
.search-item:hover { background: #f4f8ff; }
|
||
.search-item.sel { background: #ecf2ff; color: var(--primary); font-weight: 600; }
|
||
.search-item .code { color: var(--muted); font-size: 12px; }
|
||
.search-empty { padding: 12px; font-size: 12px; color: var(--muted); text-align: center; }
|
||
.file-row { display: flex; align-items: center; gap: 10px; }
|
||
.file-name { font-size: 12px; color: var(--text-sub); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.file-name.ok { color: #34b96f; }
|
||
.card-submit { width: 100%; margin-top: 4px; }
|
||
|
||
.skip-row { text-align: center; margin-top: 20px; }
|
||
.skip-link { font-size: 13px; color: var(--muted); cursor: pointer; background: none; border: none; text-decoration: underline; }
|
||
.skip-link:hover { color: var(--primary); }
|
||
|
||
.msg { display: none; margin-bottom: 16px; padding: 10px 14px; border-radius: 10px; font-size: 13px; line-height: 1.6; }
|
||
.msg.show { display: block; }
|
||
.msg-err { background: #fdf0f0; color: #d54941; border: 1px solid #f5d4d2; }
|
||
.msg-ok { background: #eefaf2; color: #2e9e5b; border: 1px solid #cdeeda; }
|
||
|
||
.foot { text-align: center; margin-top: 22px; font-size: 13px; color: var(--muted); }
|
||
.foot a { color: var(--primary); text-decoration: none; }
|
||
.foot a:hover { text-decoration: underline; }
|
||
|
||
/* 成功浮层 */
|
||
.done-mask { display: none; position: fixed; inset: 0; background: rgba(23,32,54,.45); z-index: 50; align-items: center; justify-content: center; }
|
||
.done-mask.show { display: flex; }
|
||
.done-box { width: 380px; max-width: 92vw; background: #fff; border-radius: 18px; padding: 32px 28px; text-align: center; }
|
||
.done-box .icon { width: 56px; height: 56px; border-radius: 50%; background: #eefaf2; color: #34b96f; font-size: 28px; display: flex; align-items: center; justify-content: center; margin: 0 auto 14px; }
|
||
.done-box h3 { font-size: 17px; margin-bottom: 8px; }
|
||
.done-box p { font-size: 13px; color: var(--text-sub); line-height: 1.7; margin-bottom: 18px; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="glow glow-1"></div>
|
||
<div class="glow glow-2"></div>
|
||
|
||
<div class="auth-card" id="app"
|
||
data-client-id="{{.ClientID}}"
|
||
data-redirect="{{.Redirect}}">
|
||
<div class="brand">
|
||
<h1>创建账号</h1>
|
||
<p>注册统一认证账号,一个账号密码通行所有企业</p>
|
||
</div>
|
||
|
||
<div class="steps">
|
||
<div class="step active" id="stepDot1"><span class="dot">1</span><span>核验身份</span></div>
|
||
<div class="step-line"></div>
|
||
<div class="step" id="stepDot2"><span class="dot">2</span><span>企业归属</span></div>
|
||
</div>
|
||
|
||
<div class="msg msg-err" id="globalMsg"></div>
|
||
|
||
<!-- ================= 第一步:核验身份 ================= -->
|
||
<div class="panel active" id="panelStep1">
|
||
<div class="mode-tabs">
|
||
<div class="mode-tab on" data-mode="phone">手机号注册</div>
|
||
<div class="mode-tab" data-mode="account">账号注册</div>
|
||
</div>
|
||
|
||
<!-- 手机号注册 -->
|
||
<div class="mode-pane on" id="panePhone">
|
||
<div class="field">
|
||
<label>手机号<span class="req">*</span></label>
|
||
<input class="ipt" id="phone" type="tel" maxlength="11" placeholder="请输入手机号(作为登录账号)">
|
||
</div>
|
||
<div class="field">
|
||
<label>短信验证码<span class="req">*</span></label>
|
||
<div class="input-row">
|
||
<input class="ipt" id="phoneCode" maxlength="6" placeholder="请输入短信验证码">
|
||
<button class="btn code-btn" id="sendPhoneCode" type="button">获取验证码</button>
|
||
</div>
|
||
</div>
|
||
<div class="field">
|
||
<label>设置密码<span class="req">*</span></label>
|
||
<input class="ipt" id="phonePwd" type="password" placeholder="密码长度不少于6位">
|
||
</div>
|
||
<div class="field">
|
||
<label>确认密码<span class="req">*</span></label>
|
||
<input class="ipt" id="phonePwd2" type="password" placeholder="请再次输入密码">
|
||
</div>
|
||
<button class="btn btn-primary" id="btnVerifyPhone" type="button">验证并下一步</button>
|
||
</div>
|
||
|
||
<!-- 账号注册 -->
|
||
<div class="mode-pane" id="paneAccount">
|
||
<div class="field">
|
||
<label>账号名<span class="req">*</span></label>
|
||
<input class="ipt" id="account" maxlength="32" placeholder="4-32位,字母开头,可含数字、下划线、中划线">
|
||
</div>
|
||
<div class="field">
|
||
<label>设置密码<span class="req">*</span></label>
|
||
<input class="ipt" id="accountPwd" type="password" placeholder="密码长度不少于6位">
|
||
</div>
|
||
<div class="field">
|
||
<label>确认密码<span class="req">*</span></label>
|
||
<input class="ipt" id="accountPwd2" type="password" placeholder="请再次输入密码">
|
||
</div>
|
||
<div class="field">
|
||
<label>微信身份核验<span class="req">*</span></label>
|
||
<div class="wechat-box" id="wechatBox">
|
||
<img id="wechatQr" alt="微信核验二维码" style="display:none">
|
||
<div class="scan-tip" id="scanTip">正在获取二维码…</div>
|
||
</div>
|
||
<div class="input-row" id="codeRow">
|
||
<input class="ipt" id="accountCode" maxlength="6" placeholder="公众号下发的 6 位验证码">
|
||
<button class="btn code-btn" id="btnQr" type="button">获取二维码</button>
|
||
</div>
|
||
<div class="hint" id="wxHint">点击「获取二维码」,用微信扫码完成身份核验。</div>
|
||
</div>
|
||
<button class="btn btn-primary" id="btnVerifyAccount" type="button">验证并下一步</button>
|
||
</div>
|
||
|
||
<div class="foot">已有账号?<a href="/auth/login" id="toLogin">直接登录</a> · <a href="/auth/forgot" id="toForgot">忘记密码</a></div>
|
||
</div>
|
||
|
||
<!-- ================= 第二步:企业归属 ================= -->
|
||
<div class="panel" id="panelStep2">
|
||
<div class="cards">
|
||
<!-- 卡片一:绑定企业 -->
|
||
<div class="choice-card">
|
||
<h3>🏢 绑定企业</h3>
|
||
<div class="desc">你的账号已由企业创建/企业管理员已开通企业,搜索并申请加入,由企业管理员审核。</div>
|
||
<div class="field">
|
||
<label>企业名称 / 编码<span class="req">*</span></label>
|
||
<div class="input-row">
|
||
<input class="ipt" id="tenantKeyword" placeholder="输入企业名称或编码搜索">
|
||
<button class="btn" id="btnSearchTenant" type="button">搜索</button>
|
||
</div>
|
||
<div class="search-list" id="tenantList" style="display:none"></div>
|
||
</div>
|
||
<button class="btn btn-primary card-submit" id="btnJoin" type="button" disabled>确定加入</button>
|
||
</div>
|
||
|
||
<!-- 卡片二:创建企业 -->
|
||
<div class="choice-card">
|
||
<h3>🏗️ 创建企业</h3>
|
||
<div class="desc">没有现成企业?填写企业资料创建新企业,由平台审核,通过后你将成为企业管理员。</div>
|
||
<div class="field"><label>公司名称<span class="req">*</span></label><input class="ipt" id="cName" maxlength="128" placeholder="营业执照上的公司全称"></div>
|
||
<div class="field"><label>统一社会信用代码<span class="req">*</span></label><input class="ipt" id="cCredit" maxlength="18" placeholder="18 位统一社会信用代码"></div>
|
||
<div class="field"><label>法定代表人姓名<span class="req">*</span></label><input class="ipt" id="cLegal" maxlength="64" placeholder="法定代表人姓名"></div>
|
||
<div class="field">
|
||
<label>营业执照电子档<span class="req">*</span>(PDF / JPG / PNG,≤5MB)</label>
|
||
<div class="file-row">
|
||
<button class="btn btn-ghost" id="btnPickFile" type="button">选择文件</button>
|
||
<span class="file-name" id="licenseName">未选择文件</span>
|
||
</div>
|
||
<input type="file" id="licenseFile" accept=".pdf,.jpg,.jpeg,.png" style="display:none">
|
||
</div>
|
||
<div class="field"><label>联系人<span class="req">*</span></label><input class="ipt" id="cContact" maxlength="64" placeholder="联系人姓名"></div>
|
||
<div class="field"><label>联系电话<span class="req">*</span></label><input class="ipt" id="cPhone" maxlength="11" placeholder="联系电话"></div>
|
||
<div class="field"><label>电子邮箱<span class="req">*</span></label><input class="ipt" id="cEmail" maxlength="128" placeholder="电子邮箱"></div>
|
||
<div class="field"><label>公司地址<span class="req">*</span></label><input class="ipt" id="cAddress" maxlength="255" placeholder="公司地址"></div>
|
||
<button class="btn btn-primary card-submit" id="btnCreate" type="button">确定创建</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="skip-row">
|
||
<button class="skip-link" id="btnSkip" type="button">暂不加入企业,直接完成注册 →</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 成功浮层 -->
|
||
<div class="done-mask" id="doneMask">
|
||
<div class="done-box">
|
||
<div class="icon">✓</div>
|
||
<h3 id="doneTitle">注册成功</h3>
|
||
<p id="doneText"></p>
|
||
<button class="btn btn-primary" id="btnGoLogin" type="button" style="width:100%">前往登录</button>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
(function () {
|
||
var app = document.getElementById('app');
|
||
var CLIENT_ID = app.getAttribute('data-client-id') || '';
|
||
var REDIRECT = app.getAttribute('data-redirect') || '';
|
||
|
||
// wxChannel:微信核验通道,mini=小程序码确认(优先)/ mp=公众号验证码(回退)
|
||
var state = { mode: 'phone', token: '', scene: '', qrTimer: null, selectedTid: 0, wxChannel: '' };
|
||
|
||
// ---------- 工具 ----------
|
||
function $(id) { return document.getElementById(id); }
|
||
function showMsg(text, ok) {
|
||
var el = $('globalMsg');
|
||
el.className = 'msg show ' + (ok ? 'msg-ok' : 'msg-err');
|
||
el.textContent = text;
|
||
if (!ok) { clearTimeout(showMsg._t); showMsg._t = setTimeout(function(){ el.className = 'msg'; }, 5000); }
|
||
}
|
||
function post(url, data, cb) {
|
||
fetch(url, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(data || {})
|
||
}).then(function (r) { return r.json(); }).then(function (res) { cb(res); })
|
||
.catch(function () { cb({ code: 500, msg: '网络异常,请稍后重试' }); });
|
||
}
|
||
function buildLoginURL(redirect) {
|
||
var qs = [];
|
||
if (CLIENT_ID) qs.push('client_id=' + encodeURIComponent(CLIENT_ID));
|
||
if (REDIRECT) qs.push('redirect=' + encodeURIComponent(REDIRECT));
|
||
var warn = '';
|
||
try { var u = new URL(redirect || '/auth/login', location.origin); warn = u.searchParams.get('warn') || ''; } catch (e) {}
|
||
if (warn) qs.push('warn=' + encodeURIComponent(warn));
|
||
return (qs.length ? '/auth/login?' + qs.join('&') : '/auth/login');
|
||
}
|
||
function goStep(n) {
|
||
$('panelStep1').classList.toggle('active', n === 1);
|
||
$('panelStep2').classList.toggle('active', n === 2);
|
||
$('stepDot1').className = 'step ' + (n === 1 ? 'active' : 'done');
|
||
$('stepDot1').querySelector('.dot').textContent = n === 1 ? '1' : '✓';
|
||
$('stepDot2').className = 'step ' + (n === 2 ? 'active' : '');
|
||
window.scrollTo(0, 0);
|
||
}
|
||
function done(title, text, redirect) {
|
||
$('doneTitle').textContent = title;
|
||
$('doneText').textContent = text;
|
||
$('btnGoLogin').onclick = function () { location.href = redirect || buildLoginURL(''); };
|
||
$('doneMask').classList.add('show');
|
||
}
|
||
|
||
// ---------- 第一步:方式切换 ----------
|
||
document.querySelectorAll('.mode-tab').forEach(function (tab) {
|
||
tab.addEventListener('click', function () {
|
||
document.querySelectorAll('.mode-tab').forEach(function (t) { t.classList.remove('on'); });
|
||
tab.classList.add('on');
|
||
state.mode = tab.getAttribute('data-mode');
|
||
$('panePhone').classList.toggle('on', state.mode === 'phone');
|
||
$('paneAccount').classList.toggle('on', state.mode === 'account');
|
||
});
|
||
});
|
||
|
||
// ---------- 手机号注册 ----------
|
||
var sendBtn = $('sendPhoneCode'), sendTimer = null;
|
||
sendBtn.addEventListener('click', function () {
|
||
var phone = $('phone').value.trim();
|
||
if (!/^1[3-9]\d{9}$/.test(phone)) { showMsg('请输入正确的手机号'); return; }
|
||
sendBtn.disabled = true;
|
||
post('/auth/register/send-code', { phone: phone }, function (res) {
|
||
if (res.code !== 200) {
|
||
showMsg(res.msg || '验证码发送失败');
|
||
sendBtn.disabled = false;
|
||
return;
|
||
}
|
||
showMsg('验证码已发送,请查收短信', true);
|
||
var sec = 60;
|
||
sendBtn.textContent = sec + 's 后重发';
|
||
sendTimer = setInterval(function () {
|
||
sec--;
|
||
if (sec <= 0) { clearInterval(sendTimer); sendBtn.textContent = '获取验证码'; sendBtn.disabled = false; }
|
||
else { sendBtn.textContent = sec + 's 后重发'; }
|
||
}, 1000);
|
||
});
|
||
});
|
||
|
||
$('btnVerifyPhone').addEventListener('click', function () {
|
||
var phone = $('phone').value.trim(), code = $('phoneCode').value.trim();
|
||
var pwd = $('phonePwd').value, pwd2 = $('phonePwd2').value;
|
||
if (!/^1[3-9]\d{9}$/.test(phone)) { showMsg('请输入正确的手机号'); return; }
|
||
if (!code) { showMsg('请输入短信验证码'); return; }
|
||
if (pwd.length < 6) { showMsg('密码长度不能少于6位'); return; }
|
||
if (pwd !== pwd2) { showMsg('两次输入的密码不一致'); return; }
|
||
var btn = this; btn.disabled = true;
|
||
post('/auth/register/verify', { mode: 'phone', phone: phone, code: code, password: pwd, confirm: pwd2 }, function (res) {
|
||
btn.disabled = false;
|
||
if (res.code !== 200) { showMsg(res.msg || '验证失败'); return; }
|
||
state.token = res.data.register_token;
|
||
showMsg('身份核验通过', true);
|
||
goStep(2);
|
||
});
|
||
});
|
||
|
||
// ---------- 账号注册:微信身份核验(小程序优先,不可用时回退公众号) ----------
|
||
$('btnQr').addEventListener('click', function () {
|
||
var btn = this; btn.disabled = true;
|
||
$('wechatBox').classList.add('show');
|
||
$('wechatQr').style.display = 'none';
|
||
$('scanTip').textContent = '正在获取二维码…';
|
||
$('scanTip').className = 'scan-tip';
|
||
startMiniVerify(function (ok) {
|
||
btn.disabled = false;
|
||
if (ok) return;
|
||
startMpVerify(btn); // 小程序未配置 / 小程序码生成失败 → 回退公众号验证码通道
|
||
});
|
||
});
|
||
|
||
function showQr(qrURL, tip) {
|
||
$('wechatQr').src = qrURL;
|
||
$('wechatQr').style.display = '';
|
||
$('scanTip').textContent = tip;
|
||
}
|
||
|
||
// 小程序方案:展示小程序码,用户在小程序内确认后无需填验证码
|
||
function startMiniVerify(next) {
|
||
fetch('/auth/register/mini/start').then(function (r) { return r.json(); }).then(function (res) {
|
||
if (res.code !== 200 || !res.data || !res.data.qr_url) { next(false); return; }
|
||
state.wxChannel = 'mini';
|
||
state.scene = res.data.scene || '';
|
||
$('codeRow').style.display = 'none';
|
||
$('wxHint').textContent = '用微信扫码,在小程序内确认后即可继续填写(无需验证码)。';
|
||
showQr(res.data.qr_url, '请用微信扫码,在小程序内确认注册');
|
||
next(true);
|
||
startQrPolling();
|
||
}).catch(function () { next(false); });
|
||
}
|
||
|
||
// 公众号回退:优先带参二维码;生成失败时展示「关注二维码」,
|
||
// 用户扫码关注后在公众号内发送「注册」照样能收到验证码
|
||
function startMpVerify(btn) {
|
||
fetch('/auth/register/wechat/start').then(function (r) { return r.json(); }).then(function (res) {
|
||
btn.disabled = false;
|
||
var d = res.data || {};
|
||
state.wxChannel = 'mp';
|
||
state.scene = d.scene || '';
|
||
$('codeRow').style.display = '';
|
||
$('wxHint').textContent = '扫码关注公众号后,在公众号内发送「注册」即可收到 6 位验证码。';
|
||
var qr = d.qr_url || d.follow_qrcode || '';
|
||
if (qr) {
|
||
showQr(qr, d.qr_url ? '请用微信扫码并关注公众号' : '扫码关注公众号后,在公众号内发送「注册」获取验证码');
|
||
if (d.qr_url) startQrPolling(); // 关注二维码不带 scene,无需轮询
|
||
} else if (res.code === 200) {
|
||
$('scanTip').textContent = d.notice || '无法展示二维码:请在公众号内直接发送「注册」获取验证码';
|
||
} else {
|
||
$('scanTip').textContent = '微信核验暂不可用,请改用手机号验证码注册';
|
||
}
|
||
}).catch(function () { btn.disabled = false; $('scanTip').textContent = '网络异常,请重试'; });
|
||
}
|
||
|
||
function startQrPolling() {
|
||
if (state.qrTimer) clearInterval(state.qrTimer);
|
||
if (!state.scene) return;
|
||
state.qrTimer = setInterval(function () {
|
||
var mini = state.wxChannel === 'mini';
|
||
var url = mini ? '/auth/register/mini/status' : '/auth/register/wechat/status';
|
||
fetch(url + '?scene=' + encodeURIComponent(state.scene))
|
||
.then(function (r) { return r.json(); })
|
||
.then(function (res) {
|
||
var ok = res.code === 200 && res.data && (mini ? res.data.confirmed : res.data.scanned);
|
||
if (ok) {
|
||
$('scanTip').textContent = mini
|
||
? '已在小程序内确认,请继续填写下方信息'
|
||
: '已扫码,验证码已发送到公众号,请填入下方输入框';
|
||
$('scanTip').className = 'scan-tip ok';
|
||
clearInterval(state.qrTimer);
|
||
}
|
||
}).catch(function () {});
|
||
}, 2000);
|
||
}
|
||
|
||
$('btnVerifyAccount').addEventListener('click', function () {
|
||
var account = $('account').value.trim(), code = $('accountCode').value.trim();
|
||
var pwd = $('accountPwd').value, pwd2 = $('accountPwd2').value;
|
||
if (!/^[A-Za-z][A-Za-z0-9_-]{3,31}$/.test(account)) { showMsg('账号需 4-32 位,字母开头,仅含字母/数字/下划线/中划线'); return; }
|
||
if (pwd.length < 6) { showMsg('密码长度不能少于6位'); return; }
|
||
if (pwd !== pwd2) { showMsg('两次输入的密码不一致'); return; }
|
||
if (!state.wxChannel) { showMsg('请先点击「获取二维码」完成微信核验'); return; }
|
||
var mini = state.wxChannel === 'mini';
|
||
// 小程序通道必须持有已确认的会话;公众号通道允许无 scene(用户手填公众号下发的验证码)
|
||
if (mini && !state.scene) { showMsg('请重新获取二维码并在小程序内完成确认'); return; }
|
||
if (!mini && !code) { showMsg('请输入公众号下发的验证码(在公众号内发送「注册」获取)'); return; }
|
||
var btn = this; btn.disabled = true;
|
||
post('/auth/register/verify', {
|
||
mode: mini ? 'mini' : 'account',
|
||
account: account,
|
||
scene: state.scene,
|
||
code: code,
|
||
password: pwd,
|
||
confirm: pwd2
|
||
}, function (res) {
|
||
btn.disabled = false;
|
||
if (res.code !== 200) { showMsg(res.msg || '验证失败'); return; }
|
||
state.token = res.data.register_token;
|
||
if (state.qrTimer) clearInterval(state.qrTimer);
|
||
showMsg('身份核验通过', true);
|
||
goStep(2);
|
||
});
|
||
});
|
||
|
||
// ---------- 第二步:绑定企业 ----------
|
||
$('btnSearchTenant').addEventListener('click', function () {
|
||
var kw = $('tenantKeyword').value.trim();
|
||
if (!kw) { showMsg('请输入企业名称或编码'); return; }
|
||
var list = $('tenantList');
|
||
list.style.display = '';
|
||
list.innerHTML = '<div class="search-empty">搜索中…</div>';
|
||
state.selectedTid = 0;
|
||
$('btnJoin').disabled = true;
|
||
fetch('/auth/register/tenant/search?keyword=' + encodeURIComponent(kw))
|
||
.then(function (r) { return r.json(); })
|
||
.then(function (res) {
|
||
if (res.code !== 200 || !res.data || !res.data.list || !res.data.list.length) {
|
||
list.innerHTML = '<div class="search-empty">未找到匹配企业,请确认名称或编码</div>';
|
||
return;
|
||
}
|
||
list.innerHTML = '';
|
||
res.data.list.forEach(function (t) {
|
||
var item = document.createElement('div');
|
||
item.className = 'search-item';
|
||
item.innerHTML = '<span>' + t.tenant_name.replace(/</g, '<') + '</span><span class="code">' + t.tenant_code + '</span>';
|
||
item.addEventListener('click', function () {
|
||
list.querySelectorAll('.search-item').forEach(function (x) { x.classList.remove('sel'); });
|
||
item.classList.add('sel');
|
||
state.selectedTid = t.tid;
|
||
$('btnJoin').disabled = false;
|
||
});
|
||
list.appendChild(item);
|
||
});
|
||
}).catch(function () { list.innerHTML = '<div class="search-empty">搜索失败,请重试</div>'; });
|
||
});
|
||
|
||
// 第二步提交失败:注册会话过期时退回第一步重新核验(此时账号尚未占用,可重新注册)
|
||
function failStep2(res, fallback) {
|
||
var msg = (res && res.msg) || fallback || '提交失败';
|
||
if (/注册会话已过期|注册会话已失效/.test(msg)) {
|
||
state.token = '';
|
||
goStep(1);
|
||
showMsg(msg + ',请重新完成身份核验');
|
||
return;
|
||
}
|
||
showMsg(msg);
|
||
}
|
||
|
||
$('btnJoin').addEventListener('click', function () {
|
||
if (!state.selectedTid) { showMsg('请先选择企业'); return; }
|
||
var btn = this; btn.disabled = true;
|
||
post('/auth/register/apply-join', { token: state.token, tid: state.selectedTid }, function (res) {
|
||
btn.disabled = false;
|
||
if (res.code !== 200) { failStep2(res, '提交失败'); return; }
|
||
done('申请已提交', '加入企业的申请已提交,请等待企业管理员在后台审核,审核通过后即可登录。', res.redirect || buildLoginURL(''));
|
||
});
|
||
});
|
||
|
||
// ---------- 第二步:创建企业 ----------
|
||
$('btnPickFile').addEventListener('click', function () { $('licenseFile').click(); });
|
||
$('licenseFile').addEventListener('change', function () {
|
||
var f = this.files && this.files[0];
|
||
if (!f) return;
|
||
if (!/\.(pdf|jpg|jpeg|png)$/i.test(f.name)) { showMsg('仅支持 PDF、JPG、PNG 格式'); this.value = ''; return; }
|
||
if (f.size > 5 * 1024 * 1024) { showMsg('文件大小不能超过 5MB'); this.value = ''; return; }
|
||
var nameEl = $('licenseName');
|
||
nameEl.textContent = '上传中:' + f.name;
|
||
nameEl.className = 'file-name';
|
||
var fd = new FormData();
|
||
fd.append('file', f);
|
||
fetch('/auth/register/upload-license', { method: 'POST', body: fd })
|
||
.then(function (r) { return r.json(); })
|
||
.then(function (res) {
|
||
if (res.code !== 200) { nameEl.textContent = '未选择文件'; showMsg(res.msg || '上传失败'); return; }
|
||
nameEl.textContent = '已上传:' + res.data.name;
|
||
nameEl.className = 'file-name ok';
|
||
nameEl.setAttribute('data-url', res.data.url);
|
||
}).catch(function () { nameEl.textContent = '未选择文件'; showMsg('上传失败,请重试'); });
|
||
});
|
||
|
||
$('btnCreate').addEventListener('click', function () {
|
||
if (!state.token) { failStep2({ msg: '注册会话已失效' }, '注册会话已失效'); return; }
|
||
var form = {
|
||
token: state.token,
|
||
tenant_name: $('cName').value.trim(),
|
||
credit_code: $('cCredit').value.trim(),
|
||
legal_person: $('cLegal').value.trim(),
|
||
license_file: $('licenseName').getAttribute('data-url') || '',
|
||
contact_person: $('cContact').value.trim(),
|
||
contact_phone: $('cPhone').value.trim(),
|
||
contact_email: $('cEmail').value.trim(),
|
||
address: $('cAddress').value.trim()
|
||
};
|
||
if (!form.tenant_name) { showMsg('请填写公司名称'); return; }
|
||
if (!/^[0-9A-Z]{18}$/.test(form.credit_code.toUpperCase())) { showMsg('请填写 18 位统一社会信用代码'); return; }
|
||
form.credit_code = form.credit_code.toUpperCase();
|
||
if (!form.legal_person) { showMsg('请填写法定代表人姓名'); return; }
|
||
if (!form.license_file) { showMsg('请上传营业执照电子档'); return; }
|
||
if (!form.contact_person) { showMsg('请填写联系人'); return; }
|
||
if (!/^1[3-9]\d{9}$/.test(form.contact_phone)) { showMsg('请填写正确的联系电话'); return; }
|
||
if (!form.contact_email || form.contact_email.indexOf('@') < 0) { showMsg('请填写正确的电子邮箱'); return; }
|
||
if (!form.address) { showMsg('请填写公司地址'); return; }
|
||
|
||
if (!confirm('提交后,当前注册账号将成为「' + form.tenant_name + '」的管理员账号,该绑定不可更改。确定提交并等待平台审核吗?')) return;
|
||
|
||
var btn = this; btn.disabled = true;
|
||
post('/auth/register/apply-create', form, function (res) {
|
||
btn.disabled = false;
|
||
if (res.code !== 200) { failStep2(res, '提交失败'); return; }
|
||
done('申请已提交', '创建企业的申请已提交,请等待平台审核;审核通过后当前账号将成为企业管理员账号。', res.redirect || buildLoginURL(''));
|
||
});
|
||
});
|
||
|
||
// ---------- 暂不加入 ----------
|
||
$('btnSkip').addEventListener('click', function () {
|
||
if (!confirm('暂不加入企业将直接完成注册,之后可在登录后申请加入。确定吗?')) return;
|
||
post('/auth/register/finish', { token: state.token }, function (res) {
|
||
if (res.code !== 200) { failStep2(res, '操作失败'); return; }
|
||
done('注册成功', '账号已创建。你当前未加入任何企业,可稍后申请加入。', res.redirect || buildLoginURL(''));
|
||
});
|
||
});
|
||
|
||
// 回登录页时透传 client_id/redirect
|
||
(function keepOAuthParams() {
|
||
if (!CLIENT_ID && !REDIRECT) return;
|
||
var suffix = (CLIENT_ID ? 'client_id=' + encodeURIComponent(CLIENT_ID) : '') +
|
||
(REDIRECT ? (CLIENT_ID ? '&' : '') + 'redirect=' + encodeURIComponent(REDIRECT) : '');
|
||
['toLogin', 'toForgot'].forEach(function (id) {
|
||
var a = $(id);
|
||
if (a) a.href = a.getAttribute('href') + (a.getAttribute('href').indexOf('?') >= 0 ? '&' : '?') + suffix;
|
||
});
|
||
})();
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|