perf: 登录

This commit is contained in:
pan 2024-03-13 16:59:32 +08:00
parent 52ab1f8a33
commit cdac80a1c2
17 changed files with 150 additions and 195 deletions

View File

@ -82,7 +82,7 @@ login:
verifyCodeSixReg: Please enter a 6-digit verify code verifyCodeSixReg: Please enter a 6-digit verify code
phoneReg: Please enter the phone phoneReg: Please enter the phone
phoneCorrectReg: Please enter the correct phone number format phoneCorrectReg: Please enter the correct phone number format
passwordRuleReg: The password format should be any combination of 8-18 digits passwordRuleReg: The password format should be any combination of 5-12 digits
passwordSureReg: Please enter confirm password passwordSureReg: Please enter confirm password
passwordDifferentReg: The two passwords do not match! passwordDifferentReg: The two passwords do not match!
passwordUpdateReg: Password has been updated passwordUpdateReg: Password has been updated

View File

@ -82,7 +82,7 @@ login:
verifyCodeSixReg: 请输入6位数字验证码 verifyCodeSixReg: 请输入6位数字验证码
phoneReg: 请输入手机号码 phoneReg: 请输入手机号码
phoneCorrectReg: 请输入正确的手机号码格式 phoneCorrectReg: 请输入正确的手机号码格式
passwordRuleReg: 密码格式应为8-18位数字、字母、符号的任意两种组合 passwordRuleReg: 密码格式应为5-12位数字、字母、符号的任意两种组合
passwordSureReg: 请输入确认密码 passwordSureReg: 请输入确认密码
passwordDifferentReg: 两次密码不一致! passwordDifferentReg: 两次密码不一致!
passwordUpdateReg: 修改密码成功 passwordUpdateReg: 修改密码成功

View File

@ -14,7 +14,6 @@ export default defineFakeRoute([
// 一个用户可能有多个角色 // 一个用户可能有多个角色
roles: ["admin"], roles: ["admin"],
accessToken: "eyJhbGciOiJIUzUxMiJ9.admin", accessToken: "eyJhbGciOiJIUzUxMiJ9.admin",
refreshToken: "eyJhbGciOiJIUzUxMiJ9.adminRefresh",
expires: "2030/10/30 00:00:00" expires: "2030/10/30 00:00:00"
} }
}; };
@ -26,7 +25,6 @@ export default defineFakeRoute([
// 一个用户可能有多个角色 // 一个用户可能有多个角色
roles: ["common"], roles: ["common"],
accessToken: "eyJhbGciOiJIUzUxMiJ9.common", accessToken: "eyJhbGciOiJIUzUxMiJ9.common",
refreshToken: "eyJhbGciOiJIUzUxMiJ9.commonRefresh",
expires: "2030/10/30 00:00:00" expires: "2030/10/30 00:00:00"
} }
}; };

View File

@ -1,27 +0,0 @@
import { defineFakeRoute } from "vite-plugin-fake-server/client";
// 模拟刷新token接口
export default defineFakeRoute([
{
url: "/refresh-token",
method: "post",
response: ({ body }) => {
if (body.refreshToken) {
return {
success: true,
data: {
accessToken: "eyJhbGciOiJIUzUxMiJ9.newAdmin",
refreshToken: "eyJhbGciOiJIUzUxMiJ9.newAdminRefresh",
// `expires`选择这种日期格式是为了方便调试,后端直接设置时间戳或许更方便(每次都应该递增)。如果后端返回的是时间戳格式,前端开发请来到这个目录`src/utils/auth.ts`,把第`38`行的代码换成expires = data.expires即可。
expires: "2030/10/30 23:59:59"
}
};
} else {
return {
success: false,
data: {}
};
}
}
}
]);

View File

@ -76,7 +76,8 @@
"vue-i18n": "^9.10.1", "vue-i18n": "^9.10.1",
"vue-router": "^4.3.0", "vue-router": "^4.3.0",
"vue-tippy": "^6.4.1", "vue-tippy": "^6.4.1",
"vue-types": "^5.1.1" "vue-types": "^5.1.1",
"jsencrypt": "^3.3.2"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^18.6.1", "@commitlint/cli": "^18.6.1",

