Files
shencaisuan/html/repwd.html
T
2026-08-21 17:06:12 +08:00

587 lines
21 KiB
HTML

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title id="page-title">神彩算 - 找回密码</title>
<script src="../public/css/layui/layui.js"></script>
<script src="../config.js"></script>
<script src="../utils/ApiClient.js"></script>
<script src="../utils/DateUtil.js"></script>
<script src="../utils/CommonUtil.js"></script>
<script src="../utils/AuthPageUtil.js"></script>
<link rel="stylesheet" href="../public/css/layui/css/layui.css" />
<link rel="stylesheet" href="../public/css/public.css" />
<link rel="stylesheet" href="../public/css/auth.css" />
</head>
<body>
<main class="auth-container">
<div class="auth-card">
<div class="auth-header">
<a class="auth-logo" href="index.html" aria-label="返回首页">
<img src="../images/scs-logo.png" alt="神彩算" />
</a>
<div class="auth-subtitle">神彩算 - 找回密码</div>
</div>
<div class="auth-body">
<form id="resetPasswordForm" autocomplete="off">
<div class="form-group" id="phoneContainer">
<label for="phone">手机号</label>
<input
type="text"
id="phone"
class="form-input"
placeholder="请输入手机号"
maxlength="11"
/>
<div class="error-message" id="phoneError"></div>
</div>
<div class="form-group" id="captchaGroup" style="visibility: hidden">
<label for="captchaInput">滑动验证</label>
<div class="captcha-row">
<input
type="text"
id="captchaInput"
class="form-input"
placeholder="请输入验证码"
maxlength="6"
/>
<div
class="captcha-img-box"
id="captchaContainer"
onclick="refreshCaptcha()"
>
<img id="captchaImage" src="" alt="验证码" />
</div>
</div>
<div
class="captcha-refresh-link"
id="captchaRefreshLink"
onclick="refreshCaptcha()"
>
看不清?换一张
</div>
<div class="error-message" id="captchaError"></div>
<input type="hidden" id="captchaId" value="" />
</div>
<div class="form-group" id="smsCodeContainer">
<label for="smsCode">短信验证码</label>
<div class="auth-sms-row">
<input
type="text"
id="smsCode"
class="form-input"
placeholder="请输入短信验证码"
maxlength="6"
/>
<button type="button" class="auth-sms-button" id="sendSmsBtn">
获取验证码
</button>
</div>
<div class="error-message" id="smsCodeError"></div>
</div>
<div class="form-group" id="passwordContainer">
<label for="password">新密码</label>
<input
type="password"
id="password"
class="form-input"
placeholder="请输入新密码(6-20位)"
maxlength="20"
/>
<div class="error-message" id="passwordError"></div>
</div>
<div class="form-group" id="confirmPasswordContainer">
<label for="confirmPassword">确认密码</label>
<input
type="password"
id="confirmPassword"
class="form-input"
placeholder="请再次输入新密码"
maxlength="20"
/>
<div class="error-message" id="confirmPasswordError"></div>
</div>
<button type="submit" class="auth-submit-btn" id="resetBtn">
确认重置
</button>
<div class="auth-links">
<a href="login.html">想起密码?返回登录</a>
</div>
</form>
</div>
</div>
</main>
<script src="../public/js/jquery.js"></script>
<script src="../public/js/md5.js"></script>
<script>
CommonUtil.loadPageConfig({ ads: false, links: false });
const $ = layui.$;
let layerInstance = null;
let currentCaptchaId = "";
let currentVerifyType = "captcha";
let currentSlideToken = "";
let currentSlideUuid = "";
let currentSlideX = 0;
let pendingSmsAfterVerify = false;
let smsCodeIssuedPhone = "";
function showError(field, message) {
const containerId =
field === "captcha" ? "captchaGroup" : field + "Container";
$("#" + field + "Error")
.text(message)
.show();
$("#" + containerId).addClass("error");
}
function clearError(field) {
const containerId =
field === "captcha" ? "captchaGroup" : field + "Container";
$("#" + field + "Error")
.hide()
.text("");
$("#" + containerId).removeClass("error");
}
function loadVerifyConfig() {
return AuthPageUtil.loadVerifyType().then(function (type) {
currentVerifyType = type;
});
}
function refreshCaptcha() {
$("#captchaGroup").css("visibility", "visible");
currentCaptchaId = "";
currentSlideUuid = "";
currentSlideX = 0;
$("#captchaId").val("");
$("#captchaInput").val("");
$("#captchaGroup").removeClass("is-slide-mode");
$("#sendSmsBtn").prop("disabled", false);
if (currentVerifyType === "slide") {
renderSlideCaptchaEntry();
return;
}
$("#captchaInput").show();
$("#captchaRefreshLink").show().text("看不清?换一张");
$("#captchaContainer")
.attr("onclick", "refreshCaptcha()")
.html('<img id="captchaImage" src="" alt="验证码" />');
$("#captchaImage").attr("src", ApiClient.transparentGif());
ApiClient.get(ApiClient.API.authCode, {}, { publicRequest: true })
.then(function (res) {
if (ApiClient.isSuccess(res) && res.data) {
currentCaptchaId = res.data.uuid || res.data.id || "";
$("#captchaId").val(currentCaptchaId);
$("#captchaImage").attr(
"src",
res.data.img || res.data.image || "",
);
clearError("captcha");
return;
}
layerInstance.msg(
"获取验证码失败:" + ((res && res.msg) || "未知错误"),
{ icon: 2 },
);
})
.catch(function () {
layerInstance.msg("网络错误,无法获取验证码", { icon: 2 });
});
}
function completeSlideCaptcha(result) {
const data = result || {};
const nested = data.data || {};
currentSlideToken =
data.slideToken || data.token || nested.slideToken || nested.token || "";
currentSlideUuid =
data.slideUuid || data.uuid || nested.slideUuid || nested.uuid || "";
currentSlideX =
Number(data.slideX || data.x || nested.slideX || nested.x || 0) || 0;
if (!currentSlideToken && !currentSlideUuid) {
layerInstance.msg("滑动验证未返回凭证,请重试", { icon: 2 });
renderSlideCaptchaEntry();
return;
}
$("#captchaContainer").html(
'<div class="slide-verify-card is-success"><div class="slide-verify-mark">✓</div><div class="slide-verify-main"><div class="slide-verify-title">验证已通过</div><div class="slide-verify-desc">可以获取短信或重置</div></div><div class="slide-verify-action">完成</div></div>',
);
$("#sendSmsBtn").prop("disabled", false);
if (pendingSmsAfterVerify) {
pendingSmsAfterVerify = false;
sendSmsCode();
}
}
function openSlideCaptcha() {
AuthPageUtil.loadSlideCaptchaScript()
.then(function (SlideCaptcha) {
SlideCaptcha.open({
title: "找回密码安全验证",
subtitle: "拖动滑块完成验证后继续重置",
apiBase: ApiClient.baseUrl || "",
tenantId: ApiClient.tenantId || "936208",
headers: ApiClient.publicHeaders(),
onSuccess: completeSlideCaptcha,
onCancel: function () {
currentSlideToken = "";
currentSlideUuid = "";
currentSlideX = 0;
},
onError: function (error) {
layerInstance.msg(
(error && error.message) || "滑动验证失败,请重试",
{ icon: 2 },
);
renderSlideCaptchaEntry();
},
});
})
.catch(function (error) {
layerInstance.msg(error.message || "滑动验证组件加载失败", {
icon: 2,
});
});
}
function renderSlideCaptchaEntry() {
$("#captchaInput").hide();
$("#captchaRefreshLink").hide();
$("#captchaGroup").addClass("is-slide-mode");
$("#sendSmsBtn").prop("disabled", false);
$("#captchaContainer")
.removeAttr("onclick")
.html(
'<div class="slide-verify-card" role="button" tabindex="0"><div class="slide-verify-mark">↔</div><div class="slide-verify-main"><div class="slide-verify-title">拖动滑块验证</div><div class="slide-verify-desc">完成后获取短信</div></div><div class="slide-verify-action">开始</div></div>',
);
$(".slide-verify-card")
.off("click keydown")
.on("click", openSlideCaptcha)
.on("keydown", function (event) {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
openSlideCaptcha();
}
});
}
function validatePhone(show) {
const value = $("#phone").val().trim();
if (!AuthPageUtil.isPhone(value)) {
if (show) showError("phone", "请输入正确的手机号");
return false;
}
clearError("phone");
return true;
}
function validateSmsCode(show) {
const value = $("#smsCode").val().trim();
if (!/^\d{4,6}$/.test(value)) {
if (show) showError("smsCode", "请输入4-6位短信验证码");
return false;
}
clearError("smsCode");
return true;
}
function hasIssuedSmsCodeForCurrentPhone() {
const phone = $("#phone").val().trim();
return !!phone && smsCodeIssuedPhone === phone;
}
function handlePhoneInput() {
const phone = $("#phone").val().trim();
if (smsCodeIssuedPhone && smsCodeIssuedPhone !== phone) {
smsCodeIssuedPhone = "";
$("#smsCode").val("");
}
}
function validateSmsCodePhone(show) {
if (!validateSmsCode(show)) return false;
if (!hasIssuedSmsCodeForCurrentPhone()) {
if (show) showError("smsCode", "请先获取当前手机号的短信验证码");
return false;
}
clearError("smsCode");
return true;
}
function validatePassword(show) {
if (!AuthPageUtil.isPassword($("#password").val())) {
if (show) showError("password", "密码长度应为6-20位");
return false;
}
clearError("password");
return true;
}
function validateConfirmPassword(show) {
if ($("#confirmPassword").val() !== $("#password").val()) {
if (show) showError("confirmPassword", "两次输入的密码不一致");
return false;
}
clearError("confirmPassword");
return true;
}
function getVerifyParams(show) {
if (currentVerifyType === "slide") {
if (!currentSlideToken && !currentSlideUuid) {
if (show) layerInstance.msg("请先完成滑动验证", { icon: 2 });
return null;
}
return {
verifyType: "slide",
slideToken: currentSlideToken,
slideUuid: currentSlideUuid,
slideX: currentSlideX,
};
}
const code = $("#captchaInput").val().trim();
if (!code || !currentCaptchaId) {
if (show) showError("captcha", "请填写有效验证码");
return null;
}
clearError("captcha");
return { verifyType: "captcha", code, uuid: currentCaptchaId };
}
function getSmsWaitSeconds() {
const smsCountdownApi = ApiClient.API["authSmsCountdown"];
if (!smsCountdownApi) return Promise.resolve(0);
return ApiClient.get(
smsCountdownApi,
{ phoneNumber: $("#phone").val().trim(), scene: "auth" },
{ publicRequest: true },
)
.then(function (res) {
if (!ApiClient.isSuccess(res)) return 0;
const data = res.data || {};
return Number(data.seconds || data.waitSeconds || data.remainingSeconds || data.countdown || 0) || 0;
})
.catch(function () {
return 0;
});
}
function refreshDialogCaptcha() {
currentCaptchaId = "";
$("#captchaInput").val("");
$("#captchaId").val("");
$("#captchaDialogInput").val("");
$("#captchaDialogImage").attr("src", "");
return ApiClient.get(ApiClient.API.authCode, {}, { publicRequest: true })
.then(function (res) {
if (ApiClient.isSuccess(res) && res.data) {
currentCaptchaId = res.data.uuid || res.data.id || "";
$("#captchaId").val(currentCaptchaId);
$("#captchaDialogImage").attr("src", res.data.img || res.data.image || "");
return;
}
layerInstance.msg("获取验证码失败,请重试", { icon: 2 });
})
.catch(function () {
layerInstance.msg("网络错误,无法获取验证码", { icon: 2 });
});
}
function openImageCaptchaDialog() {
const dialogHtml =
'<div class="captcha-dialog">' +
'<div class="captcha-dialog-title">完成图片验证码</div>' +
'<div class="captcha-dialog-desc">请输入右侧图片中的验证码,通过后将自动发送短信验证码。</div>' +
'<div class="captcha-dialog-row">' +
'<input type="text" class="captcha-dialog-input" id="captchaDialogInput" maxlength="6" placeholder="请输入验证码" />' +
'<div class="captcha-dialog-img-box" id="captchaDialogRefresh">' +
'<img class="captcha-dialog-img" id="captchaDialogImage" src="" alt="验证码" />' +
'</div></div>' +
'<div class="captcha-dialog-tip">看不清?<span id="captchaDialogRefreshText">换一张</span></div>' +
'<div class="captcha-dialog-actions">' +
'<button type="button" class="captcha-dialog-btn" id="captchaDialogCancel">取消</button>' +
'<button type="button" class="captcha-dialog-btn primary" id="captchaDialogConfirm">确认发送</button>' +
'</div></div>';
const dialogIndex = layerInstance.open({
type: 1,
title: false,
area: ["420px", "auto"],
shadeClose: true,
closeBtn: 1,
content: dialogHtml,
success: function () {
refreshDialogCaptcha();
$("#captchaDialogRefresh, #captchaDialogRefreshText").on("click", refreshDialogCaptcha);
$("#captchaDialogCancel").on("click", function () {
layerInstance.close(dialogIndex);
});
$("#captchaDialogInput").on("keydown", function (event) {
if (event.key === "Enter") {
event.preventDefault();
$("#captchaDialogConfirm").trigger("click");
}
});
$("#captchaDialogConfirm").on("click", function () {
const code = $("#captchaDialogInput").val().trim();
if (!code) {
layerInstance.msg("请输入验证码", { icon: 2 });
return;
}
$("#captchaInput").val(code);
layerInstance.close(dialogIndex);
sendSmsCode();
});
setTimeout(function () {
$("#captchaDialogInput").focus();
}, 80);
},
end: function () {
$("#sendSmsBtn").prop("disabled", false);
},
});
}
function sendSmsCode() {
if (!validatePhone(true)) return;
if (currentVerifyType === "slide" && !currentSlideToken && !currentSlideUuid) {
pendingSmsAfterVerify = true;
openSlideCaptcha();
return;
}
if (currentVerifyType === "captcha" && !$("#captchaInput").val().trim()) {
$("#sendSmsBtn").prop("disabled", true);
openImageCaptchaDialog();
return;
}
const verifyParams = getVerifyParams(true);
if (!verifyParams) return;
$("#sendSmsBtn").prop("disabled", true);
getSmsWaitSeconds().then(function (waitSeconds) {
if (waitSeconds > 0) {
layerInstance.msg("请 " + waitSeconds + " 秒后再获取短信验证码", { icon: 0 });
AuthPageUtil.startSmsCountdown("#sendSmsBtn", waitSeconds);
throw new Error("__SMS_WAIT__");
}
return AuthPageUtil.sendSmsCode(
Object.assign(
{ phoneNumber: $("#phone").val().trim() },
verifyParams,
),
);
})
.then(function (res) {
if (ApiClient.isSuccess(res)) {
smsCodeIssuedPhone = $("#phone").val().trim();
layerInstance.msg("短信验证码已发送", { icon: 1 });
AuthPageUtil.startSmsCountdown("#sendSmsBtn");
return;
}
$("#sendSmsBtn").prop("disabled", false);
layerInstance.msg((res && res.msg) || "短信验证码发送失败", {
icon: 2,
});
refreshCaptcha();
})
.catch(function (error) {
if (error && error.message === "__SMS_WAIT__") return;
$("#sendSmsBtn").prop("disabled", false);
layerInstance.msg("网络异常,短信发送失败", { icon: 2 });
refreshCaptcha();
});
}
function validateForm() {
if (
!validatePhone(true) ||
!validateSmsCodePhone(true) ||
!validatePassword(true) ||
!validateConfirmPassword(true)
)
return false;
if (!getVerifyParams(true)) return false;
return true;
}
function buildResetData() {
return Object.assign(
{
phoneNumber: $("#phone").val().trim(),
smsCode: $("#smsCode").val().trim(),
password: hex_md5($("#password").val()),
},
getVerifyParams(false) || {},
);
}
function submitResetPassword() {
const payload = buildResetData();
$("#resetBtn").prop("disabled", true).text("重置中...");
ApiClient.post(ApiClient.API.authForgotPasswordReset, payload, {
publicRequest: true,
})
.then(function (res) {
if (ApiClient.isSuccess(res)) {
layerInstance.msg(
"密码重置成功,即将前往登录",
{ icon: 1, time: 1200 },
function () {
NavigationUtil.open("login.html");
},
);
return;
}
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 () {
layerInstance = layui.layer;
loadVerifyConfig().then(refreshCaptcha);
$("#sendSmsBtn").on("click", sendSmsCode);
$("#resetPasswordForm").on("submit", function (event) {
event.preventDefault();
if (validateForm()) submitResetPassword();
});
$("#phone").on("input", handlePhoneInput).on("blur", function () {
validatePhone(true);
});
$("#smsCode").on("blur", function () {
validateSmsCode(true);
});
$("#password").on("blur", function () {
validatePassword(true);
});
$("#confirmPassword").on("blur", function () {
validateConfirmPassword(true);
});
$("#captchaInput").on("input", function () {
clearError("captcha");
});
});
</script>
</body>
</html>