You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1207 lines
28 KiB

<template>
<div
class="lockscreen"
:class="lockClassName"
>
<el-card
class="login-box"
shadow="never"
>
<div class="avatar-container">
<el-avatar size="large">
<img
src="/logo.png"
alt="Logo"
/>
</el-avatar>
</div>
<el-form
v-if="store.ThirdPartyLoginMethod === 'login'"
label-position="left"
label-width="0px"
>
<el-form-item>
<el-input
v-model="userName"
placeholder="请输入用户名"
autofocus
prefix-icon="UserFilled"
></el-input>
</el-form-item>
<el-form-item v-if="!sys._options.noPassword">
<el-input
v-model="userPassword"
type="password"
placeholder="请输入登录密码"
show-password
prefix-icon="Key"
@keyup.enter="onLogin()"
></el-input>
</el-form-item>
<el-button
type="primary"
@click="onLogin()"
>登录</el-button
>
<div class="divider">
<span>第三方登录</span>
</div>
<div class="third-party-login">
<el-tooltip
v-for="platform in availablePlatforms"
:key="platform.name"
:content="platform.content"
placement="top"
>
<img
:src="platform.icon"
:alt="platform.name"
style="
width: 25px;
height: 25px;
cursor: pointer;
color: #409eff;
"
@click="onThirdPartyLogin(platform.name)"
/>
</el-tooltip>
</div>
<div class="actions">
<a
href="#"
@click.prevent="
store.ThirdPartyLoginMethod = 'register'
"
>注册新用户</a
>
<a
href="#"
@click.prevent="toggleUserSwitch"
>切换角色</a
>
</div>
</el-form>
<el-form
v-else-if="store.ThirdPartyLoginMethod === 'register'"
label-position="left"
label-width="0px"
:model="store.registerInfo"
ref="regFormRef"
:rules="rules"
>
<el-form-item prop="username">
<el-input
v-model="store.registerInfo.username"
placeholder="请输入用户名"
prefix-icon="UserFilled"
></el-input>
</el-form-item>
<el-form-item prop="nickname">
<el-input
v-model="store.registerInfo.nickname"
placeholder="请输入真实姓名"
prefix-icon="Avatar"
></el-input>
</el-form-item>
<el-form-item prop="email">
<el-input
v-model="store.registerInfo.email"
placeholder="请输入邮箱"
prefix-icon="Message"
></el-input>
</el-form-item>
<el-form-item prop="phone">
<el-input
v-model="store.registerInfo.phone"
placeholder="请输入手机号"
prefix-icon="Iphone"
></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="store.registerInfo.password"
type="password"
placeholder="请输入密码"
show-password
prefix-icon="Key"
></el-input>
</el-form-item>
<el-form-item prop="confirmPassword">
<el-input
v-model="store.registerInfo.confirmPassword"
type="password"
placeholder="请再次输入密码"
show-password
prefix-icon="Lock"
></el-input>
</el-form-item>
<el-form-item>
<el-button
type="primary"
@click="onRegister"
>注册</el-button
>
</el-form-item>
<el-form-item>
<el-button
style="background-color: gray; color: white"
@click="backToLogin"
>返回</el-button
>
</el-form-item>
</el-form>
<div v-else-if="store.ThirdPartyLoginMethod === 'dingding'">
<el-row>
<el-col :span="24">
<div class="qr-code">
<div id="dd-qr-code"></div>
</div>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-button @click="backToLogin">返回</el-button>
</el-col>
</el-row>
</div>
<!-- 企业微信 -->
<div v-else-if="store.ThirdPartyLoginMethod === 'qyweixin'">
<el-row>
<el-col :span="24">
<div class="qywechat">
<div id="qywechat-qr-code"></div>
</div>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-button @click="backToLogin">返回</el-button>
</el-col>
</el-row>
</div>
<!-- 手机登录 -->
<div v-else-if="store.ThirdPartyLoginMethod === 'phone'">
<el-row>
<el-col :span="24">
<el-form
:model="phoneForm"
:rules="phoneLoginRules"
ref="phoneLoginFormRef"
>
<el-form-item prop="phone">
<el-input
v-model="phoneForm.phone"
placeholder="请输入手机号"
prefix-icon="Iphone"
/>
</el-form-item>
<!-- 验证码 -->
<el-row :gutter="24">
<el-col :span="17">
<el-form-item prop="code">
<el-input
v-model="phoneForm.code"
placeholder="请输入验证码"
prefix-icon="Key"
/>
</el-form-item>
</el-col>
<el-col :span="7">
<el-form-item>
<button
class="send-code-btn"
@click.prevent="startCountdown"
:disabled="isSendCodeButtonDisabled"
>
{{ sendCodeButtonText }}
</button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-button
style="background-color: gray; color: white"
@click="backToLogin"
>返回</el-button
>
</el-col>
<el-col :span="12">
<el-button @click="validateAndLoginByPhone"
>登录</el-button
>
</el-col>
</el-row>
</div>
<div v-else-if="store.ThirdPartyLoginMethod === 'email'">
<el-row>
<el-col :span="24">
<el-form>
<el-form-item>
<el-input
v-model="emailForm.email"
placeholder="请输入邮箱"
prefix-icon="Message"
/>
</el-form-item>
<el-row :gutter="24">
<el-col :span="17">
<el-form-item>
<el-input
v-model="emailForm.code"
placeholder="请输入验证码"
prefix-icon="Key"
/>
</el-form-item>
</el-col>
<el-col :span="7">
<el-form-item>
<button
class="send-code-btn"
@click.prevent="onSendCode()"
:disabled="
isSendEmailCodeButtonDisabled
"
>
{{ sendEmailCodeButtonText }}
</button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-button
style="background-color: gray; color: white"
@click="backToLogin"
>返回</el-button
>
</el-col>
<el-col :span="12">
<el-button @click="loginByEmail">登录</el-button>
</el-col>
</el-row>
</div>
<div v-else-if="store.ThirdPartyLoginMethod === 'thirdparty'">
<el-row :gutter="24">
<el-col :span="24">
<el-form
label-position="right"
label-width="100px"
>
<el-form-item label="用户特征码">
<el-input
v-model="thirdpartyCode"
placeholder="请输入用户特征码"
/>
</el-form-item>
</el-form>
</el-col>
</el-row>
<el-row :gutter="24">
<el-col :span="12">
<el-button
@click="backToLogin"
style="background-color: gray; color: white"
>返回</el-button
>
</el-col>
<el-col :span="12">
<el-button @click="validateAndLoginByThirdparty"
>登录</el-button
>
</el-col>
</el-row>
</div>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { useLoginStore } from "@/stores/login";
import { useSystem } from "@/system";
import { getSystemConfig, setSystemConfig } from "@/system/config";
import router from "@/system/router";
import { RestartApp } from "@/util/goutil";
import { notifyError } from "@/util/msg";
import { computed, onMounted, ref, watchEffect } from "vue";
import { useRoute } from "vue-router";
const route = useRoute();
const store = useLoginStore();
const sys = useSystem();
const loginCallback = sys._options.loginCallback;
const config = getSystemConfig();
const lockClassName = ref("screen-show");
const backToLogin = () => {
store.ThirdPartyLoginMethod = "login";
localStorage.removeItem("ThirdPartyPlatform");
};
const phoneForm = ref({
phone: "",
code: "",
});
const emailForm = ref({
email: "",
code: "",
});
const thirdpartyCode = ref("");
const validateAndLoginByThirdparty = () => {
if (thirdpartyCode.value === "") {
notifyError("请输入第三方登录码");
return;
}
onLogin(thirdpartyCode.value);
};
// 声明 DTFrameLogin 和 WwLogin 方法
declare global {
interface Window {
DTFrameLogin: (
frameParams: IDTLoginFrameParams,
loginParams: IDTLoginLoginParams,
successCbk: (result: IDTLoginSuccess) => void,
errorCbk?: (error: any) => void
) => void;
WwLogin: (options: {
id: string; // 需要显示二维码的元素ID
appid: string; // 微信企业号的应用ID
agentid: string; // 微信企业号的代理ID
redirect_uri: string; // 登录成功后的重定向地址
state: string; // 用于防止CSRF攻击的状态参数
}) => void;
}
}
// 包裹容器的尺寸与样式需要接入方自己使用css设置
interface IDTLoginFrameParams {
id: string; // 必传,包裹容器元素ID,不带'#'
width?: number; // 选传,二维码iframe元素宽度,最小280,默认300
height?: number; // 选传,二维码iframe元素高度,最小280,默认300
}
// 增加了isPre参数来设定运行环境
interface IDTLoginLoginParams {
redirect_uri: string; // 必传,注意url需要encode
response_type: string; // 必传,值固定为code
client_id: string; // 必传
scope: string; // 必传,如果值为openid+corpid,则下面的org_type和corpId参数必传,否则无法成功登录
prompt: string; // 必传,值为consent。
state?: string; // 选传
org_type?: string; // 选传,当scope值为openid+corpid时必传
corpId?: string; // 选传,当scope值为openid+corpid时必传
exclusiveLogin?: string; // 选传,如需生成专属组织专用二维码时,可指定为true,可以限制非组织帐号的扫码
exclusiveCorpId?: string; // 选传,当exclusiveLogin为true时必传,指定专属组织的corpId
}
interface IDTLoginSuccess {
redirectUrl: string; // 登录成功后的重定向地址,接入方可以直接使用该地址进行重定向
authCode: string; // 登录成功后获取到的authCode,接入方可直接进行认证,无需跳转页面
state?: string; // 登录成功后获取到的state
}
onMounted(async () => {
await getThirdpartyList();
});
const getThirdpartyList = async () => {
const result = await fetch(
config.userInfo.url + "/user/thirdparty/list"
);
if (result.ok) {
const data = await result.json();
store.thirdpartyList = data.data.list;
console.log(data);
}
};
const isPlatformAvailable = (platform: string) => {
for (let i = 0; i < store.thirdpartyList.length; i++) {
if (store.thirdpartyList[i] === platform) {
return true;
}
}
return false;
};
const onDingDingScan = () => {
store.ThirdPartyLoginMethod = "dingding";
initDingDingScan();
};
const initDingDingScan = async () => {
try {
// 加载钉钉登录脚本
await loadScript(
"https://g.alicdn.com/dingding/h5-dingtalk-login/0.21.0/ddlogin.js"
);
const res = await fetch("http://192.168.1.10:8816/user/ding/conf");
const data = await res.json();
// 在这里可以调用DTFrameLogin或其他依赖于该脚本的方法
window.DTFrameLogin(
{
id: "dd-qr-code",
width: 200,
height: 300,
},
{
redirect_uri: encodeURIComponent(data.data.host),
client_id: data.data.client_id,
scope: "openid",
response_type: "code",
state: "xxxxxxxxx",
prompt: "consent",
},
(loginResult: any) => {
const { authCode } = loginResult;
onLogin(authCode);
},
(errorMsg: any) => {
console.log("二维码获取错误", errorMsg);
}
);
} catch (error) {
console.error(error);
}
};
// 函数用于动态加载外部JS文件
function loadScript(url: string): Promise<void> {
return new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = url;
script.onload = () => resolve();
script.onerror = () =>
reject(new Error(`Failed to load script ${url}`));
document.head.appendChild(script);
});
}
const sendCodeButtonText = ref("发送验证码");
const isSendCodeButtonDisabled = ref(false);
const onSendCode = async () => {
try {
const response = await fetch(
config.userInfo.url + "/user/emailcode",
{
method: "POST",
body: JSON.stringify({ email: emailForm.value.email }),
}
);
console.log(response);
if (!response.ok) {
notifyError("发送验证码失败,请重试");
return;
}
const result = await response.json();
if (result.success) {
console.log("验证码发送成功");
startEmailCountdown();
} else {
notifyError(result.message || "发送验证码失败,请重试");
}
} catch (error) {
console.error("发送验证码时发生错误", error);
notifyError("发送验证码时发生错误,请重试");
}
};
const startCountdown = () => {
let countdown = 2;
isSendCodeButtonDisabled.value = true;
sendCodeButtonText.value = `${countdown}秒后重试`;
const interval = setInterval(() => {
countdown--;
if (countdown > 0) {
sendCodeButtonText.value = `${countdown}秒后重试`;
} else {
clearInterval(interval);
sendCodeButtonText.value = "发送验证码";
isSendCodeButtonDisabled.value = false;
}
}, 1000);
};
const sendEmailCodeButtonText = ref("发送验证码");
const isSendEmailCodeButtonDisabled = ref(false);
const startEmailCountdown = () => {
let countdown = 2;
isSendEmailCodeButtonDisabled.value = true;
sendEmailCodeButtonText.value = `${countdown}秒后重试`;
const interval = setInterval(() => {
countdown--;
if (countdown > 0) {
sendEmailCodeButtonText.value = `${countdown}秒后重试`;
} else {
clearInterval(interval);
sendEmailCodeButtonText.value = "发送验证码";
isSendEmailCodeButtonDisabled.value = false;
}
}, 1000);
};
const thirdPartyPlatforms = [
{
name: "qyweixin",
icon: new URL("@/assets/login/qywechat.png", import.meta.url).href,
content: "企业微信",
},
{
name: "dingding",
icon: new URL("@/assets/login/dingding.png", import.meta.url).href,
content: "钉钉",
},
{
name: "phone",
icon: new URL("@/assets/login/phone.png", import.meta.url).href,
content: "手机号",
},
{
name: "github",
icon: new URL("@/assets/login/github.png", import.meta.url).href,
content: "Github",
},
{
name: "gitee",
icon: new URL("@/assets/login/gitee.png", import.meta.url).href,
content: "Gitee",
},
{
name: "email",
icon: new URL("@/assets/login/email.png", import.meta.url).href,
content: "邮箱",
},
{
name: "thirdparty",
icon: new URL("@/assets/login/login.png", import.meta.url).href,
content: "第三方服务登录",
},
];
const availablePlatforms = computed(() => {
return thirdPartyPlatforms.filter((platform) =>
isPlatformAvailable(platform.name)
);
});
function loginSuccess() {
lockClassName.value = "screen-hidean";
setTimeout(() => {
lockClassName.value = "screen-hide";
}, 500);
}
const userName = ref("");
const userPassword = ref("");
onMounted(() => {
if (config.userType === "person") {
userName.value = sys._options.login?.username || "admin";
userPassword.value = sys._options.login?.password || "";
} else {
userName.value = config.userInfo.username;
// userPassword.value = config.userInfo.password;
}
});
// 使用 watchEffect 监听 code 参数
watchEffect(() => {
const code = route.query.code;
if (code) {
console.log("code:", code);
onLogin();
}
});
async function onLogin(authCode?: string) {
if (loginCallback) {
const platform = localStorage.getItem("ThirdPartyPlatform");
const code =
authCode || (router.currentRoute.value.query.code as string);
// 使用映射对象,将平台名称映射到相应的参数名称
const platformCodeMap: Record<string, string> = {
github: "github",
gitee: "gitee",
qyweixin: "qyweixin_scan",
qq: "qq",
dingding: "dingtalk_scan",
phone: "sms_code",
email: "email",
thirdparty: "third_api",
};
// 获取对应的参数名称
const codeParam = platform ? platformCodeMap[platform] : null;
// 根据登录类型传递参数
let param;
if (codeParam) {
if (platform === "phone") {
param = {
phone: phoneForm.value.phone,
sms_code: phoneForm.value.code,
};
} else if (platform === "email") {
param = {
email: emailForm.value.email,
code: emailForm.value.code,
};
} else if (platform === "thirdparty") {
param = {
unionid: thirdpartyCode.value,
};
} else {
param = { code: code };
}
} else {
param = {
username: userName.value,
password: userPassword.value,
};
}
const login_type = codeParam ? codeParam : "password";
const res = await loginCallback(login_type, param);
if (res) {
localStorage.removeItem("ThirdPartyPlatform");
loginSuccess();
} else {
console.log("登录失败");
}
}
}
const toggleUserSwitch = () => {
config.userType = "person";
setSystemConfig(config);
RestartApp();
};
const regFormRef: any = ref(null);
const rules = {
username: [
{ required: true, message: "用户名不能为空", trigger: "blur" },
{
min: 3,
max: 20,
message: "用户名长度应在3到20个字符之间",
trigger: "blur",
},
{
pattern: /^[a-zA-Z0-9]+$/,
message: "用户名只能包含英文和数字",
trigger: "blur",
},
],
password: [
{ required: true, message: "密码不能为空", trigger: "blur" },
{ min: 6, message: "密码长度不能小于6位", trigger: "blur" },
],
confirmPassword: [
{ required: true, message: "请再次输入密码", trigger: "blur" },
{
validator: (rule: any, value: any, callback: any) => {
console.log(rule);
if (value === "") {
callback(new Error("请再次输入密码"));
} else if (value !== store.registerInfo.password) {
callback(new Error("两次输入的密码不一致"));
} else {
callback();
}
},
trigger: "blur",
},
],
email: [
{ required: true, message: "邮箱不能为空", trigger: "blur" },
{
type: "email",
message: "请输入有效的邮箱地址",
trigger: ["blur", "change"],
},
],
phone: [
{ required: true, message: "手机号不能为空", trigger: "blur" },
{
pattern: /^1[3-9]\d{9}$/,
message: "请输入有效的手机号",
trigger: "blur",
},
],
nickname: [
{ required: true, message: "昵称不能为空", trigger: "blur" },
{
min: 2,
max: 20,
message: "昵称长度应在2到20个字符之间",
trigger: "blur",
},
],
};
const onRegister = async () => {
try {
await regFormRef.value.validate();
const userInfo = config.userInfo;
const comp = await fetch(userInfo.url + "/user/register", {
method: "POST",
body: JSON.stringify(store.registerInfo),
});
if (!comp.ok) {
notifyError("网络错误,注册失败");
return;
}
const res = await comp.json();
if (res.success) {
notifyError("注册成功");
loginSuccess();
window.location.href = "/";
// 设置用户名和密码
config.userInfo.username = store.registerInfo.username;
config.userInfo.password = store.registerInfo.password;
setSystemConfig(config);
} else {
notifyError(res.message);
return;
}
} catch (error) {
console.error(error);
}
// console.log("123");
// const res = await fetch(config.userInfo.url + "/user/editdata", {
// method: "POST",
// body: JSON.stringify({
// phone: userName.value,
// }),
// headers: {
// ClientID: store.tempClientId,
// Authorization: store.tempToken,
// },
// });
// if (res.ok) {
// notifySuccess("手机号设置成功");
// } else {
// notifyError("手机号设置失败");
// }
};
const onThirdPartyLogin = async (platform: string) => {
localStorage.setItem("ThirdPartyPlatform", platform);
let loginFunction: (() => Promise<boolean>) | undefined;
switch (platform) {
case "github":
loginFunction = async function () {
return await authWithGithub();
};
break;
case "gitee":
loginFunction = async function () {
return await authWithGitee();
};
break;
case "dingding":
loginFunction = async function () {
return await authWithDingDing();
};
break;
case "qyweixin":
loginFunction = async function () {
return await authWithWechat();
};
break;
case "phone":
loginFunction = async function () {
return await authWithPhone();
};
break;
case "email":
loginFunction = async function () {
return await authWithEmail();
};
break;
case "thirdparty":
loginFunction = async function () {
return await authWithThirdParty();
};
break;
default:
notifyError("不支持的第三方登录平台");
return;
}
if (loginFunction) {
const success = await loginFunction();
if (success) {
loginSuccess();
}
}
};
const authWithThirdParty = async (): Promise<boolean> => {
store.ThirdPartyLoginMethod = "thirdparty";
return true;
};
const authWithPhone = async (): Promise<boolean> => {
store.ThirdPartyLoginMethod = "phone";
return true;
};
const authWithDingDing = async (): Promise<boolean> => {
onDingDingScan();
return true;
};
const authWithGithub = async (): Promise<boolean> => {
// 传递state用于防止CSRF攻击,使用时间戳加随机字符串
const state = Date.now() + Math.random().toString(36).substring(2, 15);
store.State = state;
// 获取当前页面url当做回调参数
const currentUrl = window.location.href;
const url =
config.userInfo.url + "/user/github/authorize?state=" + state;
const res: any = await fetch(url, {
method: "POST",
body: JSON.stringify({
state: state,
redirect_url: currentUrl,
}),
});
if (!res.ok) {
return false;
}
const data = await res.json();
if (data && data.data && data.data.url) {
// 使用正则表达式检查URL格式
const urlPattern = /client_id=[^&]+/;
if (urlPattern.test(data.data.url)) {
window.location.href = data.data.url;
return true;
} else {
notifyError("请先在系统配置中设置github登陆配置");
return false;
}
} else {
notifyError("获取授权URL失败");
return false;
}
};
const authWithWechat = async (): Promise<boolean> => {
await loadScript(
"http://rescdn.qqmail.com/node/ww/wwopenmng/js/sso/wwLogin-1.0.0.js"
);
store.ThirdPartyLoginMethod = "qyweixin";
const res = await fetch(
"http://server001.godoos.com/user/qyweixin/conf"
);
if (res.ok) {
const data = await res.json();
if (data.success) {
console.log(data.data);
window.WwLogin({
id: "qywechat-qr-code",
appid: data.data.corp_id,
agentid: data.data.agent_id,
redirect_uri: data.data.redirect,
state: "WWLogin",
});
return true;
}
return false;
} else {
notifyError("网络错误,无法获取二维码");
return false;
}
};
const authWithGitee = async (): Promise<boolean> => {
// 传递state用于防止CSRF攻击,使用时间戳加随机字符串
const state = Date.now() + Math.random().toString(36).substring(2, 15);
store.State = state;
// 获取当前页面url当做回调参数
const currentUrl = window.location.href;
const url =
config.userInfo.url + "/user/gitee/authorize?state=" + state;
const res: any = await fetch(url, {
method: "POST",
body: JSON.stringify({
state: state,
redirect_url: currentUrl,
}),
});
if (!res.ok) {
return false;
}
const data = await res.json();
if (data && data.data && data.data.url) {
// 使用正则表达式检查URL格式
const urlPattern = /client_id=[^&]+/;
if (urlPattern.test(data.data.url)) {
window.location.href = data.data.url;
return true;
} else {
notifyError("请先在系统配置中设置gitee登陆配置");
return false;
}
} else {
notifyError("获取授权URL失败");
return false;
}
// store.page = "phone";
// return true;
};
const authWithEmail = async (): Promise<boolean> => {
store.ThirdPartyLoginMethod = "email";
return true;
};
const loginByEmail = async () => {
await onLogin();
};
const phoneLoginFormRef: any = ref(null);
const phoneLoginRules = {
phone: [
{ required: true, message: "手机号不能为空", trigger: "blur" },
{
pattern: /^\d{11}$/,
message: "手机号格式不正确,必须为11位有效数字",
trigger: "blur",
},
],
code: [
{ required: true, message: "验证码不能为空", trigger: "blur" },
{
pattern: /^\d{4,6}$/,
message: "验证码必须为4到6位数字",
trigger: "blur",
},
],
};
const validateAndLoginByPhone = async () => {
await phoneLoginFormRef.value.validate();
onLogin();
};
</script>
<style scoped lang="scss">
.lockscreen {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 201;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
color: #fff;
background-color: rgba(25, 28, 34, 0.78);
backdrop-filter: blur(7px);
.login-box {
width: 400px;
padding: 20px;
text-align: center;
background: #ffffff;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border: 1px solid #e0e0e0;
.avatar-container {
margin-bottom: 20px;
}
.el-input {
width: 100%;
margin-bottom: 10px;
background: #f9f9f9;
border-radius: 4px;
}
.el-button {
width: 100%;
margin-top: 10px;
background: #409eff;
color: #ffffff;
border: none;
border-radius: 4px;
transition: background 0.3s ease;
}
.el-button:hover {
background: #66b1ff;
}
.tip {
padding: 4px 0;
font-size: 12px;
color: red;
height: 30px;
}
.actions {
margin-top: 10px;
display: flex;
justify-content: space-between;
a {
color: #409eff;
text-decoration: none;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
}
}
.screen-hidean {
animation: outan 0.5s forwards;
}
.screen-hide {
display: none;
}
.third-party-login {
margin-top: 10px;
display: flex;
justify-content: center;
gap: 15px;
el-icon {
font-size: 24px;
cursor: pointer;
transition: color 0.3s ease;
&:hover {
color: #409eff;
}
}
}
.divider {
display: flex;
align-items: center;
text-align: center;
margin: 20px 0;
color: #999;
font-size: 14px;
&::before,
&::after {
content: "";
flex: 1;
border-bottom: 1px solid #ddd;
}
&::before {
margin-right: 0.25em;
}
&::after {
margin-left: 0.25em;
}
}
}
.qr-code {
width: 200px;
height: 200px;
padding: 10px;
margin: 0 auto;
overflow: hidden;
border: 1px solid rgb(203, 203, 203);
border-radius: 10px;
:deep(#dd-qr-code) {
width: 200px;
height: 200px;
iframe {
margin-top: -50px;
}
}
}
@keyframes outan {
0% {
opacity: 1;
}
30% {
opacity: 0;
}
100% {
transform: translateY(-100%);
opacity: 0;
}
}
.send-code-btn {
border: none;
height: 40px;
background: #409eff;
color: #fff;
border-radius: 4px;
line-height: 40px;
cursor: pointer;
transition: background 0.3s ease;
&:disabled {
background: #c0c4cc; // 灰色背景
cursor: not-allowed; // 禁用时的鼠标样式
}
}
.qywechat {
width: 300px;
height: 400px;
padding: 10px;
margin: 0 auto;
overflow: hidden;
border: 1px solid rgb(203, 203, 203);
border-radius: 10px;
// 使用 :deep 选择器来确保样式应用到子元素
:deep(#qywechat-qr-code) {
width: 100%;
height: 100%;
iframe {
width: 100%;
height: 100%;
border: none; // 确保没有额外的边框
}
}
}
</style>