7
pnpm-lock.yaml generated
View File

@ -44,6 +44,9 @@ dependencies:
js-cookie: js-cookie:
specifier: ^3.0.5 specifier: ^3.0.5
version: 3.0.5 version: 3.0.5
jsencrypt:
specifier: ^3.3.2
version: 3.3.2
localforage: localforage:
specifier: ^1.10.0 specifier: ^1.10.0
version: 1.10.0 version: 1.10.0
@ -3907,6 +3910,10 @@ packages:
argparse: 2.0.1 argparse: 2.0.1
dev: true dev: true
/jsencrypt@3.3.2:
resolution: {integrity: sha512-arQR1R1ESGdAxY7ZheWr12wCaF2yF47v5qpB76TtV64H1pyGudk9Hvw8Y9tb/FiTIaaTRUyaSnm5T/Y53Ghm/A==}
dev: false
/jsesc@2.5.2: /jsesc@2.5.2:
resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
engines: {node: '>=4'} engines: {node: '>=4'}

67
src/api/login.ts Normal file
View File

@ -0,0 +1,67 @@
import { http } from "@/utils/http";
import { ApiAbstract } from "@/utils/http/ApiAbstract";
import { baseUrlAuth } from "./utils";
import Cookies from "js-cookie";
export class UserResult extends ApiAbstract {
declare data: {
img: string;
uuid: string;
};
}
export interface UserUser {
avatarName?: string;
avatarPath?: string;
createTime?: Date;
dept?: any;
deptId?: 0;
email?: string;
enabled?: boolean;
gender?: string;
id?: Number;
isAdmin?: boolean;
jobs?: any;
nickName?: string;
password?: string;
phone?: string;
roles?: any;
updateBy?: string;
updateTime?: Date;
username?: string;
}
export interface User {
authorities?: any;
dataScopes?: any;
roles?: any;
user?: UserUser;
}
export class UserLogResult extends ApiAbstract {
declare data: {
token: string;
user: User;
username: string;
roles: Array<string>;
};
}
/** 获取验证码 */
export const getCode = () => {
return http.request<UserResult>("get", baseUrlAuth("code"));
};
/** 登录 */
export const login = (data?: object) => {
return http.request<UserLogResult>("post", baseUrlAuth("login"), { data });
};
/** 获取用户信息 */
export const userInfo = () => {
return http.request<ApiAbstract>("get", baseUrlAuth("info"), null, {
headers: {
Authorization: Cookies.get("token")
}
});
};
/** 退出登录 */
export const logout = () => {
return http.request<UserResult>("delete", baseUrlAuth("logout"));
};

View File

@ -1,39 +0,0 @@
import { http } from "@/utils/http";
export type UserResult = {
success: boolean;
data: {
/** 用户名 */
username: string;
/** 当前登陆用户的角色 */
roles: Array<string>;
/** `token` */
accessToken: string;
/** 用于调用刷新`accessToken`的接口时所需的`token` */
refreshToken: string;
/** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx' */
expires: Date;
};
};
export type RefreshTokenResult = {
success: boolean;
data: {
/** `token` */
accessToken: string;
/** 用于调用刷新`accessToken`的接口时所需的`token` */
refreshToken: string;
/** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx' */
expires: Date;
};
};
/** 登录 */
export const getLogin = (data?: object) => {
return http.request<UserResult>("post", "/login", { data });
};
/** 刷新token */
export const refreshTokenApi = (data?: object) => {
return http.request<RefreshTokenResult>("post", "/refresh-token", { data });
};

View File

