caixianzhi pc接口修改完成
This commit is contained in:
@@ -96,6 +96,12 @@
|
|||||||
<a href="reg.html">立即注册</a>
|
<a href="reg.html">立即注册</a>
|
||||||
<a href="repwd.html">忘记密码</a>
|
<a href="repwd.html">忘记密码</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="wxdl auth-wechat-login">
|
||||||
|
<a href="javascript:void(0)">
|
||||||
|
<img class="auth-wechat-icon" src="../public/img/pay-wechat.png" alt="微信" />
|
||||||
|
<span>微信登录</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -113,6 +119,7 @@
|
|||||||
let layerInstance = null;
|
let layerInstance = null;
|
||||||
let currentCaptchaId = "";
|
let currentCaptchaId = "";
|
||||||
let currentVerifyType = "captcha";
|
let currentVerifyType = "captcha";
|
||||||
|
let currentSlideToken = "";
|
||||||
let currentSlideUuid = "";
|
let currentSlideUuid = "";
|
||||||
let currentSlideX = 0;
|
let currentSlideX = 0;
|
||||||
|
|
||||||
@@ -142,6 +149,7 @@
|
|||||||
|
|
||||||
function refreshCaptcha() {
|
function refreshCaptcha() {
|
||||||
currentCaptchaId = "";
|
currentCaptchaId = "";
|
||||||
|
currentSlideToken = "";
|
||||||
currentSlideUuid = "";
|
currentSlideUuid = "";
|
||||||
currentSlideX = 0;
|
currentSlideX = 0;
|
||||||
$("#captchaId").val("");
|
$("#captchaId").val("");
|
||||||
@@ -185,6 +193,9 @@
|
|||||||
function completeSlideCaptcha(result) {
|
function completeSlideCaptcha(result) {
|
||||||
const data = result || {};
|
const data = result || {};
|
||||||
const nested = data.data || {};
|
const nested = data.data || {};
|
||||||
|
currentSlideToken =
|
||||||
|
data.slideToken || data.token || data.verifyToken ||
|
||||||
|
nested.slideToken || nested.token || nested.verifyToken || "";
|
||||||
currentSlideUuid =
|
currentSlideUuid =
|
||||||
data.slideUuid || data.uuid || nested.slideUuid || nested.uuid || "";
|
data.slideUuid || data.uuid || nested.slideUuid || nested.uuid || "";
|
||||||
currentSlideX =
|
currentSlideX =
|
||||||
@@ -209,6 +220,7 @@
|
|||||||
onSuccess: completeSlideCaptcha,
|
onSuccess: completeSlideCaptcha,
|
||||||
onCancel: function () {
|
onCancel: function () {
|
||||||
currentSlideUuid = "";
|
currentSlideUuid = "";
|
||||||
|
currentSlideToken = "";
|
||||||
currentSlideX = 0;
|
currentSlideX = 0;
|
||||||
},
|
},
|
||||||
onError: function (error) {
|
onError: function (error) {
|
||||||
@@ -274,6 +286,7 @@
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
verifyType: "slide",
|
verifyType: "slide",
|
||||||
|
slideToken: currentSlideToken,
|
||||||
slideUuid: currentSlideUuid,
|
slideUuid: currentSlideUuid,
|
||||||
slideX: currentSlideX,
|
slideX: currentSlideX,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -146,6 +146,7 @@
|
|||||||
let layerInstance = null;
|
let layerInstance = null;
|
||||||
let currentCaptchaId = "";
|
let currentCaptchaId = "";
|
||||||
let currentVerifyType = "captcha";
|
let currentVerifyType = "captcha";
|
||||||
|
let currentSlideToken = "";
|
||||||
let currentSlideUuid = "";
|
let currentSlideUuid = "";
|
||||||
let currentSlideX = 0;
|
let currentSlideX = 0;
|
||||||
let pendingSmsAfterVerify = false;
|
let pendingSmsAfterVerify = false;
|
||||||
@@ -177,6 +178,7 @@
|
|||||||
|
|
||||||
function refreshCaptcha() {
|
function refreshCaptcha() {
|
||||||
currentCaptchaId = "";
|
currentCaptchaId = "";
|
||||||
|
currentSlideToken = "";
|
||||||
currentSlideUuid = "";
|
currentSlideUuid = "";
|
||||||
currentSlideX = 0;
|
currentSlideX = 0;
|
||||||
$("#captchaId").val("");
|
$("#captchaId").val("");
|
||||||
@@ -218,6 +220,9 @@
|
|||||||
function completeSlideCaptcha(result) {
|
function completeSlideCaptcha(result) {
|
||||||
const data = result || {};
|
const data = result || {};
|
||||||
const nested = data.data || {};
|
const nested = data.data || {};
|
||||||
|
currentSlideToken =
|
||||||
|
data.slideToken || data.token || data.verifyToken ||
|
||||||
|
nested.slideToken || nested.token || nested.verifyToken || "";
|
||||||
currentSlideUuid =
|
currentSlideUuid =
|
||||||
data.slideUuid || data.uuid || nested.slideUuid || nested.uuid || "";
|
data.slideUuid || data.uuid || nested.slideUuid || nested.uuid || "";
|
||||||
currentSlideX =
|
currentSlideX =
|
||||||
@@ -247,6 +252,7 @@
|
|||||||
onSuccess: completeSlideCaptcha,
|
onSuccess: completeSlideCaptcha,
|
||||||
onCancel: function () {
|
onCancel: function () {
|
||||||
currentSlideUuid = "";
|
currentSlideUuid = "";
|
||||||
|
currentSlideToken = "";
|
||||||
currentSlideX = 0;
|
currentSlideX = 0;
|
||||||
},
|
},
|
||||||
onError: function (error) {
|
onError: function (error) {
|
||||||
@@ -342,6 +348,7 @@
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
verifyType: "slide",
|
verifyType: "slide",
|
||||||
|
slideToken: currentSlideToken,
|
||||||
slideUuid: currentSlideUuid,
|
slideUuid: currentSlideUuid,
|
||||||
slideX: currentSlideX,
|
slideX: currentSlideX,
|
||||||
};
|
};
|
||||||
@@ -408,6 +415,8 @@
|
|||||||
|
|
||||||
function buildRegisterData() {
|
function buildRegisterData() {
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const inviteCode =
|
||||||
|
params.get("inviteCode") || params.get("referralCode") || "";
|
||||||
const referrerId =
|
const referrerId =
|
||||||
(referralPreviewData &&
|
(referralPreviewData &&
|
||||||
(referralPreviewData.referrerId ||
|
(referralPreviewData.referrerId ||
|
||||||
@@ -416,6 +425,7 @@
|
|||||||
params.get("referrerId") ||
|
params.get("referrerId") ||
|
||||||
params.get("pid") ||
|
params.get("pid") ||
|
||||||
0;
|
0;
|
||||||
|
const invitePayload = inviteCode ? { inviteCode: inviteCode } : {};
|
||||||
return Object.assign(
|
return Object.assign(
|
||||||
{
|
{
|
||||||
phoneNumber: $("#phone").val().trim(),
|
phoneNumber: $("#phone").val().trim(),
|
||||||
@@ -424,6 +434,7 @@
|
|||||||
nickname: $("#nickname").val().trim(),
|
nickname: $("#nickname").val().trim(),
|
||||||
referrerId: referrerId,
|
referrerId: referrerId,
|
||||||
},
|
},
|
||||||
|
invitePayload,
|
||||||
getVerifyParams(false) || {},
|
getVerifyParams(false) || {},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-7
@@ -127,6 +127,7 @@
|
|||||||
let layerInstance = null;
|
let layerInstance = null;
|
||||||
let currentCaptchaId = "";
|
let currentCaptchaId = "";
|
||||||
let currentVerifyType = "captcha";
|
let currentVerifyType = "captcha";
|
||||||
|
let currentSlideToken = "";
|
||||||
let currentSlideUuid = "";
|
let currentSlideUuid = "";
|
||||||
let currentSlideX = 0;
|
let currentSlideX = 0;
|
||||||
let pendingSmsAfterVerify = false;
|
let pendingSmsAfterVerify = false;
|
||||||
@@ -157,6 +158,7 @@
|
|||||||
|
|
||||||
function refreshCaptcha() {
|
function refreshCaptcha() {
|
||||||
currentCaptchaId = "";
|
currentCaptchaId = "";
|
||||||
|
currentSlideToken = "";
|
||||||
currentSlideUuid = "";
|
currentSlideUuid = "";
|
||||||
currentSlideX = 0;
|
currentSlideX = 0;
|
||||||
$("#captchaId").val("");
|
$("#captchaId").val("");
|
||||||
@@ -198,6 +200,9 @@
|
|||||||
function completeSlideCaptcha(result) {
|
function completeSlideCaptcha(result) {
|
||||||
const data = result || {};
|
const data = result || {};
|
||||||
const nested = data.data || {};
|
const nested = data.data || {};
|
||||||
|
currentSlideToken =
|
||||||
|
data.slideToken || data.token || data.verifyToken ||
|
||||||
|
nested.slideToken || nested.token || nested.verifyToken || "";
|
||||||
currentSlideUuid =
|
currentSlideUuid =
|
||||||
data.slideUuid || data.uuid || nested.slideUuid || nested.uuid || "";
|
data.slideUuid || data.uuid || nested.slideUuid || nested.uuid || "";
|
||||||
currentSlideX =
|
currentSlideX =
|
||||||
@@ -227,6 +232,7 @@
|
|||||||
onSuccess: completeSlideCaptcha,
|
onSuccess: completeSlideCaptcha,
|
||||||
onCancel: function () {
|
onCancel: function () {
|
||||||
currentSlideUuid = "";
|
currentSlideUuid = "";
|
||||||
|
currentSlideToken = "";
|
||||||
currentSlideX = 0;
|
currentSlideX = 0;
|
||||||
},
|
},
|
||||||
onError: function (error) {
|
onError: function (error) {
|
||||||
@@ -312,6 +318,7 @@
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
verifyType: "slide",
|
verifyType: "slide",
|
||||||
|
slideToken: currentSlideToken,
|
||||||
slideUuid: currentSlideUuid,
|
slideUuid: currentSlideUuid,
|
||||||
slideX: currentSlideX,
|
slideX: currentSlideX,
|
||||||
};
|
};
|
||||||
@@ -334,7 +341,7 @@
|
|||||||
const verifyParams = getVerifyParams(true);
|
const verifyParams = getVerifyParams(true);
|
||||||
if (!verifyParams) return;
|
if (!verifyParams) return;
|
||||||
$("#sendSmsBtn").prop("disabled", true);
|
$("#sendSmsBtn").prop("disabled", true);
|
||||||
AuthPageUtil.sendSmsCode(
|
AuthPageUtil.sendForgotPasswordSmsCode(
|
||||||
Object.assign(
|
Object.assign(
|
||||||
{ phoneNumber: $("#phone").val().trim() },
|
{ phoneNumber: $("#phone").val().trim() },
|
||||||
verifyParams,
|
verifyParams,
|
||||||
@@ -384,14 +391,36 @@
|
|||||||
|
|
||||||
function submitResetPassword() {
|
function submitResetPassword() {
|
||||||
const payload = buildResetData();
|
const payload = buildResetData();
|
||||||
console.warn(
|
$("#resetBtn").prop("disabled", true).text("重置中...");
|
||||||
"找回密码提交参数已准备好,但最新接口文档暂未提供提交接口:",
|
ApiClient.post(ApiClient.API.authForgotPasswordReset, payload, {
|
||||||
payload,
|
publicRequest: true,
|
||||||
|
})
|
||||||
|
.then(function (res) {
|
||||||
|
if (ApiClient.isSuccess(res)) {
|
||||||
|
layerInstance.msg(
|
||||||
|
"密码重置成功,即将前往登录",
|
||||||
|
{ icon: 1, time: 1200 },
|
||||||
|
function () {
|
||||||
|
window.location.href = "login.html";
|
||||||
|
},
|
||||||
);
|
);
|
||||||
layerInstance.alert(
|
return;
|
||||||
"最新接口文档暂未提供找回密码提交接口。页面流程、短信验证码和验证参数已按参考项目准备好,等后端补接口后即可接入提交。",
|
}
|
||||||
{ title: "暂不能提交" },
|
layerInstance.msg((res && res.msg) || "密码重置失败,请稍后重试", {
|
||||||
|
icon: 2,
|
||||||
|
});
|
||||||
|
refreshCaptcha();
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
layerInstance.msg(
|
||||||
|
"密码重置请求失败:" + ((error && error.message) || "网络错误"),
|
||||||
|
{ icon: 2 },
|
||||||
);
|
);
|
||||||
|
refreshCaptcha();
|
||||||
|
})
|
||||||
|
.finally(function () {
|
||||||
|
$("#resetBtn").prop("disabled", false).text("确认重置");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
layui.use(["layer", "jquery"], function () {
|
layui.use(["layer", "jquery"], function () {
|
||||||
|
|||||||
@@ -0,0 +1,904 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>3D研究室 - 微信登录</title>
|
||||||
|
<script src="../config.js"></script>
|
||||||
|
<script src="../utils/ApiClient.js"></script>
|
||||||
|
<script src="../utils/AuthPageUtil.js"></script>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
color: #1f2933;
|
||||||
|
font-family:
|
||||||
|
-apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei",
|
||||||
|
sans-serif;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 72% 18%, rgba(255, 190, 98, 0.45), rgba(255, 190, 98, 0) 32%),
|
||||||
|
linear-gradient(135deg, #ff7a1a 0%, #ff6f12 54%, #ff8a23 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 40px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
width: 420px;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 28px 30px 30px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 18px 48px rgba(124, 38, 4, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #de2103;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 800;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
min-height: 22px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
color: #667085;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin: 14px 0 20px;
|
||||||
|
color: #de2103;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border: 2px solid #f5c7bd;
|
||||||
|
border-top-color: #de2103;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bind-form {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
color: #344054;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input,
|
||||||
|
.sms-input {
|
||||||
|
width: 100%;
|
||||||
|
height: 42px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border: 1px solid #d9dee7;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: #1f2933;
|
||||||
|
font-size: 14px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input:focus,
|
||||||
|
.sms-input:focus {
|
||||||
|
border-color: #de2103;
|
||||||
|
box-shadow: 0 0 0 3px rgba(222, 33, 3, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sms-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha-row {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha-box {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha-box .input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha-image {
|
||||||
|
flex: 0 0 108px;
|
||||||
|
height: 42px;
|
||||||
|
border: 1px solid #d9dee7;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #f8fafc;
|
||||||
|
cursor: pointer;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sms-input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sms-button {
|
||||||
|
flex: 0 0 108px;
|
||||||
|
height: 42px;
|
||||||
|
border: 1px solid #de2103;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #fff8f6;
|
||||||
|
color: #de2103;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sms-button:disabled,
|
||||||
|
.submit:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit {
|
||||||
|
width: 100%;
|
||||||
|
height: 42px;
|
||||||
|
margin-top: 20px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: linear-gradient(180deg, #ff4a22 0%, #de2103 100%);
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid #f2d2ca;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #fff8f6;
|
||||||
|
color: #8a4b42;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
display: none;
|
||||||
|
margin-top: 14px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid #f4c7c7;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #fff5f5;
|
||||||
|
color: #b42318;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions a,
|
||||||
|
.actions button {
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: #de2103;
|
||||||
|
font-size: 13px;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="page">
|
||||||
|
<div class="card">
|
||||||
|
<div class="brand">3D研究室</div>
|
||||||
|
<div class="title" id="title">微信登录</div>
|
||||||
|
<div class="desc" id="desc">正在处理微信授权,请稍候...</div>
|
||||||
|
<div class="status" id="status">
|
||||||
|
<span class="spinner"></span>
|
||||||
|
<span id="statusText">正在登录</span>
|
||||||
|
</div>
|
||||||
|
<div class="error" id="errorBox"></div>
|
||||||
|
|
||||||
|
<form class="bind-form" id="bindForm" autocomplete="off">
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="phoneInput">绑定手机号</label>
|
||||||
|
<input
|
||||||
|
class="input"
|
||||||
|
id="phoneInput"
|
||||||
|
maxlength="11"
|
||||||
|
placeholder="请输入手机号"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="smsInput">短信验证码</label>
|
||||||
|
<div class="sms-row">
|
||||||
|
<input
|
||||||
|
class="sms-input"
|
||||||
|
id="smsInput"
|
||||||
|
maxlength="6"
|
||||||
|
placeholder="请输入验证码"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
<button class="sms-button" id="sendSmsButton" type="button">
|
||||||
|
获取验证码
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="submit" id="bindButton" type="submit">
|
||||||
|
绑定并登录
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="tips">
|
||||||
|
该微信还没有绑定手机号,请先完成绑定。手机号不存在时会创建账号,已存在时会绑定到该账号。
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<a href="login.html">返回登录</a>
|
||||||
|
<button id="retryButton" type="button">重试</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const socialContext = {
|
||||||
|
source: params.get("source") || "wechat_open",
|
||||||
|
code: params.get("code") || params.get("socialCode") || "",
|
||||||
|
state: params.get("state") || params.get("socialState") || "",
|
||||||
|
bindToken: params.get("bindToken") || "",
|
||||||
|
bindVerifyType: "slide",
|
||||||
|
captchaUuid: "",
|
||||||
|
};
|
||||||
|
const ACCOUNT_BIND_MODE = "account_bind";
|
||||||
|
const ACCOUNT_BIND_MAX_AGE = 10 * 60 * 1000;
|
||||||
|
|
||||||
|
const $ = (selector) => document.querySelector(selector);
|
||||||
|
const status = $("#status");
|
||||||
|
const statusText = $("#statusText");
|
||||||
|
const errorBox = $("#errorBox");
|
||||||
|
const bindForm = $("#bindForm");
|
||||||
|
const sendSmsButton = $("#sendSmsButton");
|
||||||
|
const bindButton = $("#bindButton");
|
||||||
|
let bindVerifyLoaded = false;
|
||||||
|
|
||||||
|
function socialHeaders() {
|
||||||
|
return { clientid: ApiClient.CLIENT_ID };
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStatus(text, loading = true) {
|
||||||
|
status.style.display = "flex";
|
||||||
|
status.querySelector(".spinner").style.display = loading ? "" : "none";
|
||||||
|
statusText.textContent = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeMessage(message, fallback) {
|
||||||
|
const text = String(message || "").trim();
|
||||||
|
const map = {
|
||||||
|
"rate.limiter.message": "操作太频繁,请稍后再试",
|
||||||
|
};
|
||||||
|
|
||||||
|
return map[text] || text || fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showError(message) {
|
||||||
|
errorBox.textContent = normalizeMessage(message, "登录失败,请稍后重试");
|
||||||
|
errorBox.style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideError() {
|
||||||
|
errorBox.textContent = "";
|
||||||
|
errorBox.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getToken(data) {
|
||||||
|
return (
|
||||||
|
data.access_token ||
|
||||||
|
data.accessToken ||
|
||||||
|
data.token ||
|
||||||
|
data.access_token_value ||
|
||||||
|
""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBindToken(rawData) {
|
||||||
|
const data = rawData || {};
|
||||||
|
const nested = data.data || {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
data.bindToken ||
|
||||||
|
data.bind_token ||
|
||||||
|
data.phoneBindToken ||
|
||||||
|
data.socialBindToken ||
|
||||||
|
nested.bindToken ||
|
||||||
|
nested.bind_token ||
|
||||||
|
nested.phoneBindToken ||
|
||||||
|
nested.socialBindToken ||
|
||||||
|
""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveLoginData(rawData) {
|
||||||
|
const data = rawData || {};
|
||||||
|
const token = getToken(data);
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem("token", token);
|
||||||
|
localStorage.setItem("access_token", data.access_token || token);
|
||||||
|
localStorage.setItem("userInfo", JSON.stringify(data));
|
||||||
|
localStorage.setItem("accountInfo", JSON.stringify(data));
|
||||||
|
sessionStorage.removeItem("authExpiredRedirecting");
|
||||||
|
sessionStorage.removeItem("authExpiredMessage");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function homeUrl() {
|
||||||
|
if (
|
||||||
|
location.hostname === "localhost" ||
|
||||||
|
location.hostname === "127.0.0.1" ||
|
||||||
|
location.port
|
||||||
|
) {
|
||||||
|
return "index.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
function redirectHome() {
|
||||||
|
setStatus("登录成功,正在跳转...", false);
|
||||||
|
window.setTimeout(() => {
|
||||||
|
window.location.replace(homeUrl());
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readStoredValue(key) {
|
||||||
|
try {
|
||||||
|
return sessionStorage.getItem(key) || localStorage.getItem(key) || "";
|
||||||
|
} catch (e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeStoredValue(key) {
|
||||||
|
try {
|
||||||
|
sessionStorage.removeItem(key);
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
localStorage.removeItem(key);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAccountBindMode() {
|
||||||
|
removeStoredValue("wechatSocialMode");
|
||||||
|
removeStoredValue("wechatSocialReturnUrl");
|
||||||
|
removeStoredValue("wechatSocialStartedAt");
|
||||||
|
}
|
||||||
|
|
||||||
|
function markAccountBindSuccess() {
|
||||||
|
try {
|
||||||
|
sessionStorage.setItem("wechatAccountBindSuccess", "1");
|
||||||
|
sessionStorage.setItem("wechatAccountBindSuccessAt", String(Date.now()));
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAccountBindReturnUrl() {
|
||||||
|
const stored = readStoredValue("wechatSocialReturnUrl");
|
||||||
|
|
||||||
|
if (stored && stored.charAt(0) === "/" && !/^\/\//.test(stored)) {
|
||||||
|
return stored;
|
||||||
|
}
|
||||||
|
|
||||||
|
return location.hostname === "localhost" || location.hostname === "127.0.0.1" || location.port
|
||||||
|
? "usercenter.html"
|
||||||
|
: "/html/usercenter.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAccountBindCallback() {
|
||||||
|
const mode = readStoredValue("wechatSocialMode");
|
||||||
|
const startedAt = Number(readStoredValue("wechatSocialStartedAt") || 0);
|
||||||
|
const isFresh = !startedAt || Date.now() - startedAt <= ACCOUNT_BIND_MAX_AGE;
|
||||||
|
const token = localStorage.getItem("token") || localStorage.getItem("access_token") || "";
|
||||||
|
|
||||||
|
return mode === ACCOUNT_BIND_MODE && isFresh && Boolean(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
function redirectAccountBindDone() {
|
||||||
|
const returnUrl = getAccountBindReturnUrl();
|
||||||
|
markAccountBindSuccess();
|
||||||
|
clearAccountBindMode();
|
||||||
|
setStatus("微信绑定成功,正在返回个人中心...", false);
|
||||||
|
window.setTimeout(() => {
|
||||||
|
window.location.replace(returnUrl);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function socialBindAccount() {
|
||||||
|
hideError();
|
||||||
|
bindForm.style.display = "none";
|
||||||
|
setStatus("正在绑定微信");
|
||||||
|
|
||||||
|
if (!socialContext.code || !socialContext.state) {
|
||||||
|
status.style.display = "none";
|
||||||
|
showError("绑定参数缺失,请回到个人中心重新发起微信绑定");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await ApiClient.post(
|
||||||
|
ApiClient.API.authSocialBind,
|
||||||
|
{
|
||||||
|
source: socialContext.source,
|
||||||
|
socialCode: socialContext.code,
|
||||||
|
socialState: socialContext.state,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: socialHeaders(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (ApiClient.isSuccess(res)) {
|
||||||
|
redirectAccountBindDone();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
status.style.display = "none";
|
||||||
|
showError((res && res.msg) || "微信绑定失败,请回到个人中心重新发起绑定");
|
||||||
|
}
|
||||||
|
|
||||||
|
function isBindRequired(res) {
|
||||||
|
const data = ApiClient.pickData(res) || {};
|
||||||
|
const bindToken = getBindToken({ ...data, data: res && res.data });
|
||||||
|
const status = String(
|
||||||
|
data.loginStatus ||
|
||||||
|
data.login_status ||
|
||||||
|
data.status ||
|
||||||
|
data.state ||
|
||||||
|
data.result ||
|
||||||
|
data.code ||
|
||||||
|
"",
|
||||||
|
).toUpperCase();
|
||||||
|
const message = String(
|
||||||
|
(res && (res.msg || res.message)) ||
|
||||||
|
data.msg ||
|
||||||
|
data.message ||
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
|
||||||
|
return Boolean(
|
||||||
|
data.bindRequired ||
|
||||||
|
data.needBind ||
|
||||||
|
data.needBindPhone ||
|
||||||
|
data.needPhoneBind ||
|
||||||
|
data.phoneBindRequired ||
|
||||||
|
status === "NEED_BIND_PHONE" ||
|
||||||
|
status === "NEED_BIND_MOBILE" ||
|
||||||
|
status === "NEED_PHONE_BIND" ||
|
||||||
|
Boolean(bindToken) ||
|
||||||
|
message.includes("\u7ed1\u5b9a") ||
|
||||||
|
message.includes("\u624b\u673a\u53f7") ||
|
||||||
|
message.includes("\u624b\u673a") ||
|
||||||
|
/bind|phone/i.test(message),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureCaptchaRow() {
|
||||||
|
let row = $("#captchaRow");
|
||||||
|
|
||||||
|
if (row) {
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
const phoneRow = $("#phoneInput").closest(".form-row");
|
||||||
|
phoneRow.insertAdjacentHTML(
|
||||||
|
"afterend",
|
||||||
|
`
|
||||||
|
<div class="form-row captcha-row" id="captchaRow">
|
||||||
|
<label for="captchaInput">图形验证码</label>
|
||||||
|
<div class="captcha-box">
|
||||||
|
<input
|
||||||
|
class="input"
|
||||||
|
id="captchaInput"
|
||||||
|
maxlength="6"
|
||||||
|
placeholder="请输入图形验证码"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
<img class="captcha-image" id="captchaImage" alt="点击刷新验证码" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
row = $("#captchaRow");
|
||||||
|
$("#captchaImage").addEventListener("click", refreshBindCaptcha);
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setBindVerifyType(type) {
|
||||||
|
socialContext.bindVerifyType = type === "slide" ? "slide" : "captcha";
|
||||||
|
const row = ensureCaptchaRow();
|
||||||
|
|
||||||
|
if (socialContext.bindVerifyType === "captcha") {
|
||||||
|
row.style.display = "block";
|
||||||
|
refreshBindCaptcha();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
row.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshBindCaptcha() {
|
||||||
|
if (socialContext.bindVerifyType !== "captcha") {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
socialContext.captchaUuid = "";
|
||||||
|
const input = $("#captchaInput");
|
||||||
|
const image = $("#captchaImage");
|
||||||
|
if (input) input.value = "";
|
||||||
|
if (image) image.removeAttribute("src");
|
||||||
|
|
||||||
|
return ApiClient.get(
|
||||||
|
ApiClient.API.authCode,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
publicRequest: true,
|
||||||
|
headers: socialHeaders(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.then((res) => {
|
||||||
|
const data = ApiClient.pickData(res) || {};
|
||||||
|
|
||||||
|
if (!ApiClient.isSuccess(res) || !data) {
|
||||||
|
throw new Error((res && res.msg) || "图形验证码获取失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
socialContext.captchaUuid = data.uuid || data.captchaId || "";
|
||||||
|
if (image) {
|
||||||
|
image.src = data.img || data.image || data.base64 || "";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
showError(error.message || "图形验证码获取失败,请刷新重试");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initBindVerify() {
|
||||||
|
if (bindVerifyLoaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bindVerifyLoaded = true;
|
||||||
|
setBindVerifyType("slide");
|
||||||
|
}
|
||||||
|
|
||||||
|
function showBindForm(message, data = {}) {
|
||||||
|
socialContext.bindToken = getBindToken(data) || socialContext.bindToken;
|
||||||
|
const desc = normalizeMessage(message, "");
|
||||||
|
$("#title").textContent = "绑定手机号";
|
||||||
|
$("#desc").textContent =
|
||||||
|
!desc || desc === "操作成功"
|
||||||
|
? "微信还没有绑定手机号,请先完成手机验证"
|
||||||
|
: desc;
|
||||||
|
status.style.display = "none";
|
||||||
|
bindForm.style.display = "block";
|
||||||
|
initBindVerify();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function socialLogin() {
|
||||||
|
hideError();
|
||||||
|
bindForm.style.display = "none";
|
||||||
|
setStatus("正在登录");
|
||||||
|
|
||||||
|
if (!socialContext.code || !socialContext.state) {
|
||||||
|
status.style.display = "none";
|
||||||
|
showError("登录参数缺失,请返回登录页重新发起微信登录");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await ApiClient.post(
|
||||||
|
ApiClient.API.authSocialLogin,
|
||||||
|
{
|
||||||
|
source: socialContext.source,
|
||||||
|
socialCode: socialContext.code,
|
||||||
|
socialState: socialContext.state,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
publicRequest: true,
|
||||||
|
headers: socialHeaders(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = ApiClient.pickData(res) || {};
|
||||||
|
|
||||||
|
if (ApiClient.isSuccess(res) && saveLoginData(data)) {
|
||||||
|
redirectHome();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ApiClient.isSuccess(res) || isBindRequired(res)) {
|
||||||
|
showBindForm((res && res.msg) || "", {
|
||||||
|
...data,
|
||||||
|
data: res && res.data,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
status.style.display = "none";
|
||||||
|
showError((res && res.msg) || "微信登录失败,请返回登录页重试");
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateBindForm() {
|
||||||
|
const phone = $("#phoneInput").value.trim();
|
||||||
|
const smsCode = $("#smsInput").value.trim();
|
||||||
|
|
||||||
|
if (!AuthPageUtil.isPhone(phone)) {
|
||||||
|
showError("请输入正确的手机号");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/^\d{4,6}$/.test(smsCode)) {
|
||||||
|
showError("请输入4-6位短信验证码");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!socialContext.bindToken) {
|
||||||
|
showError("缺少绑定凭证,请返回登录页重新扫码");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
hideError();
|
||||||
|
|
||||||
|
return { phone, smsCode };
|
||||||
|
}
|
||||||
|
|
||||||
|
function startSmsCountdown(seconds = 60) {
|
||||||
|
let left = seconds;
|
||||||
|
sendSmsButton.disabled = true;
|
||||||
|
sendSmsButton.textContent = `${left}s后重发`;
|
||||||
|
|
||||||
|
const timer = window.setInterval(() => {
|
||||||
|
left -= 1;
|
||||||
|
if (left <= 0) {
|
||||||
|
window.clearInterval(timer);
|
||||||
|
sendSmsButton.disabled = false;
|
||||||
|
sendSmsButton.textContent = "获取验证码";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendSmsButton.textContent = `${left}s后重发`;
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBindCaptchaParams() {
|
||||||
|
const code = ($("#captchaInput") && $("#captchaInput").value.trim()) || "";
|
||||||
|
|
||||||
|
if (!socialContext.captchaUuid || !code) {
|
||||||
|
showError("请先输入图形验证码");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
verifyType: "captcha",
|
||||||
|
uuid: socialContext.captchaUuid,
|
||||||
|
code,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function openBindSlideCaptcha() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
AuthPageUtil.openSlideCaptcha({
|
||||||
|
title: "绑定手机号安全验证",
|
||||||
|
subtitle: "拖动滑块完成验证后发送短信",
|
||||||
|
onSuccess: resolve,
|
||||||
|
onCancel: () => reject(new Error("已取消安全验证")),
|
||||||
|
onError: (error) => reject(error || new Error("滑动验证失败")),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getBindSmsVerifyParams() {
|
||||||
|
if (socialContext.bindVerifyType === "slide") {
|
||||||
|
sendSmsButton.textContent = "验证中...";
|
||||||
|
const result = await openBindSlideCaptcha();
|
||||||
|
const slideToken =
|
||||||
|
result.slideToken ||
|
||||||
|
result.token ||
|
||||||
|
result.verifyToken ||
|
||||||
|
"";
|
||||||
|
const slideUuid = result.slideUuid || result.uuid || "";
|
||||||
|
const slideXValue = result.slideX !== undefined ? result.slideX : result.x;
|
||||||
|
|
||||||
|
if (!slideToken && !slideUuid) {
|
||||||
|
throw new Error("滑动验证未返回凭证,请重试");
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
verifyType: "slide",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (slideToken) {
|
||||||
|
params.slideToken = slideToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slideUuid) {
|
||||||
|
params.slideUuid = slideUuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slideXValue !== undefined && slideXValue !== null && slideXValue !== "") {
|
||||||
|
params.slideX = Number(slideXValue) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getBindCaptchaParams();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function requestBindPhoneSmsCode(phone, verifyParams) {
|
||||||
|
return ApiClient.get(
|
||||||
|
ApiClient.API.authSocialBindPhoneSmsCode,
|
||||||
|
{
|
||||||
|
phoneNumber: phone,
|
||||||
|
...verifyParams,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
publicRequest: true,
|
||||||
|
headers: socialHeaders(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendSmsCode() {
|
||||||
|
const phone = $("#phoneInput").value.trim();
|
||||||
|
|
||||||
|
if (!AuthPageUtil.isPhone(phone)) {
|
||||||
|
showError("请输入正确的手机号");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!socialContext.bindToken) {
|
||||||
|
showError("缺少绑定凭证,请返回登录页重新扫码");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hideError();
|
||||||
|
sendSmsButton.disabled = true;
|
||||||
|
sendSmsButton.textContent = "发送中...";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const verifyParams = await getBindSmsVerifyParams();
|
||||||
|
|
||||||
|
if (!verifyParams) {
|
||||||
|
sendSmsButton.disabled = false;
|
||||||
|
sendSmsButton.textContent = "获取验证码";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendSmsButton.textContent = "发送中...";
|
||||||
|
const res = await requestBindPhoneSmsCode(phone, verifyParams);
|
||||||
|
|
||||||
|
if (!ApiClient.isSuccess(res)) {
|
||||||
|
throw new Error((res && res.msg) || "短信验证码发送失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
startSmsCountdown();
|
||||||
|
} catch (error) {
|
||||||
|
sendSmsButton.disabled = false;
|
||||||
|
sendSmsButton.textContent = "获取验证码";
|
||||||
|
showError(error.message || "短信验证码发送失败");
|
||||||
|
if (socialContext.bindVerifyType === "captcha") {
|
||||||
|
refreshBindCaptcha();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitBind(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const formData = validateBindForm();
|
||||||
|
|
||||||
|
if (!formData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bindButton.disabled = true;
|
||||||
|
bindButton.textContent = "绑定中...";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await ApiClient.post(
|
||||||
|
ApiClient.API.authSocialBindPhone,
|
||||||
|
{
|
||||||
|
bindToken: socialContext.bindToken,
|
||||||
|
phoneNumber: formData.phone,
|
||||||
|
smsCode: formData.smsCode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
publicRequest: true,
|
||||||
|
headers: socialHeaders(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!ApiClient.isSuccess(res)) {
|
||||||
|
throw new Error((res && res.msg) || "绑定失败,请稍后重试");
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = ApiClient.pickData(res) || {};
|
||||||
|
|
||||||
|
if (saveLoginData(data)) {
|
||||||
|
redirectHome();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setStatus("绑定成功,正在登录...");
|
||||||
|
await socialLogin();
|
||||||
|
} catch (error) {
|
||||||
|
showError(error.message || "绑定失败,请稍后重试");
|
||||||
|
} finally {
|
||||||
|
bindButton.disabled = false;
|
||||||
|
bindButton.textContent = "绑定并登录";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendSmsButton.addEventListener("click", sendSmsCode);
|
||||||
|
bindForm.addEventListener("submit", submitBind);
|
||||||
|
$("#retryButton").addEventListener("click", () => {
|
||||||
|
const action = isAccountBindCallback() ? socialBindAccount : socialLogin;
|
||||||
|
action().catch((error) => {
|
||||||
|
status.style.display = "none";
|
||||||
|
showError(error.message || "微信登录失败,请返回登录页重试");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const initialAction = isAccountBindCallback() ? socialBindAccount : socialLogin;
|
||||||
|
initialAction().catch((error) => {
|
||||||
|
status.style.display = "none";
|
||||||
|
showError(error.message || "微信登录失败,请返回登录页重试");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -76,6 +76,26 @@
|
|||||||
<div class="uc-nickname" id="userNickName">未登录</div>
|
<div class="uc-nickname" id="userNickName">未登录</div>
|
||||||
<div class="uc-level" id="userLevel">普通会员</div>
|
<div class="uc-level" id="userLevel">普通会员</div>
|
||||||
<div class="uc-phone" id="userPhone"></div>
|
<div class="uc-phone" id="userPhone"></div>
|
||||||
|
<div class="wechatBindCard" id="wechat-bind-card">
|
||||||
|
<div class="wechatBindTop">
|
||||||
|
<div class="wechatBindIconBox">
|
||||||
|
<img class="wechatBindIcon" src="../public/img/pay-wechat.png" alt="微信" />
|
||||||
|
</div>
|
||||||
|
<div class="wechatBindInfo">
|
||||||
|
<div class="wechatBindTitle">微信账号</div>
|
||||||
|
<div class="wechatBindDesc" id="wechat-bind-desc">检测中...</div>
|
||||||
|
</div>
|
||||||
|
<div class="wechatBindStateDot"></div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="wechatBindButton"
|
||||||
|
id="wechat-bind-button"
|
||||||
|
onclick="openWeChatBindPopup()"
|
||||||
|
>
|
||||||
|
绑定微信
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="uc-actions">
|
<div class="uc-actions">
|
||||||
<button
|
<button
|
||||||
@@ -120,6 +140,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="wechatBindCard" id="wechat-bind-card">
|
||||||
|
<div class="wechatBindTop">
|
||||||
|
<div class="wechatBindIconBox">
|
||||||
|
<img class="wechatBindIcon" src="../public/img/pay-wechat.png" alt="微信" />
|
||||||
|
</div>
|
||||||
|
<div class="wechatBindInfo">
|
||||||
|
<div class="wechatBindTitle">微信账号</div>
|
||||||
|
<div class="wechatBindDesc" id="wechat-bind-desc">正在检测绑定状态...</div>
|
||||||
|
</div>
|
||||||
|
<div class="wechatBindStateDot"></div>
|
||||||
|
</div>
|
||||||
|
<div class="wechatBindLine"></div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="wechatBindButton"
|
||||||
|
id="wechat-bind-button"
|
||||||
|
onclick="openWeChatBindPopup()"
|
||||||
|
>
|
||||||
|
绑定微信
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<!-- Balance Cards -->
|
<!-- Balance Cards -->
|
||||||
<div class="uc-balance-grid">
|
<div class="uc-balance-grid">
|
||||||
<div class="uc-balance-item">
|
<div class="uc-balance-item">
|
||||||
@@ -370,10 +411,43 @@
|
|||||||
↑
|
↑
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<div id="mask-layer"></div>
|
||||||
|
<div id="wechat-bind-popup">
|
||||||
|
<div class="wechatBindPopupHeader">
|
||||||
|
<div>
|
||||||
|
<div class="wechatBindPopupTitle">绑定微信</div>
|
||||||
|
<div class="wechatBindPopupSub">绑定后可使用微信快捷登录当前账号</div>
|
||||||
|
</div>
|
||||||
|
<div class="popup-close" onclick="closeWeChatBindPopup()">
|
||||||
|
<i class="layui-icon layui-icon-close"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="wechatBindPopupBody">
|
||||||
|
<div class="wechatBindFormRow">
|
||||||
|
<label>手机号</label>
|
||||||
|
<input id="wechatBindPhone" type="text" placeholder="请输入手机号" />
|
||||||
|
</div>
|
||||||
|
<div class="wechatBindFormRow">
|
||||||
|
<label>短信验证码</label>
|
||||||
|
<div class="wechatBindCodeBox">
|
||||||
|
<input id="wechatBindSmsCode" type="text" placeholder="请输入验证码" />
|
||||||
|
<button type="button" onclick="sendWeChatBindSms()">获取验证码</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="wechatBindFormRow">
|
||||||
|
<label>登录密码</label>
|
||||||
|
<input id="wechatBindPassword" type="password" placeholder="请输入登录密码" />
|
||||||
|
</div>
|
||||||
|
<div class="wechatBindTip">当前接口需要微信授权参数、手机号、短信验证码和密码。</div>
|
||||||
|
<button type="button" class="wechatBindSubmit" onclick="submitWeChatBind()">确认绑定</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="../public/js/md5.js"></script>
|
<script src="../public/js/md5.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const $ = layui.$;
|
const $ = layui.$;
|
||||||
var currentUserData = null;
|
var currentUserData = null;
|
||||||
|
var accountInfoData = {};
|
||||||
var menuListData = [];
|
var menuListData = [];
|
||||||
var signInPageNum = 1;
|
var signInPageNum = 1;
|
||||||
var articlePageNum = 1;
|
var articlePageNum = 1;
|
||||||
@@ -466,6 +540,148 @@
|
|||||||
$("#publishArticleBtn").toggle(expert);
|
$("#publishArticleBtn").toggle(expert);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadAccountInfoForWechat() {
|
||||||
|
ApiClient.get(ApiClient.API.accountInfo, null, { headers: CommonUtil.authHeaders() })
|
||||||
|
.then(function (res) {
|
||||||
|
accountInfoData = ApiClient.isSuccess(res) && res.data ? res.data : {};
|
||||||
|
updateWechatBindPanel();
|
||||||
|
})
|
||||||
|
.catch(function () {
|
||||||
|
accountInfoData = {};
|
||||||
|
updateWechatBindPanel();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function isWechatBound() {
|
||||||
|
var data = Object.assign({}, currentUserData || {}, accountInfoData || {});
|
||||||
|
return Boolean(
|
||||||
|
data.openId ||
|
||||||
|
data.openid ||
|
||||||
|
data.wxOpenId ||
|
||||||
|
data.wechatOpenId ||
|
||||||
|
data.wechatOpenid ||
|
||||||
|
data.unionId ||
|
||||||
|
data.wechatUnionId ||
|
||||||
|
data.wechatBound ||
|
||||||
|
data.isWechatBound,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateWechatBindPanel() {
|
||||||
|
var $desc = $("#wechat-bind-desc");
|
||||||
|
var $button = $("#wechat-bind-button");
|
||||||
|
var $card = $("#wechat-bind-card");
|
||||||
|
if (!$desc.length || !$button.length) return;
|
||||||
|
|
||||||
|
$card.removeClass("is-bound is-pending is-ready");
|
||||||
|
if (isWechatBound()) {
|
||||||
|
$desc.text("微信已绑定");
|
||||||
|
$card.addClass("is-bound");
|
||||||
|
$button
|
||||||
|
.text("已绑定")
|
||||||
|
.removeClass("is-pending")
|
||||||
|
.addClass("is-bound")
|
||||||
|
.prop("disabled", true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$desc.text("未绑定微信");
|
||||||
|
$card.addClass("is-ready");
|
||||||
|
$button
|
||||||
|
.text("绑定微信")
|
||||||
|
.removeClass("is-bound is-pending")
|
||||||
|
.prop("disabled", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWechatAuthParams() {
|
||||||
|
var params = new URLSearchParams(window.location.search);
|
||||||
|
return {
|
||||||
|
socialCode: params.get("socialCode") || params.get("code") || "",
|
||||||
|
socialState: params.get("socialState") || params.get("state") || "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function openWeChatBindPopup() {
|
||||||
|
var authParams = getWechatAuthParams();
|
||||||
|
if (isWechatBound()) {
|
||||||
|
layui.layer.msg("当前账号已绑定微信", { icon: 1 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!authParams.socialCode) {
|
||||||
|
layui.layer.msg("请先通过微信授权入口进入绑定流程", { icon: 0 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#wechatBindPhone").val(
|
||||||
|
(currentUserData && currentUserData.phoneNumber) ||
|
||||||
|
accountInfoData.phoneNumber ||
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
$("#wechatBindSmsCode").val("");
|
||||||
|
$("#wechatBindPassword").val("");
|
||||||
|
$("#mask-layer").show();
|
||||||
|
$("#wechat-bind-popup").show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeWeChatBindPopup() {
|
||||||
|
$("#wechat-bind-popup").hide();
|
||||||
|
$("#mask-layer").hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendWeChatBindSms() {
|
||||||
|
var phoneNumber = $("#wechatBindPhone").val().trim();
|
||||||
|
if (!phoneNumber) {
|
||||||
|
layui.layer.msg("请输入手机号", { icon: 2 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiClient.get(ApiClient.API.authSocialBindPhoneSmsCode, {
|
||||||
|
phoneNumber: phoneNumber,
|
||||||
|
}).then(function (res) {
|
||||||
|
if (ApiClient.isSuccess(res)) {
|
||||||
|
layui.layer.msg("验证码已发送", { icon: 1 });
|
||||||
|
} else {
|
||||||
|
layui.layer.msg(res.msg || "验证码发送失败", { icon: 2 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitWeChatBind() {
|
||||||
|
var authParams = getWechatAuthParams();
|
||||||
|
var phoneNumber = $("#wechatBindPhone").val().trim();
|
||||||
|
var smsCode = $("#wechatBindSmsCode").val().trim();
|
||||||
|
var password = $("#wechatBindPassword").val();
|
||||||
|
|
||||||
|
if (!authParams.socialCode) {
|
||||||
|
layui.layer.msg("缺少微信授权参数", { icon: 2 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!phoneNumber || !smsCode || !password) {
|
||||||
|
layui.layer.msg("请完整填写绑定信息", { icon: 2 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiClient.post(ApiClient.API.authSocialBind, {
|
||||||
|
source: "wechat_open",
|
||||||
|
socialCode: authParams.socialCode,
|
||||||
|
socialState: authParams.socialState,
|
||||||
|
phoneNumber: phoneNumber,
|
||||||
|
smsCode: smsCode,
|
||||||
|
password: hex_md5(password),
|
||||||
|
}).then(function (res) {
|
||||||
|
if (ApiClient.isSuccess(res)) {
|
||||||
|
layui.layer.msg("微信绑定成功", { icon: 1 });
|
||||||
|
try {
|
||||||
|
sessionStorage.setItem("wechatAccountBindSuccess", "1");
|
||||||
|
} catch (e) {}
|
||||||
|
closeWeChatBindPopup();
|
||||||
|
loadUserDetail();
|
||||||
|
} else {
|
||||||
|
layui.layer.msg(res.msg || "微信绑定失败", { icon: 2 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function isTodayTime(value) {
|
function isTodayTime(value) {
|
||||||
if (!value) return false;
|
if (!value) return false;
|
||||||
var dateText = String(value).slice(0, 10);
|
var dateText = String(value).slice(0, 10);
|
||||||
@@ -534,6 +750,7 @@
|
|||||||
: "普通会员",
|
: "普通会员",
|
||||||
);
|
);
|
||||||
updateExpertActions(data);
|
updateExpertActions(data);
|
||||||
|
loadAccountInfoForWechat();
|
||||||
$("#userPoints").html((data.points || 0) + "<span>点</span>");
|
$("#userPoints").html((data.points || 0) + "<span>点</span>");
|
||||||
if (hasSignedTodayValue(data)) {
|
if (hasSignedTodayValue(data)) {
|
||||||
setSignInButtonSigned(readSignedToday(data));
|
setSignInButtonSigned(readSignedToday(data));
|
||||||
|
|||||||
@@ -634,6 +634,42 @@ body:has(.auth-container) {
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.auth-wechat-login {
|
||||||
|
margin-top: 18px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-wechat-login a {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
min-height: 48px;
|
||||||
|
padding: 0 24px;
|
||||||
|
border: 1px solid rgba(7, 193, 96, 0.24);
|
||||||
|
border-radius: 24px;
|
||||||
|
color: #07c160;
|
||||||
|
background: rgba(7, 193, 96, 0.06);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-wechat-login a:hover {
|
||||||
|
background: rgba(7, 193, 96, 0.12);
|
||||||
|
border-color: rgba(7, 193, 96, 0.42);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-wechat-icon {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
display: block;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
Register Page - Step Indicators
|
Register Page - Step Indicators
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|||||||
@@ -1,3 +1,214 @@
|
|||||||
|
.wechatBindCard {
|
||||||
|
width: fit-content;
|
||||||
|
max-width: 100%;
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uc-profile-card + .wechatBindCard {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindTop {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
min-height: 32px;
|
||||||
|
padding: 3px 10px 3px 4px;
|
||||||
|
border: 1px solid rgba(7, 193, 96, 0.18);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(7, 193, 96, 0.07);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindIconBox {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: transparent;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindIcon {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindInfo {
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindTitle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindDesc {
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #166534;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindStateDot {
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #f59e0b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindCard.is-bound .wechatBindStateDot,
|
||||||
|
.wechatBindCard.is-ready .wechatBindStateDot {
|
||||||
|
background: #07c160;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindLine {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindButton,
|
||||||
|
.wechatBindSubmit,
|
||||||
|
.wechatBindCodeBox button {
|
||||||
|
border: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindButton {
|
||||||
|
min-height: 26px;
|
||||||
|
padding: 0 10px;
|
||||||
|
border-radius: 13px;
|
||||||
|
color: #fff;
|
||||||
|
background: #07c160;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindButton:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindButton.is-bound {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mask-layer {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 998;
|
||||||
|
background: rgba(15, 23, 42, 0.42);
|
||||||
|
}
|
||||||
|
|
||||||
|
#wechat-bind-popup {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
z-index: 999;
|
||||||
|
width: min(420px, calc(100vw - 32px));
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 24px 70px rgba(15, 23, 42, 0.24);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindPopupHeader {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 20px 22px;
|
||||||
|
border-bottom: 1px solid var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindPopupTitle {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--text-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindPopupSub {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindPopupBody {
|
||||||
|
padding: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindFormRow {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindFormRow label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 7px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindFormRow input,
|
||||||
|
.wechatBindCodeBox input {
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border: 1px solid var(--border-light);
|
||||||
|
border-radius: 6px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindFormRow input:focus,
|
||||||
|
.wechatBindCodeBox input:focus {
|
||||||
|
border-color: #07c160;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindCodeBox {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindCodeBox button {
|
||||||
|
width: 112px;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #fff;
|
||||||
|
background: var(--primary);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindTip {
|
||||||
|
margin: 4px 0 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechatBindSubmit {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 42px;
|
||||||
|
border-radius: 21px;
|
||||||
|
color: #fff;
|
||||||
|
background: #07c160;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-close {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
usercenter.css - 用户中心页 (usercenter.html)
|
usercenter.css - 用户中心页 (usercenter.html)
|
||||||
Comprehensive styles with responsive breakpoints
|
Comprehensive styles with responsive breakpoints
|
||||||
|
|||||||
@@ -9,8 +9,17 @@ const ApiClient = {
|
|||||||
authLogout: '/api/web/auth/logout',
|
authLogout: '/api/web/auth/logout',
|
||||||
authRegister: '/api/web/auth/register',
|
authRegister: '/api/web/auth/register',
|
||||||
authSmsCode: '/api/web/auth/sms/code',
|
authSmsCode: '/api/web/auth/sms/code',
|
||||||
|
authWechatOpen: '/api/web/auth/binding/wechat_open',
|
||||||
authVerifyConfig: '/api/web/auth/verify-config',
|
authVerifyConfig: '/api/web/auth/verify-config',
|
||||||
authSlideCaptcha: '/api/web/auth/slide-captcha',
|
authSlideCaptcha: '/api/web/auth/slide-captcha',
|
||||||
|
authSlideCaptchaVerify: '/api/web/auth/slide-captcha/verify',
|
||||||
|
authForgotPasswordSmsCode: '/api/web/auth/forgot-password/sms-code',
|
||||||
|
authForgotPasswordReset: '/api/web/auth/forgot-password/reset',
|
||||||
|
authSocialLogin: '/api/web/auth/social-login',
|
||||||
|
authSocialCallback: '/api/web/auth/social-callback',
|
||||||
|
authSocialBind: '/api/web/auth/social-bind',
|
||||||
|
authSocialBindPhone: '/api/web/auth/social-bind-phone',
|
||||||
|
authSocialBindPhoneSmsCode: '/api/web/auth/social-bind-phone/sms-code',
|
||||||
webConfigList: '/api/web/config/list',
|
webConfigList: '/api/web/config/list',
|
||||||
referralShareInfo: '/api/web/referral/share-info',
|
referralShareInfo: '/api/web/referral/share-info',
|
||||||
referralSummary: '/api/web/referral/summary',
|
referralSummary: '/api/web/referral/summary',
|
||||||
@@ -18,6 +27,7 @@ const ApiClient = {
|
|||||||
referralRebateList: '/api/web/referral/rebate-list',
|
referralRebateList: '/api/web/referral/rebate-list',
|
||||||
referralBindPreview: '/api/web/referral/bind-preview',
|
referralBindPreview: '/api/web/referral/bind-preview',
|
||||||
referralBind: '/api/web/referral/bind',
|
referralBind: '/api/web/referral/bind',
|
||||||
|
accountInfo: '/api/web/account/info',
|
||||||
accountProfile: '/api/web/account/profile',
|
accountProfile: '/api/web/account/profile',
|
||||||
accountProfileAvatar: '/api/web/account/profile/avatar',
|
accountProfileAvatar: '/api/web/account/profile/avatar',
|
||||||
expertConfig: '/api/web/expert/config',
|
expertConfig: '/api/web/expert/config',
|
||||||
@@ -83,6 +93,10 @@ const ApiClient = {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get CLIENT_ID() {
|
||||||
|
return this.clientId;
|
||||||
|
},
|
||||||
|
|
||||||
get tenantId() {
|
get tenantId() {
|
||||||
return (
|
return (
|
||||||
(window.CURRENT_CONFIG && window.CURRENT_CONFIG.TENANT_ID) ||
|
(window.CURRENT_CONFIG && window.CURRENT_CONFIG.TENANT_ID) ||
|
||||||
|
|||||||
+65
-3
@@ -2,6 +2,7 @@ const AuthPageUtil = {
|
|||||||
smsTimers: {},
|
smsTimers: {},
|
||||||
slideUuid: '',
|
slideUuid: '',
|
||||||
slideX: 0,
|
slideX: 0,
|
||||||
|
slideToken: '',
|
||||||
|
|
||||||
isPhone(phone) {
|
isPhone(phone) {
|
||||||
return /^1[3-9]\d{9}$/.test(String(phone || '').trim());
|
return /^1[3-9]\d{9}$/.test(String(phone || '').trim());
|
||||||
@@ -15,7 +16,7 @@ const AuthPageUtil = {
|
|||||||
loadVerifyType() {
|
loadVerifyType() {
|
||||||
return ApiClient.get(ApiClient.API.authVerifyConfig, {}, { publicRequest: true })
|
return ApiClient.get(ApiClient.API.authVerifyConfig, {}, { publicRequest: true })
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const data = (res && res.data) || {};
|
const data = (res && res.data) || res || {};
|
||||||
const yes = (value) =>
|
const yes = (value) =>
|
||||||
value === true || value === 1 || value === '1' || String(value).toLowerCase() === 'true';
|
value === true || value === 1 || value === '1' || String(value).toLowerCase() === 'true';
|
||||||
if (
|
if (
|
||||||
@@ -32,12 +33,13 @@ const AuthPageUtil = {
|
|||||||
.catch(() => 'captcha');
|
.catch(() => 'captcha');
|
||||||
},
|
},
|
||||||
|
|
||||||
sendSmsCode(options) {
|
buildSmsParams(options = {}) {
|
||||||
const params = {
|
const params = {
|
||||||
phoneNumber: options.phoneNumber,
|
phoneNumber: options.phoneNumber,
|
||||||
};
|
};
|
||||||
if (options.verifyType === 'slide') {
|
if (options.verifyType === 'slide') {
|
||||||
params.verifyType = 'slide';
|
params.verifyType = 'slide';
|
||||||
|
params.slideToken = options.slideToken || this.slideToken;
|
||||||
params.slideUuid = options.slideUuid || this.slideUuid;
|
params.slideUuid = options.slideUuid || this.slideUuid;
|
||||||
params.slideX = options.slideX || this.slideX;
|
params.slideX = options.slideX || this.slideX;
|
||||||
}
|
}
|
||||||
@@ -46,7 +48,23 @@ const AuthPageUtil = {
|
|||||||
params.code = options.code || '';
|
params.code = options.code || '';
|
||||||
params.uuid = options.uuid || '';
|
params.uuid = options.uuid || '';
|
||||||
}
|
}
|
||||||
return ApiClient.get(ApiClient.API.authSmsCode, params, { publicRequest: true });
|
return params;
|
||||||
|
},
|
||||||
|
|
||||||
|
sendSmsCode(options) {
|
||||||
|
return ApiClient.get(ApiClient.API.authSmsCode, this.buildSmsParams(options), {
|
||||||
|
publicRequest: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
sendForgotPasswordSmsCode(options) {
|
||||||
|
return ApiClient.get(ApiClient.API.authForgotPasswordSmsCode, this.buildSmsParams(options), {
|
||||||
|
publicRequest: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getImageCaptcha() {
|
||||||
|
return ApiClient.get(ApiClient.API.authCode, {}, { publicRequest: true });
|
||||||
},
|
},
|
||||||
|
|
||||||
installSlideCaptchaTenantHeader() {
|
installSlideCaptchaTenantHeader() {
|
||||||
@@ -138,6 +156,50 @@ const AuthPageUtil = {
|
|||||||
});
|
});
|
||||||
return window.__slideCaptchaLoading;
|
return window.__slideCaptchaLoading;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
openSlideCaptcha(options = {}) {
|
||||||
|
this.installSlideCaptchaTenantHeader();
|
||||||
|
|
||||||
|
this.loadSlideCaptchaScript()
|
||||||
|
.then((SlideCaptcha) => {
|
||||||
|
SlideCaptcha.open({
|
||||||
|
title: options.title || '安全验证',
|
||||||
|
subtitle: options.subtitle || '拖动滑块完成验证',
|
||||||
|
apiBase: ApiClient.baseUrl || '',
|
||||||
|
tenantId: ApiClient.tenantId || '000000',
|
||||||
|
headers: ApiClient.publicHeaders(),
|
||||||
|
onSuccess: (result) => {
|
||||||
|
const data = result || {};
|
||||||
|
const nested = data.data || {};
|
||||||
|
this.slideToken =
|
||||||
|
data.slideToken || data.token || data.verifyToken ||
|
||||||
|
nested.slideToken || nested.token || nested.verifyToken || '';
|
||||||
|
this.slideUuid = data.slideUuid || data.uuid || nested.slideUuid || nested.uuid || '';
|
||||||
|
this.slideX = Number(data.slideX || data.x || nested.slideX || nested.x || 0) || 0;
|
||||||
|
|
||||||
|
if (!this.slideToken && !this.slideUuid) {
|
||||||
|
if (typeof options.onError === 'function') {
|
||||||
|
options.onError(new Error('滑动验证未返回凭证,请重试'));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof options.onSuccess === 'function') {
|
||||||
|
options.onSuccess({
|
||||||
|
slideToken: this.slideToken,
|
||||||
|
slideUuid: this.slideUuid,
|
||||||
|
slideX: this.slideX,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onCancel: options.onCancel,
|
||||||
|
onError: options.onError,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if (typeof options.onError === 'function') options.onError(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
|
|||||||
@@ -254,6 +254,8 @@ const CommonUtil = {
|
|||||||
loadPageConfig(options = {}) {
|
loadPageConfig(options = {}) {
|
||||||
if (typeof ApiClient === 'undefined') return;
|
if (typeof ApiClient === 'undefined') return;
|
||||||
this.installUnifiedPagination();
|
this.installUnifiedPagination();
|
||||||
|
this.installWechatLoginEntry();
|
||||||
|
this.installWechatAccountBindEntry();
|
||||||
const loadHomeData = options.home !== false && this.isHomePageDom();
|
const loadHomeData = options.home !== false && this.isHomePageDom();
|
||||||
const request = loadHomeData ? this.loadHomePageData() : this.loadBasePageData(options);
|
const request = loadHomeData ? this.loadHomePageData() : this.loadBasePageData(options);
|
||||||
this.applyCaiExpertListLabels();
|
this.applyCaiExpertListLabels();
|
||||||
@@ -270,6 +272,8 @@ const CommonUtil = {
|
|||||||
this.processAndRenderAds(data.adList, options.adPositions);
|
this.processAndRenderAds(data.adList, options.adPositions);
|
||||||
}
|
}
|
||||||
this.applyCaiExpertListLabels();
|
this.applyCaiExpertListLabels();
|
||||||
|
this.installWechatLoginEntry();
|
||||||
|
this.installWechatAccountBindEntry();
|
||||||
if (options.links !== false) this.loadLinks();
|
if (options.links !== false) this.loadLinks();
|
||||||
if (typeof options.onSuccess === 'function') options.onSuccess(result);
|
if (typeof options.onSuccess === 'function') options.onSuccess(result);
|
||||||
})
|
})
|
||||||
@@ -278,6 +282,230 @@ const CommonUtil = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
installWechatLoginEntry() {
|
||||||
|
if (typeof window === 'undefined' || window.__WECHAT_LOGIN_ENTRY_INSTALLED__) return;
|
||||||
|
const $entry = this.$('.wxdl');
|
||||||
|
if (!$entry.length || typeof ApiClient === 'undefined') return;
|
||||||
|
|
||||||
|
window.__WECHAT_LOGIN_ENTRY_INSTALLED__ = true;
|
||||||
|
$entry.find('a').attr({
|
||||||
|
href: 'javascript:void(0)',
|
||||||
|
title: '微信登录',
|
||||||
|
});
|
||||||
|
|
||||||
|
$entry.on('click.wechatLogin', 'a, .wx_Box', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
this.startWechatLogin();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getWechatLoginDomain() {
|
||||||
|
if (typeof window === 'undefined' || !window.location || window.location.protocol === 'file:') {
|
||||||
|
return window.CURRENT_CONFIG && window.CURRENT_CONFIG.SITE_DOMAIN
|
||||||
|
? window.CURRENT_CONFIG.SITE_DOMAIN
|
||||||
|
: 'http://47.108.24.205:9100';
|
||||||
|
}
|
||||||
|
return window.location.origin;
|
||||||
|
},
|
||||||
|
|
||||||
|
extractWechatAuthUrl(res) {
|
||||||
|
const data = res && res.data;
|
||||||
|
if (typeof data === 'string') return data;
|
||||||
|
if (data && typeof data === 'object') {
|
||||||
|
return data.url || data.authUrl || data.authorizeUrl || data.redirectUrl || data.loginUrl || '';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
|
||||||
|
startWechatLogin() {
|
||||||
|
const $ = this.$;
|
||||||
|
const layer = typeof layui !== 'undefined' && layui.layer;
|
||||||
|
const $agreement = $('#checkbox, #agreementCheckbox').first();
|
||||||
|
|
||||||
|
if ($agreement.length && !$agreement.prop('checked')) {
|
||||||
|
if (layer) layer.msg('请先阅读并同意用户协议和隐私政策', { icon: 2 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const $loading = $('#loadingOverlay');
|
||||||
|
const $button = $('.wxdl');
|
||||||
|
$loading.addClass('is-active');
|
||||||
|
$button.addClass('is-loading');
|
||||||
|
|
||||||
|
ApiClient.get(
|
||||||
|
ApiClient.API.authWechatOpen,
|
||||||
|
{ domain: this.getWechatLoginDomain() },
|
||||||
|
{
|
||||||
|
publicRequest: true,
|
||||||
|
headers: { clientid: ApiClient.CLIENT_ID },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((res) => {
|
||||||
|
if (!ApiClient.isSuccess(res)) {
|
||||||
|
throw new Error((res && res.msg) || '微信授权地址获取失败');
|
||||||
|
}
|
||||||
|
const authUrl = this.extractWechatAuthUrl(res);
|
||||||
|
if (!authUrl) throw new Error('后端未返回微信授权地址');
|
||||||
|
window.location.href = authUrl;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
const message = error.message || '微信登录暂不可用,请稍后再试';
|
||||||
|
if (layer) layer.msg(message, { icon: 2 });
|
||||||
|
else alert(message);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
$loading.removeClass('is-active');
|
||||||
|
$button.removeClass('is-loading');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
installWechatAccountBindEntry() {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
const button = document.getElementById('wechat-bind-button');
|
||||||
|
if (!button || typeof ApiClient === 'undefined') return;
|
||||||
|
|
||||||
|
this.normalizeWechatAccountBindPanel();
|
||||||
|
if (window.__WECHAT_ACCOUNT_BIND_ENTRY_INSTALLED__) return;
|
||||||
|
|
||||||
|
window.__WECHAT_ACCOUNT_BIND_ENTRY_INSTALLED__ = true;
|
||||||
|
button.setAttribute('title', '绑定微信');
|
||||||
|
button.addEventListener(
|
||||||
|
'click',
|
||||||
|
(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
if (event.stopImmediatePropagation) event.stopImmediatePropagation();
|
||||||
|
this.startWechatAccountBind();
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
this.observeWechatAccountBindPanel();
|
||||||
|
},
|
||||||
|
|
||||||
|
isWechatAccountBindPanelBound() {
|
||||||
|
const $ = this.$;
|
||||||
|
const descText = ($('#wechat-bind-desc').text() || '').trim();
|
||||||
|
const buttonText = ($('#wechat-bind-button').text() || '').trim();
|
||||||
|
return (
|
||||||
|
this.hasWechatAccountBindSuccessMark() ||
|
||||||
|
$('#wechat-bind-card').hasClass('is-bound') ||
|
||||||
|
descText.indexOf('已绑定') > -1 ||
|
||||||
|
buttonText.indexOf('已绑定') > -1
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
hasWechatAccountBindSuccessMark() {
|
||||||
|
if (typeof window === 'undefined') return false;
|
||||||
|
try {
|
||||||
|
return sessionStorage.getItem('wechatAccountBindSuccess') === '1';
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
applyWechatAccountBindBoundPanel() {
|
||||||
|
const $button = this.$('#wechat-bind-button');
|
||||||
|
if (!$button.length) return;
|
||||||
|
this.$('#wechat-bind-desc').text('微信已绑定');
|
||||||
|
this.$('#wechat-bind-card').removeClass('is-pending is-ready').addClass('is-bound');
|
||||||
|
$button
|
||||||
|
.text('已绑定')
|
||||||
|
.attr('title', '微信已绑定')
|
||||||
|
.removeClass('is-pending')
|
||||||
|
.addClass('is-bound')
|
||||||
|
.prop('disabled', true);
|
||||||
|
},
|
||||||
|
|
||||||
|
normalizeWechatAccountBindPanel() {
|
||||||
|
const $button = this.$('#wechat-bind-button');
|
||||||
|
if (!$button.length) return;
|
||||||
|
|
||||||
|
if (this.hasWechatAccountBindSuccessMark()) {
|
||||||
|
this.applyWechatAccountBindBoundPanel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.isWechatAccountBindPanelBound()) return;
|
||||||
|
|
||||||
|
this.$('#wechat-bind-desc').text('未绑定微信');
|
||||||
|
this.$('#wechat-bind-card').removeClass('is-pending').addClass('is-ready');
|
||||||
|
$button
|
||||||
|
.text('绑定微信')
|
||||||
|
.attr('title', '绑定微信')
|
||||||
|
.removeClass('is-bound is-pending')
|
||||||
|
.prop('disabled', false);
|
||||||
|
},
|
||||||
|
|
||||||
|
observeWechatAccountBindPanel() {
|
||||||
|
if (typeof window === 'undefined' || window.__WECHAT_ACCOUNT_BIND_OBSERVER__) return;
|
||||||
|
const target = document.getElementById('wechat-bind-card') || document.getElementById('wechat-bind-button');
|
||||||
|
if (!target || typeof MutationObserver === 'undefined') return;
|
||||||
|
|
||||||
|
window.__WECHAT_ACCOUNT_BIND_OBSERVER__ = new MutationObserver(() => {
|
||||||
|
window.clearTimeout(window.__WECHAT_ACCOUNT_BIND_NORMALIZE_TIMER__);
|
||||||
|
window.__WECHAT_ACCOUNT_BIND_NORMALIZE_TIMER__ = window.setTimeout(() => {
|
||||||
|
this.normalizeWechatAccountBindPanel();
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
window.__WECHAT_ACCOUNT_BIND_OBSERVER__.observe(target, {
|
||||||
|
attributes: true,
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
rememberWechatAccountBindMode() {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
const returnUrl =
|
||||||
|
`${window.location.pathname}${window.location.search}${window.location.hash}` || '/html/usercenter.html';
|
||||||
|
const pairs = {
|
||||||
|
wechatSocialMode: 'account_bind',
|
||||||
|
wechatSocialReturnUrl: returnUrl,
|
||||||
|
wechatSocialStartedAt: String(Date.now()),
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.keys(pairs).forEach((key) => {
|
||||||
|
try {
|
||||||
|
sessionStorage.setItem(key, pairs[key]);
|
||||||
|
} catch (e) {}
|
||||||
|
try {
|
||||||
|
localStorage.setItem(key, pairs[key]);
|
||||||
|
} catch (e) {}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
startWechatAccountBind() {
|
||||||
|
const layer = typeof layui !== 'undefined' && layui.layer;
|
||||||
|
const token = typeof ApiClient !== 'undefined' && ApiClient.token ? ApiClient.token() : '';
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
if (layer) layer.msg('请先登录后再绑定微信', { icon: 2 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const $button = this.$('#wechat-bind-button');
|
||||||
|
this.rememberWechatAccountBindMode();
|
||||||
|
$button.addClass('is-loading').prop('disabled', true);
|
||||||
|
|
||||||
|
ApiClient.get(ApiClient.API.authWechatOpen, { domain: this.getWechatLoginDomain() })
|
||||||
|
.then((res) => {
|
||||||
|
if (!ApiClient.isSuccess(res)) {
|
||||||
|
throw new Error((res && res.msg) || '微信授权地址获取失败');
|
||||||
|
}
|
||||||
|
const authUrl = this.extractWechatAuthUrl(res);
|
||||||
|
if (!authUrl) throw new Error('后端未返回微信授权地址');
|
||||||
|
window.location.href = authUrl;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
const message = error.message || '微信绑定暂不可用,请稍后再试';
|
||||||
|
if (layer) layer.msg(message, { icon: 2 });
|
||||||
|
else alert(message);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
$button.removeClass('is-loading').prop('disabled', false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
installUnifiedPagination() {
|
installUnifiedPagination() {
|
||||||
if (typeof window === 'undefined' || typeof document === 'undefined') return;
|
if (typeof window === 'undefined' || typeof document === 'undefined') return;
|
||||||
this.injectPaginationStyles();
|
this.injectPaginationStyles();
|
||||||
|
|||||||
Reference in New Issue
Block a user