|
@@ -0,0 +1,344 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <!-- 登录页面 -->
|
|
|
|
|
+ <div class="login-content">
|
|
|
|
|
+ <!-- 背景图容器 -->
|
|
|
|
|
+ <div
|
|
|
|
|
+ class="bg-image-container"
|
|
|
|
|
+ :style="{ backgroundImage: `url(${BGImages})`, backgroundSize: 'cover' }"
|
|
|
|
|
+ ></div>
|
|
|
|
|
+ <!-- 登录输入框 -->
|
|
|
|
|
+ <div class="login-wrapper">
|
|
|
|
|
+ <div class="login-input">
|
|
|
|
|
+ <span>{{ appTitle }}</span>
|
|
|
|
|
+ <el-form
|
|
|
|
|
+ ref="loginFormRef"
|
|
|
|
|
+ :model="loginData.loginForm"
|
|
|
|
|
+ :rules="rules"
|
|
|
|
|
+ label-width="0px"
|
|
|
|
|
+ class="input-item"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-form-item prop="tenantName">
|
|
|
|
|
+ <el-input v-model="loginData.loginForm.tenantName"
|
|
|
|
|
+ :prefix-icon="HomeFilled"
|
|
|
|
|
+ placeholder="学校"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 条件显示手机号和短信验证码或账号和密码 -->
|
|
|
|
|
+ <template v-if="isAuthorized">
|
|
|
|
|
+ <el-form-item prop="username">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="loginData.loginForm.username"
|
|
|
|
|
+ :prefix-icon="Avatar"
|
|
|
|
|
+ placeholder="账号"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item prop="password">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="loginData.loginForm.password"
|
|
|
|
|
+ class="password-input"
|
|
|
|
|
+ type="password"
|
|
|
|
|
+ :prefix-icon="Lock"
|
|
|
|
|
+ placeholder="密码"
|
|
|
|
|
+ show-password
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <template v-else>
|
|
|
|
|
+ <el-form-item prop="phoneNumber">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="loginData.loginForm.phoneNumber"
|
|
|
|
|
+ :prefix-icon="Iphone"
|
|
|
|
|
+ placeholder="手机号"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <!-- 短信验证码输入框和获取验证码按钮 -->
|
|
|
|
|
+ <el-form-item prop="smsCode">
|
|
|
|
|
+ <div class="sms-code-container">
|
|
|
|
|
+ <el-input
|
|
|
|
|
+ v-model="loginData.loginForm.smsCode"
|
|
|
|
|
+ placeholder="短信验证码"
|
|
|
|
|
+ class="sms-input"
|
|
|
|
|
+ />
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ @click="handleGetSmsCode"
|
|
|
|
|
+ :disabled="countingDown"
|
|
|
|
|
+ class="get-code-btn"
|
|
|
|
|
+ :loading="sendingCode"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ countingDown ? `${countDown}秒后重新获取` : '获取验证码' }}
|
|
|
|
|
+ </el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 登录按钮 -->
|
|
|
|
|
+ <el-form-item>
|
|
|
|
|
+ <el-button class="login-btn" type="primary" @click="handleLogin">登录</el-button>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ <!-- 多选框 -->
|
|
|
|
|
+ <div class="check-box">
|
|
|
|
|
+ <el-checkbox
|
|
|
|
|
+ v-model="loginData.loginForm.rememberMe"
|
|
|
|
|
+ label="记住我"
|
|
|
|
|
+ size="large"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup>
|
|
|
|
|
+import { ref, onMounted, onUnmounted } from 'vue'
|
|
|
|
|
+import { useRouter } from 'vue-router'
|
|
|
|
|
+import { HomeFilled, Avatar, Lock, Iphone } from '@element-plus/icons-vue'
|
|
|
|
|
+import { ElMessage } from 'element-plus'
|
|
|
|
|
+
|
|
|
|
|
+import BGImages from '@/assets/images/homeBG.png'
|
|
|
|
|
+import {
|
|
|
|
|
+ createLoginData,
|
|
|
|
|
+ createVerificationCodeLogic,
|
|
|
|
|
+ getTenantId,
|
|
|
|
|
+ loginLogic,
|
|
|
|
|
+ loadLoginData,
|
|
|
|
|
+ checkLoginStatus,
|
|
|
|
|
+ generateRules
|
|
|
|
|
+} from '@/utils/loginUtils.js'
|
|
|
|
|
+
|
|
|
|
|
+const router = useRouter()
|
|
|
|
|
+
|
|
|
|
|
+// 获取环境变量
|
|
|
|
|
+const appTitle = import.meta.env.VITE_APP_TITLE
|
|
|
|
|
+
|
|
|
|
|
+const loginFormRef = ref(null)
|
|
|
|
|
+
|
|
|
|
|
+// 初始化登录数据
|
|
|
|
|
+const loginData = createLoginData()
|
|
|
|
|
+
|
|
|
|
|
+// 初始化验证码逻辑
|
|
|
|
|
+const { countingDown, countDown, sendingCode, getSmsCode, clearCountDownTimer } = createVerificationCodeLogic()
|
|
|
|
|
+
|
|
|
|
|
+// 登录状态标识
|
|
|
|
|
+const isLoggedIn = ref(false)
|
|
|
|
|
+
|
|
|
|
|
+// 授权状态 默认授权
|
|
|
|
|
+const isAuthorized = ref(true)
|
|
|
|
|
+
|
|
|
|
|
+// 生成表单验证规则
|
|
|
|
|
+const rules = generateRules(isAuthorized)
|
|
|
|
|
+
|
|
|
|
|
+// 获取短信验证码
|
|
|
|
|
+const handleGetSmsCode = async () => {
|
|
|
|
|
+ // 先验证租户和手机号是否填写
|
|
|
|
|
+ if (!loginData.value.loginForm.tenantName) {
|
|
|
|
|
+ ElMessage.warning('请先输入学校名称')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!loginData.value.loginForm.phoneNumber) {
|
|
|
|
|
+ ElMessage.warning('请先输入手机号')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ // 验证租户是否存在
|
|
|
|
|
+ const tenantId = await getTenantId(loginData.value.loginForm.tenantName)
|
|
|
|
|
+ if (!tenantId) {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 调用验证码逻辑
|
|
|
|
|
+ getSmsCode(tenantId, loginData.value.loginForm.tenantName, loginData.value.loginForm.phoneNumber)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 登录
|
|
|
|
|
+const handleLogin = async () => {
|
|
|
|
|
+ if (!loginFormRef.value) return
|
|
|
|
|
+ await loginFormRef.value.validate(async valid => {
|
|
|
|
|
+ if (valid) {
|
|
|
|
|
+ // 先验证租户是否存在
|
|
|
|
|
+ const tenantId = await getTenantId(loginData.value.loginForm.tenantName)
|
|
|
|
|
+ if (!tenantId) {
|
|
|
|
|
+ // 租户验证失败,不执行登录
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 调用登录逻辑
|
|
|
|
|
+ await loginLogic(loginData.value.loginForm, tenantId, isAuthorized, router, '/aiCourseHome')
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 在组件挂载时检查登录状态和恢复登录信息
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ // 加载本地存储的登录数据
|
|
|
|
|
+ loadLoginData(loginData)
|
|
|
|
|
+
|
|
|
|
|
+ // 检查地址栏是否有tenantName参数
|
|
|
|
|
+ if (Object.keys(router.currentRoute.value.query).length > 0) {
|
|
|
|
|
+ // 其他参数,重定向到登录页
|
|
|
|
|
+ router.replace('/ai-login')
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 检查登录状态,如果已登录则直接跳转到首页
|
|
|
|
|
+ checkLoginStatus(router, '/aiCourseHome')
|
|
|
|
|
+
|
|
|
|
|
+ const handleKeyPress = (event) => {
|
|
|
|
|
+ // 检查是否按下回车键(keyCode 13)
|
|
|
|
|
+ if (event.key === 'Enter' || event.keyCode === 13) {
|
|
|
|
|
+ handleLogin()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ document.addEventListener('keydown', handleKeyPress)
|
|
|
|
|
+
|
|
|
|
|
+ // 在组件卸载时移除事件监听
|
|
|
|
|
+ onUnmounted(() => {
|
|
|
|
|
+ document.removeEventListener('keydown', handleKeyPress)
|
|
|
|
|
+ clearCountDownTimer()
|
|
|
|
|
+ })
|
|
|
|
|
+})
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped lang="scss">
|
|
|
|
|
+@use 'sass:math';
|
|
|
|
|
+// 定义rpx转换函数
|
|
|
|
|
+@function rpx($px) {
|
|
|
|
|
+ @return math.div($px, 750) * 100vw;
|
|
|
|
|
+}
|
|
|
|
|
+.login-content {
|
|
|
|
|
+ position: fixed;
|
|
|
|
|
+ top: 0;
|
|
|
|
|
+ left: 0;
|
|
|
|
|
+ right: 0;
|
|
|
|
|
+ bottom: 0;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: row; // 修改为水平布局
|
|
|
|
|
+}
|
|
|
|
|
+.bg-image-container {
|
|
|
|
|
+ flex: 3; // 背景图占比为 3
|
|
|
|
|
+ background-size: cover;
|
|
|
|
|
+ background-position: center;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.login-wrapper {
|
|
|
|
|
+ flex: 1; // 登录框占比为 1
|
|
|
|
|
+ background: linear-gradient(to bottom, #001169, #8a78d0);
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+ position: static;
|
|
|
|
|
+ transform: none;
|
|
|
|
|
+ display: flex; // 添加 Flexbox 布局
|
|
|
|
|
+ justify-content: center; // 水平居中
|
|
|
|
|
+ align-items: center; // 垂直居中
|
|
|
|
|
+}
|
|
|
|
|
+.login-input {
|
|
|
|
|
+ width: rpx(190);
|
|
|
|
|
+ height: rpx(240);
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center; // 水平居中
|
|
|
|
|
+ align-items: center; // 垂直居中
|
|
|
|
|
+ flex-direction: column; // 子元素垂直排列
|
|
|
|
|
+ text-align: center; // 文本居中
|
|
|
|
|
+}
|
|
|
|
|
+.login-input span{
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ font-size: rpx(11);
|
|
|
|
|
+ padding-bottom: rpx(5);
|
|
|
|
|
+ letter-spacing: rpx(1);
|
|
|
|
|
+}
|
|
|
|
|
+.input-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column; // 子元素垂直排列
|
|
|
|
|
+ justify-content: center; // 内容垂直居中
|
|
|
|
|
+ align-items: center; // 内容水平居中
|
|
|
|
|
+}
|
|
|
|
|
+.el-input ::v-deep(.el-input__wrapper){
|
|
|
|
|
+ border-radius: rpx(5);
|
|
|
|
|
+}
|
|
|
|
|
+.input-item .el-form-item {
|
|
|
|
|
+ margin-bottom: 0;
|
|
|
|
|
+}
|
|
|
|
|
+.input-item .el-input {
|
|
|
|
|
+ width: rpx(150);
|
|
|
|
|
+ height: rpx(22);
|
|
|
|
|
+ margin-bottom: rpx(15);
|
|
|
|
|
+ font-size: rpx(7);
|
|
|
|
|
+}
|
|
|
|
|
+.el-form-item ::v-deep(.el-form-item__error) {
|
|
|
|
|
+ top: rpx(25);
|
|
|
|
|
+}
|
|
|
|
|
+.login-btn {
|
|
|
|
|
+ width: rpx(150);
|
|
|
|
|
+ height: rpx(22);
|
|
|
|
|
+ color: black;
|
|
|
|
|
+ font-size: rpx(8);
|
|
|
|
|
+ letter-spacing: rpx(10);
|
|
|
|
|
+ border-radius: rpx(5);
|
|
|
|
|
+ margin: rpx(15) 0 auto;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ background: linear-gradient(to bottom, #fee78a, #ffce1b);
|
|
|
|
|
+ box-shadow: 0 8px 8px rgb(0, 0, 0, 0.2);
|
|
|
|
|
+}
|
|
|
|
|
+.password-input {
|
|
|
|
|
+ margin-bottom: rpx(0) !important;
|
|
|
|
|
+}
|
|
|
|
|
+.check-box {
|
|
|
|
|
+ width: rpx(150);
|
|
|
|
|
+ height: rpx(18);
|
|
|
|
|
+ margin: rpx(5) auto;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+}
|
|
|
|
|
+.check-box .el-checkbox {
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ padding-right: rpx(10);
|
|
|
|
|
+ font-size: rpx(6);
|
|
|
|
|
+}
|
|
|
|
|
+.el-checkbox ::v-deep(.el-checkbox__label){
|
|
|
|
|
+ font-size: rpx(6);
|
|
|
|
|
+}
|
|
|
|
|
+.check-box .forgot-password {
|
|
|
|
|
+ color: white;
|
|
|
|
|
+ font-size: rpx(6);
|
|
|
|
|
+ text-decoration: none;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 短信验证码容器样式
|
|
|
|
|
+.sms-code-container {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ width: rpx(150);
|
|
|
|
|
+ height: rpx(22);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.sms-input {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ height: rpx(22);
|
|
|
|
|
+ margin-bottom: 0 !important;
|
|
|
|
|
+}
|
|
|
|
|
+.sms-input ::v-deep(.el-input__wrapper) {
|
|
|
|
|
+ border-top-right-radius: 0;
|
|
|
|
|
+ border-bottom-right-radius: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.get-code-btn {
|
|
|
|
|
+ width: rpx(40);
|
|
|
|
|
+ height: rpx(22);
|
|
|
|
|
+ margin: 0 !important;
|
|
|
|
|
+ padding: 0;
|
|
|
|
|
+ font-size: rpx(5);
|
|
|
|
|
+ border-radius: rpx(5);
|
|
|
|
|
+ border-top-left-radius: 0;
|
|
|
|
|
+ border-bottom-left-radius: 0;
|
|
|
|
|
+ letter-spacing: normal;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ background-color: #fff;
|
|
|
|
|
+ color: #919191;
|
|
|
|
|
+ // background: linear-gradient(to bottom, #78c0ff, #0070f3);
|
|
|
|
|
+ border: none;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|