@ -1,85 +1,30 @@
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import { getCode } from "@/api/login";
/** /**
* *
* @param width - * @param width -
* @param height - * @param height -
*/ */
export const useImageVerify = (width = 120, height = 40) => { export const useImageVerify = () => {
const domRef = ref<HTMLCanvasElement>();
const imgCode = ref(""); const imgCode = ref("");
const imgSrc = ref("");
function setImgCode(code: string) {
imgCode.value = code;
}
function getImgCode() { function getImgCode() {
if (!domRef.value) return; getCode().then(data => {
imgCode.value = draw(domRef.value, width, height); if (data.status) {
imgCode.value = data.data.uuid;
imgSrc.value = data.data.img;
}
});
} }
onMounted(() => { onMounted(() => {
getImgCode(); getImgCode();
}); });
return { return {
domRef, imgSrc,
imgCode, imgCode,
setImgCode,
getImgCode getImgCode
}; };
}; };
function randomNum(min: number, max: number) {
const num = Math.floor(Math.random() * (max - min) + min);
return num;
}
function randomColor(min: number, max: number) {
const r = randomNum(min, max);
const g = randomNum(min, max);
const b = randomNum(min, max);
return `rgb(${r},${g},${b})`;
}
function draw(dom: HTMLCanvasElement, width: number, height: number) {
let imgCode = "";
const NUMBER_STRING = "0123456789";
const ctx = dom.getContext("2d");
if (!ctx) return imgCode;
ctx.fillStyle = randomColor(180, 230);
ctx.fillRect(0, 0, width, height);
for (let i = 0; i < 4; i += 1) {
const text = NUMBER_STRING[randomNum(0, NUMBER_STRING.length)];
imgCode += text;
const fontSize = randomNum(18, 41);
const deg = randomNum(-30, 30);
ctx.font = `${fontSize}px Simhei`;
ctx.textBaseline = "top";
ctx.fillStyle = randomColor(80, 150);
ctx.save();
ctx.translate(30 * i + 15, 15);
ctx.rotate((deg * Math.PI) / 180);
ctx.fillText(text, -15 + 5, -15);
ctx.restore();
}
for (let i = 0; i < 5; i += 1) {
ctx.beginPath();
ctx.moveTo(randomNum(0, width), randomNum(0, height));
ctx.lineTo(randomNum(0, width), randomNum(0, height));
ctx.strokeStyle = randomColor(180, 230);
ctx.closePath();
ctx.stroke();
}
for (let i = 0; i < 41; i += 1) {
ctx.beginPath();
ctx.arc(randomNum(0, width), randomNum(0, height), 1, 0, 2 * Math.PI);
ctx.closePath();
ctx.fillStyle = randomColor(150, 200);
ctx.fill();
}
return imgCode;
}

View File

@ -20,14 +20,8 @@ const props = withDefaults(defineProps<Props>(), {
const emit = defineEmits<Emits>(); const emit = defineEmits<Emits>();
const { domRef, imgCode, setImgCode, getImgCode } = useImageVerify(); const { imgSrc, imgCode, getImgCode } = useImageVerify();
watch(
() => props.code,
newValue => {
setImgCode(newValue);
}
);
watch(imgCode, newValue => { watch(imgCode, newValue => {
emit("update:code", newValue); emit("update:code", newValue);
}); });
@ -36,11 +30,10 @@ defineExpose({ getImgCode });
</script> </script>
<template> <template>
<canvas <el-image
ref="domRef" style="width: 120; height: 40"
width="120" :src="imgSrc"
height="40" fit="contain"
class="cursor-pointer"
@click="getImgCode" @click="getImgCode"
/> />
</template> </template>

View File

@ -38,7 +38,6 @@ export type setType = {
export type userType = { export type userType = {
username?: string; username?: string;
roles?: Array<string>; roles?: Array<string>;
verifyCode?: string;
currentPage?: number; currentPage?: number;
isRemembered?: boolean; isRemembered?: boolean;
loginDay?: number; loginDay?: number;

View File

@ -4,10 +4,10 @@ import type { userType } from "./types";
import { routerArrays } from "@/layout/types"; import { routerArrays } from "@/layout/types";
import { router, resetRouter } from "@/router"; import { router, resetRouter } from "@/router";
import { storageLocal } from "@pureadmin/utils"; import { storageLocal } from "@pureadmin/utils";
import { getLogin, refreshTokenApi } from "@/api/user"; import { type UserLogResult, login } from "@/api/login";
import type { UserResult, RefreshTokenResult } from "@/api/user";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import { type DataInfo, setToken, removeToken, userKey } from "@/utils/auth"; import { type DataInfo, setToken, removeToken, userKey } from "@/utils/auth";
import { encrypt } from "@/utils/rsaEncrypt";
export const useUserStore = defineStore({ export const useUserStore = defineStore({
id: "pure-user", id: "pure-user",
@ -16,8 +16,6 @@ export const useUserStore = defineStore({
username: storageLocal().getItem<DataInfo<number>>(userKey)?.username ?? "", username: storageLocal().getItem<DataInfo<number>>(userKey)?.username ?? "",
// 页面级别权限 // 页面级别权限
roles: storageLocal().getItem<DataInfo<number>>(userKey)?.roles ?? [], roles: storageLocal().getItem<DataInfo<number>>(userKey)?.roles ?? [],
// 前端生成的验证码(按实际需求替换)
verifyCode: "",
// 判断登录页面显示哪个组件0登录默认、1手机登录、2二维码登录、3注册、4忘记密码 // 判断登录页面显示哪个组件0登录默认、1手机登录、2二维码登录、3注册、4忘记密码
currentPage: 0, currentPage: 0,
// 是否勾选了登录页的免登录 // 是否勾选了登录页的免登录
@ -34,10 +32,6 @@ export const useUserStore = defineStore({
SET_ROLES(roles: Array<string>) { SET_ROLES(roles: Array<string>) {
this.roles = roles; this.roles = roles;
}, },
/** 存储前端生成的验证码 */
SET_VERIFYCODE(verifyCode: string) {
this.verifyCode = verifyCode;
},
/** 存储登录页面显示哪个组件 */ /** 存储登录页面显示哪个组件 */
SET_CURRENTPAGE(value: number) { SET_CURRENTPAGE(value: number) {
this.currentPage = value; this.currentPage = value;
@ -52,11 +46,23 @@ export const useUserStore = defineStore({
}, },
/** 登入 */ /** 登入 */
async loginByUsername(data) { async loginByUsername(data) {
return new Promise<UserResult>((resolve, reject) => { return new Promise<UserLogResult>((resolve, reject) => {
getLogin(data) login({
username: data.username,
password: encrypt(data.password),
code: data.code,
uuid: data.uuid
})
.then(data => { .then(data => {
if (data) { if (data) {
setToken(data.data); //;
setToken({
accessToken: data.data.token,
username: data.data?.user?.user?.nickName,
expires: new Date("2033-03-15T12:00:00Z"),
roles: data.data?.user?.roles,
user: data.data?.user?.user
});
resolve(data); resolve(data);
} }
}) })
@ -73,21 +79,6 @@ export const useUserStore = defineStore({
useMultiTagsStoreHook().handleTags("equal", [...routerArrays]); useMultiTagsStoreHook().handleTags("equal", [...routerArrays]);
resetRouter(); resetRouter();
router.push("/login"); router.push("/login");
},
/** 刷新`token` */
async handRefreshToken(data) {
return new Promise<RefreshTokenResult>((resolve, reject) => {
refreshTokenApi(data)
.then(data => {
if (data) {
setToken(data.data);
resolve(data);
}
})
.catch(error => {
reject(error);
});
});
} }
} }
}); });

View File

@ -7,12 +7,12 @@ export interface DataInfo<T> {
accessToken: string; accessToken: string;
/** `accessToken`的过期时间(时间戳) */ /** `accessToken`的过期时间(时间戳) */
expires: T; expires: T;
/** 用于调用刷新accessToken的接口时所需的token */
refreshToken: string;
/** 用户名 */ /** 用户名 */
username?: string; username?: string;
/** 当前登陆用户的角色 */ /** 当前登陆用户的角色 */
roles?: Array<string>; roles?: Array<string>;
/** 当前登陆用户的角色 */
user?: any;
} }
export const userKey = "user-info"; export const userKey = "user-info";
@ -35,13 +35,13 @@ export function getToken(): DataInfo<number> {
/** /**
* @description `token``token` * @description `token``token`
* `accessToken`访使`token``refreshToken``accessToken``token``refreshToken`30`accessToken`2`expires``accessToken` * `accessToken`访使`token``accessToken``token`30`accessToken`2`expires``accessToken`
* `accessToken``expires`key值为authorized-token的cookie里 * `accessToken``expires`key值为authorized-token的cookie里
* `username``roles``refreshToken``expires`key值为`user-info`localStorage里`multipleTabsKey` * `username``roles``expires`key值为`user-info`localStorage里`multipleTabsKey`
*/ */
export function setToken(data: DataInfo<Date>) { export function setToken(data: DataInfo<Date>) {
let expires = 0; let expires = 0;
const { accessToken, refreshToken } = data; const { accessToken } = data;
const { isRemembered, loginDay } = useUserStoreHook(); const { isRemembered, loginDay } = useUserStoreHook();
expires = new Date(data.expires).getTime(); // 如果后端直接设置时间戳将此处代码改为expires = data.expires然后把上面的DataInfo<Date>改成DataInfo<number>即可 expires = new Date(data.expires).getTime(); // 如果后端直接设置时间戳将此处代码改为expires = data.expires然后把上面的DataInfo<Date>改成DataInfo<number>即可
const cookieString = JSON.stringify({ accessToken, expires }); const cookieString = JSON.stringify({ accessToken, expires });
@ -62,11 +62,15 @@ export function setToken(data: DataInfo<Date>) {
: {} : {}
); );
function setUserKey(username: string, roles: Array<string>) { function setUserKey(
accessToken: string,
username: string,
roles: Array<string>
) {
useUserStoreHook().SET_USERNAME(username); useUserStoreHook().SET_USERNAME(username);
useUserStoreHook().SET_ROLES(roles); useUserStoreHook().SET_ROLES(roles);
storageLocal().setItem(userKey, { storageLocal().setItem(userKey, {
refreshToken, accessToken,
expires, expires,
username, username,
roles roles
@ -75,13 +79,13 @@ export function setToken(data: DataInfo<Date>) {
if (data.username && data.roles) { if (data.username && data.roles) {
const { username, roles } = data; const { username, roles } = data;
setUserKey(username, roles); setUserKey(accessToken, username, roles);
} else { } else {
const username = const username =
storageLocal().getItem<DataInfo<number>>(userKey)?.username ?? ""; storageLocal().getItem<DataInfo<number>>(userKey)?.username ?? "";
const roles = const roles =
storageLocal().getItem<DataInfo<number>>(userKey)?.roles ?? []; storageLocal().getItem<DataInfo<number>>(userKey)?.roles ?? [];
setUserKey(username, roles); setUserKey(accessToken, username, roles);
} }
} }

View File

@ -12,6 +12,8 @@ import type {
import { stringify } from "qs"; import { stringify } from "qs";
import NProgress from "../progress"; import NProgress from "../progress";
import { getToken, formatToken } from "@/utils/auth"; import { getToken, formatToken } from "@/utils/auth";
import { message } from "@/utils/message";
// 相关配置请参考www.axios-js.com/zh-cn/docs/#axios-request-config-1 // 相关配置请参考www.axios-js.com/zh-cn/docs/#axios-request-config-1
const defaultConfig: AxiosRequestConfig = { const defaultConfig: AxiosRequestConfig = {
// 请求超时时间 // 请求超时时间
@ -91,6 +93,7 @@ class PureHttp {
}); });
}, },
error => { error => {
message("请求异常!", { type: "error" });
return Promise.reject(error); return Promise.reject(error);
} }
); );
@ -116,6 +119,7 @@ class PureHttp {
return response.data; return response.data;
}, },
(error: PureHttpError) => { (error: PureHttpError) => {
message("服务异常!", { type: "error" });
const $error = error; const $error = error;
$error.isCancelRequest = Axios.isCancel($error); $error.isCancelRequest = Axios.isCancel($error);
// 关闭进度条动画 // 关闭进度条动画

14
src/utils/rsaEncrypt.ts Normal file
View File

@ -0,0 +1,14 @@
import JSEncrypt from "jsencrypt";
// 密钥对生成 http://web.chacuo.net/netrsakeypair
const publicKey =
"MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANL378k3RiZHWx5AfJqdH9xRNBmD9wGD\n" +
"2iRe41HdTNF8RUhNnHit5NpMNtGL0NPTSSpPjjI1kJfVorRvaQerUgkCAwEAAQ==";
// 加密
export const encrypt = (txt: string) => {
const crypt = new JSEncrypt();
// 设置公钥
crypt.setPublicKey(publicKey);
const enc = crypt.encrypt(txt);
return enc; // 对需要加密的数据进行加密
};

View File

@ -68,9 +68,14 @@ const onLogin = async (formEl: FormInstance | undefined) => {
if (valid) { if (valid) {
loading.value = true; loading.value = true;
useUserStoreHook() useUserStoreHook()
.loginByUsername({ username: ruleForm.username, password: "admin123" }) .loginByUsername({
username: ruleForm.username,
password: ruleForm.password,
code: ruleForm.verifyCode,
uuid: imgCode.value
})
.then(res => { .then(res => {
if (res.success) { if (res) {
// //
return initRouter().then(() => { return initRouter().then(() => {
disabled.value = true; disabled.value = true;
@ -85,6 +90,7 @@ const onLogin = async (formEl: FormInstance | undefined) => {
}) })
.finally(() => (loading.value = false)); .finally(() => (loading.value = false));
} else { } else {
loading.value = false;
return fields; return fields;
} }
}); });
@ -101,9 +107,6 @@ useEventListener(document, "keypress", ({ code }) => {
immediateDebounce(ruleFormRef.value); immediateDebounce(ruleFormRef.value);
}); });
watch(imgCode, value => {
useUserStoreHook().SET_VERIFYCODE(value);
});
watch(checked, bool => { watch(checked, bool => {
useUserStoreHook().SET_ISREMEMBERED(bool); useUserStoreHook().SET_ISREMEMBERED(bool);
}); });

View File

@ -2,14 +2,13 @@ import { reactive } from "vue";
import { isPhone } from "@pureadmin/utils"; import { isPhone } from "@pureadmin/utils";
import type { FormRules } from "element-plus"; import type { FormRules } from "element-plus";
import { $t, transformI18n } from "@/plugins/i18n"; import { $t, transformI18n } from "@/plugins/i18n";
import { useUserStoreHook } from "@/store/modules/user";
/** 6位数字验证码正则 */ /** 6位数字验证码正则 */
export const REGEXP_SIX = /^\d{6}$/; export const REGEXP_SIX = /^\d{6}$/;
/** 密码正则密码格式应为8-18位数字、字母、符号的任意两种组合 */ /** 密码正则密码格式应为8-18位数字、字母、符号的任意两种组合 */
export const REGEXP_PWD = export const REGEXP_PWD =
/^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[()])+$)(?!^.*[\u4E00-\u9FA5].*$)([^(0-9a-zA-Z)]|[()]|[a-z]|[A-Z]|[0-9]){8,18}$/; /^[0-9a-zA-Z!@#$%^&*()_+\-=\[\]{};':"\\|,.<>/?]{5,12}$/;
/** 登录校验 */ /** 登录校验 */
const loginRules = reactive<FormRules>({ const loginRules = reactive<FormRules>({
@ -32,8 +31,6 @@ const loginRules = reactive<FormRules>({
validator: (rule, value, callback) => { validator: (rule, value, callback) => {
if (value === "") { if (value === "") {
callback(new Error(transformI18n($t("login.verifyCodeReg")))); callback(new Error(transformI18n($t("login.verifyCodeReg"))));
} else if (useUserStoreHook().verifyCode !== value) {
callback(new Error(transformI18n($t("login.verifyCodeCorrectReg"))));
} else { } else {
callback(); callback();
} }
@ -64,8 +61,6 @@ const phoneRules = reactive<FormRules>({
validator: (rule, value, callback) => { validator: (rule, value, callback) => {
if (value === "") { if (value === "") {
callback(new Error(transformI18n($t("login.verifyCodeReg")))); callback(new Error(transformI18n($t("login.verifyCodeReg"))));
} else if (!REGEXP_SIX.test(value)) {
callback(new Error(transformI18n($t("login.verifyCodeSixReg"))));
} else { } else {
callback(); callback();
} }