Просмотр исходного кода

登录页面逻辑整理优化,抽离公共方法

liyanbo 3 месяцев назад
Родитель
Сommit
d634e53ed3
6 измененных файлов с 753 добавлено и 412 удалено
  1. 1 1
      src/router/index.js
  2. 301 0
      src/utils/loginUtils.js
  3. 351 0
      src/views/BlocklyLogin.vue
  4. 80 259
      src/views/Login.vue
  5. 11 77
      src/views/PromotionLogin.vue
  6. 9 75
      src/views/QuickLogin.vue

+ 1 - 1
src/router/index.js

@@ -6,7 +6,7 @@ const routes = [
   { path: '/', component: () => import('../views/Login.vue') },
   { path: '/', component: () => import('../views/Login.vue') },
   { path: '/login', component: () => import('../views/Login.vue') },
   { path: '/login', component: () => import('../views/Login.vue') },
     //免租户
     //免租户
-  { path: '/blockly-login', meta: {TENANT: '内部测试租户'}, component: () => import('../views/Login.vue') },
+  { path: '/blockly-login', meta: {TENANT: '内部测试租户'}, component: () => import('../views/BlocklyLogin.vue') },
   // 免登录
   // 免登录
   { path: '/quick-login', component: () => import('../views/QuickLogin.vue') },
   { path: '/quick-login', component: () => import('../views/QuickLogin.vue') },
   { path: '/promotion-login', component: () => import('../views/PromotionLogin.vue') },
   { path: '/promotion-login', component: () => import('../views/PromotionLogin.vue') },

+ 301 - 0
src/utils/loginUtils.js

@@ -0,0 +1,301 @@
+import { ref, computed } from 'vue'
+import { getTenantIdByName, login, smsLogin, smsCode } from '@/api/login/login.js'
+import { ElLoading, ElMessage } from 'element-plus'
+
+// 登录数据结构
+const createLoginData = () => {
+  return ref({
+    loginForm: {
+      tenantName: import.meta.env.VITE_APP_DEFAULT_LOGIN_TENANT || '',
+      username: import.meta.env.VITE_APP_DEFAULT_LOGIN_USERNAME || '',
+      password: import.meta.env.VITE_APP_DEFAULT_LOGIN_PASSWORD || '',
+      smsCode: '', // 短信验证码字段
+      rememberMe: false, // 记住
+      phoneNumber: '' // 手机号字段
+    }
+  })
+}
+
+// 验证码相关逻辑
+const createVerificationCodeLogic = () => {
+  const countingDown = ref(false)
+  const countDown = ref(60)
+  const sendingCode = ref(false)
+  let countDownTimer = null
+
+  // 开始倒计时
+  const startCountDown = () => {
+    countingDown.value = true
+    countDown.value = 60
+
+    if (countDownTimer) {
+      clearInterval(countDownTimer)
+    }
+    countDownTimer = setInterval(() => {
+      countDown.value--
+      if (countDown.value <= 0) {
+        clearInterval(countDownTimer)
+        countingDown.value = false
+      }
+    }, 1000)
+  }
+
+  // 清除计时器
+  const clearCountDownTimer = () => {
+    if (countDownTimer) {
+      clearInterval(countDownTimer)
+      countDownTimer = null
+    }
+  }
+
+  // 获取短信验证码
+  const getSmsCode = async (tenantId, tenantName, mobile) => {
+    sendingCode.value = true
+    try {
+      const res = await smsCode(
+        { 'Tenant-Id': tenantId }, {
+          tenantName,
+          mobile,
+          scene: import.meta.env.VITE_APP_LOGIN_SMS_TEMPLATE_ID,
+        }
+      )
+      if (res.code === 0) {
+        ElMessage.success('验证码发送成功')
+        startCountDown()
+      } else {
+        ElMessage.error(res.message || '验证码发送失败')
+      }
+    } catch (error) {
+      ElMessage.error('验证码发送失败,请重试')
+      console.error('发送验证码错误:', error)
+    } finally {
+      sendingCode.value = false
+    }
+  }
+
+  return {
+    countingDown,
+    countDown,
+    sendingCode,
+    getSmsCode,
+    clearCountDownTimer
+  }
+}
+
+// 获取租户ID
+const getTenantId = async (tenantName) => {
+  try {
+    const res = await getTenantIdByName(tenantName)
+    if (res && res.data) {
+      return res.data
+    } else {
+      ElMessage.error('租户填写错误!')
+      return null
+    }
+  } catch (error) {
+    ElMessage.error('租户填写错误!')
+    console.error('获取租户 ID 错误:', error)
+    return null
+  }
+}
+
+// 登录逻辑
+const loginLogic = async (loginForm, tenantId, isAuthorized, router, redirectPath) => {
+  const loginLoading = ref(false)
+  const loading = ref()
+  const isLoggedIn = ref(false)
+
+  loginLoading.value = true
+  try {
+    let res
+    if (!isAuthorized.value) {
+      // 未授权状态,使用短信验证码登录
+      res = await smsLogin(
+        { 'Tenant-Id': tenantId }, {
+          mobile: loginForm.phoneNumber,
+          code: loginForm.smsCode,
+        }
+      )
+    } else {
+      // 授权状态,使用账号密码登录
+      res = await login(
+        { 'Tenant-Id': tenantId },
+        loginForm
+      )
+    }
+
+    if (!res) {
+      return false
+    }
+
+    // 校验登录状态
+    if (res.code === 0) {
+      ElMessage.success('登录成功')
+      isLoggedIn.value = true
+      // 存储登录状态
+      localStorage.setItem('isLoggedIn', 'true')
+      localStorage.setItem('token', res.data.accessToken)
+
+      // 总是存储用户名和租户名称
+      localStorage.setItem('userName', loginForm.username)
+      localStorage.setItem('tenantName', loginForm.tenantName)
+      // 存储记住我状态
+      localStorage.setItem('rememberMe', loginForm.rememberMe)
+      
+      if (loginForm.rememberMe) {
+        // 保存密码
+        localStorage.setItem('password', loginForm.password)
+      } else {
+        // 如果没有勾选记住我,清除密码
+        localStorage.removeItem('password')
+        localStorage.removeItem('maxCourseSections')
+      }
+      loading.value = ElLoading.service({
+        lock: true,
+        text: '正在加载系统中...',
+        background: 'rgba(0, 0, 0, 0.7)'
+      })
+      // 登录成功后,跳转到指定的页面
+      router.push(redirectPath)
+      return true
+    } else if (res.code === 1002000009) {
+      // 未授权状态,切换到短信验证码登录
+      ElMessage.warning(res.msg || '登录IP未被授权,请使用手机号短信验证码登录!')
+      isAuthorized.value = false
+      return false
+    } else {
+      ElMessage.error(res.msg || '登录失败,请检查账号密码!')
+      return false
+    }
+  } catch (error) {
+    ElMessage.error('登录出错,请重试!')
+    console.error('登录错误:', error)
+    return false
+  } finally {
+    loginLoading.value = false
+    if (loading.value) {
+      loading.value.close()
+    }
+  }
+}
+
+// 本地存储管理
+const loadLoginData = (loginData) => {
+  const storedTenantName = localStorage.getItem('tenantName')
+  const storedUserName = localStorage.getItem('userName')
+  const storedPassword = localStorage.getItem('password')
+
+  // 恢复登录信息到输入框
+  if (storedTenantName) {
+    loginData.value.loginForm.tenantName = storedTenantName
+  }
+  if (storedUserName) {
+    loginData.value.loginForm.username = storedUserName
+  }
+  if (storedPassword) {
+    loginData.value.loginForm.password = storedPassword
+    loginData.value.loginForm.rememberMe = true
+  }
+}
+
+// 检查登录状态
+const checkLoginStatus = (router, redirectPath) => {
+  const storedStatus = localStorage.getItem('isLoggedIn')
+  if (storedStatus === 'true') {
+    router.push(redirectPath)
+    return true
+  }
+  return false
+}
+
+// 表单验证规则生成
+const generateRules = (isAuthorized) => {
+  return computed(() => {
+    if (isAuthorized.value) {
+      // 授权状态:需要账号和密码
+      return {
+        tenantName: [{ required: true, message: '请输入学校名称', trigger: 'blur' }],
+        username: [{ required: true, message: '请输入账号', trigger: 'blur' }],
+        password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
+      }
+    } else {
+      // 未授权状态:需要手机号和短信验证码
+      return {
+        tenantName: [{ required: true, message: '请输入学校名称', trigger: 'blur' }],
+        phoneNumber: [
+          { required: true, message: '请输入手机号', trigger: 'blur' },
+          { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', trigger: 'blur' }
+        ],
+        smsCode: [{ required: true, message: '请输入短信验证码', trigger: 'blur' }]
+      }
+    }
+  })
+}
+
+// 自动登录逻辑(用于QuickLogin和PromotionLogin)
+  const autoLogin = async (tenantName, username, password, router, redirectPath) => {
+  let loading = null
+  try {
+    // 显示全局加载状态
+    loading = ElLoading.service({
+      lock: true,
+      text: '登录中...',
+      background: 'rgba(0, 0, 0, 0.7)'
+    })
+
+    // 获取租户ID
+    const tenantId = await getTenantId(tenantName)
+    if (!tenantId) {
+      // 租户验证失败
+      return false
+    }
+
+    // 执行登录
+    const res = await login(
+      { 'Tenant-Id': tenantId },
+      { tenantName, username, password }
+    )
+
+    if (res && res.code === 0) {
+      // 登录成功,保存登录状态
+      localStorage.setItem('isLoggedIn', 'true')
+      localStorage.setItem('token', res.data.accessToken)
+      localStorage.setItem('userName', username)
+      localStorage.setItem('tenantName', tenantName)
+      localStorage.setItem('password', password)
+      localStorage.setItem('rememberMe', 'true')
+
+      ElMessage.success('信息校验成功')
+      // 跳转到课程界面
+      router.push(redirectPath)
+      return true
+    } else {
+      ElMessage.error(res?.message || '信息校验失败')
+      // 如果登录失败,跳转到正常登录页面
+      router.push('/login')
+      return false
+    }
+  } catch (error) {
+    ElMessage.error('信息校验过程中发生错误')
+    console.error('信息校验错误:', error)
+    // 错误时跳转到登录页面
+    router.push('/login')
+    return false
+  } finally {
+    // 关闭加载状态
+    if (loading) {
+      loading.close()
+    }
+  }
+}
+
+export {
+  createLoginData,
+  createVerificationCodeLogic,
+  getTenantId,
+  loginLogic,
+  loadLoginData,
+  checkLoginStatus,
+  generateRules,
+  autoLogin
+}

+ 351 - 0
src/views/BlocklyLogin.vue

@@ -0,0 +1,351 @@
+<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-show="!tenantNameQuery"
+                      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 tenantNameQuery = ref()
+
+// 授权状态 默认授权
+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, '/programming')
+    }
+  })
+}
+
+// 在组件挂载时检查登录状态和恢复登录信息
+onMounted(() => {
+  // 加载本地存储的登录数据
+  loadLoginData(loginData)
+
+  // 检查地址栏是否有tenantName参数
+  let tenantName = router.currentRoute.value.meta?.TENANT;
+  if (tenantName && tenantName === "内部测试租户") {
+    loginData.value.loginForm.tenantName = tenantNameQuery.value = tenantName
+  } else if (Object.keys(router.currentRoute.value.query).length > 0) {
+    // 其他参数,重定向到登录页
+    router.replace('/blockly-login')
+  }
+
+  // 检查登录状态,如果已登录则直接跳转到首页
+  checkLoginStatus(router, '/programming')
+
+  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>

+ 80 - 259
src/views/Login.vue

@@ -3,71 +3,71 @@
   <div class="login-content">
   <div class="login-content">
     <!-- 背景图容器 -->
     <!-- 背景图容器 -->
     <div
     <div
-      class="bg-image-container"
-      :style="{ backgroundImage: `url(${BGImages})`, backgroundSize: 'cover' }"
+        class="bg-image-container"
+        :style="{ backgroundImage: `url(${BGImages})`, backgroundSize: 'cover' }"
     ></div>
     ></div>
     <!-- 登录输入框 -->
     <!-- 登录输入框 -->
     <div class="login-wrapper">
     <div class="login-wrapper">
       <div class="login-input">
       <div class="login-input">
         <span>{{ appTitle }}</span>
         <span>{{ appTitle }}</span>
         <el-form
         <el-form
-          ref="loginFormRef"
-          :model="loginData.loginForm"
-          :rules="rules"
-          label-width="0px"
-          class="input-item"
+            ref="loginFormRef"
+            :model="loginData.loginForm"
+            :rules="rules"
+            label-width="0px"
+            class="input-item"
         >
         >
           <el-form-item prop="tenantName">
           <el-form-item prop="tenantName">
-            <el-input v-show="!tenantNameQuery"
-              v-model="loginData.loginForm.tenantName"
-              :prefix-icon="HomeFilled"
-              placeholder="学校"
+            <el-input
+                v-model="loginData.loginForm.tenantName"
+                :prefix-icon="HomeFilled"
+                placeholder="学校"
             />
             />
           </el-form-item>
           </el-form-item>
 
 
           <!-- 条件显示手机号和短信验证码或账号和密码 -->
           <!-- 条件显示手机号和短信验证码或账号和密码 -->
-         <template v-if="isAuthorized">
+          <template v-if="isAuthorized">
             <el-form-item prop="username">
             <el-form-item prop="username">
               <el-input
               <el-input
-                v-model="loginData.loginForm.username"
-                :prefix-icon="Avatar"
-                placeholder="账号"
+                  v-model="loginData.loginForm.username"
+                  :prefix-icon="Avatar"
+                  placeholder="账号"
               />
               />
             </el-form-item>
             </el-form-item>
             <el-form-item prop="password">
             <el-form-item prop="password">
               <el-input
               <el-input
-                v-model="loginData.loginForm.password"
-                class="password-input"
-                type="password"
-                :prefix-icon="Lock"
-                placeholder="密码"
-                show-password
+                  v-model="loginData.loginForm.password"
+                  class="password-input"
+                  type="password"
+                  :prefix-icon="Lock"
+                  placeholder="密码"
+                  show-password
               />
               />
             </el-form-item>
             </el-form-item>
           </template>
           </template>
 
 
-       <template v-else>
+          <template v-else>
             <el-form-item prop="phoneNumber">
             <el-form-item prop="phoneNumber">
               <el-input
               <el-input
-                v-model="loginData.loginForm.phoneNumber"
-                :prefix-icon="Iphone"
-                placeholder="手机号"
+                  v-model="loginData.loginForm.phoneNumber"
+                  :prefix-icon="Iphone"
+                  placeholder="手机号"
               />
               />
             </el-form-item>
             </el-form-item>
             <!-- 短信验证码输入框和获取验证码按钮 -->
             <!-- 短信验证码输入框和获取验证码按钮 -->
             <el-form-item prop="smsCode">
             <el-form-item prop="smsCode">
               <div class="sms-code-container">
               <div class="sms-code-container">
                 <el-input
                 <el-input
-                  v-model="loginData.loginForm.smsCode"
-                  placeholder="短信验证码"
-                  class="sms-input"
+                    v-model="loginData.loginForm.smsCode"
+                    placeholder="短信验证码"
+                    class="sms-input"
                 />
                 />
                 <el-button
                 <el-button
-                  type="primary"
-                  @click="getSmsCode"
-                  :disabled="countingDown"
-                  class="get-code-btn"
-                  :loading="sendingCode"
+                    type="primary"
+                    @click="handleGetSmsCode"
+                    :disabled="countingDown"
+                    class="get-code-btn"
+                    :loading="sendingCode"
                 >
                 >
                   {{ countingDown ? `${countDown}秒后重新获取` : '获取验证码' }}
                   {{ countingDown ? `${countDown}秒后重新获取` : '获取验证码' }}
                 </el-button>
                 </el-button>
@@ -81,26 +81,34 @@
           </el-form-item>
           </el-form-item>
         </el-form>
         </el-form>
         <!-- 多选框 -->
         <!-- 多选框 -->
-          <div class="check-box">
-            <el-checkbox
+        <div class="check-box">
+          <el-checkbox
               v-model="loginData.loginForm.rememberMe"
               v-model="loginData.loginForm.rememberMe"
               label="记住我"
               label="记住我"
               size="large"
               size="large"
-            />
-          </div>
+          />
+        </div>
       </div>
       </div>
     </div>
     </div>
   </div>
   </div>
 </template>
 </template>
 
 
 <script setup>
 <script setup>
-import { ref, onMounted, computed, onUnmounted, nextTick } from 'vue'
+import { ref, onMounted, onUnmounted } from 'vue'
 import { useRouter } from 'vue-router'
 import { useRouter } from 'vue-router'
-import { HomeFilled, Avatar, Lock,Iphone } from '@element-plus/icons-vue'
-import { getTenantIdByName, login,smsLogin,smsCode } from '@/api/login/login.js'
-import { ElLoading, ElMessage } from 'element-plus'
+import { HomeFilled, Avatar, Lock, Iphone } from '@element-plus/icons-vue'
+import { ElMessage } from 'element-plus'
 
 
 import BGImages from '@/assets/images/homeBG.png'
 import BGImages from '@/assets/images/homeBG.png'
+import {
+  createLoginData,
+  createVerificationCodeLogic,
+  getTenantId,
+  loginLogic,
+  loadLoginData,
+  checkLoginStatus,
+  generateRules
+} from '@/utils/loginUtils.js'
 
 
 const router = useRouter()
 const router = useRouter()
 
 
@@ -109,78 +117,20 @@ const appTitle = import.meta.env.VITE_APP_TITLE
 
 
 const loginFormRef = ref(null)
 const loginFormRef = ref(null)
 
 
-const loginData = ref({
-  loginForm: {
-    tenantName: import.meta.env.VITE_APP_DEFAULT_LOGIN_TENANT || '',
-    username: import.meta.env.VITE_APP_DEFAULT_LOGIN_USERNAME || '',
-    password: import.meta.env.VITE_APP_DEFAULT_LOGIN_PASSWORD || '',
-    smsCode: '', // 短信验证码字段
-    rememberMe: false, // 记住
-     phoneNumber: '' // 手机号字段
-  }
-})
-
-const loginLoading = ref(false)
-const loading = ref() // ElLoading.service 返回的实例
-const tenantId = ref('')
-// 登录状态标识
-const isLoggedIn = ref(false)
-//地址栏传参默认值
-const tenantNameQuery = ref()
+// 初始化登录数据
+const loginData = createLoginData()
 
 
-// 短信验证码相关状态
-const countingDown = ref(false)
-const countDown = ref(60)
-const sendingCode = ref(false)
-let countDownTimer = null
+// 初始化验证码逻辑
+const { countingDown, countDown, sendingCode, getSmsCode, clearCountDownTimer } = createVerificationCodeLogic()
 
 
 // 授权状态 默认授权
 // 授权状态 默认授权
 const isAuthorized = ref(true)
 const isAuthorized = ref(true)
 
 
-// 输入框校验 - 根据授权状态动态调整
-const rules = computed(() => {
-  if (isAuthorized.value) {
-    // 授权状态:需要账号和密码
-    return {
-      tenantName: [{ required: true, message: '请输入学校名称', trigger: 'blur' }],
-      username: [{ required: true, message: '请输入账号', trigger: 'blur' }],
-      password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
-    }
-  } else {
-    // 未授权状态:需要手机号和短信验证码
-    return {
-      tenantName: [{ required: true, message: '请输入学校名称', trigger: 'blur' }],
-      phoneNumber: [
-        { required: true, message: '请输入手机号', trigger: 'blur' },
-        { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', trigger: 'blur' }
-      ],
-      smsCode: [{ required: true, message: '请输入短信验证码', trigger: 'blur' }]
-    }
-  }
-})
-
-// 获取租户 ID
-const getTenantId = async () => {
-  try {
-    const res = await getTenantIdByName(loginData.value.loginForm.tenantName)
-    console.log(res);
-    if (res && res.data) {
-      //记录租户id
-      tenantId.value = res.data
-      return true; // 租户验证成功
-    } else {
-      ElMessage.error('租户填写错误!')
-      return false; // 租户验证失败
-    }
-  } catch (error) {
-    ElMessage.error('租户填写错误!')
-    console.error('获取租户 ID 错误:', error)
-    return false; // 租户验证失败
-  }
-}
+// 生成表单验证规则
+const rules = generateRules(isAuthorized)
 
 
-// 获取短信验证码函数
-const getSmsCode = async () => {
+// 获取短信验证码
+const handleGetSmsCode = async () => {
   // 先验证租户和手机号是否填写
   // 先验证租户和手机号是否填写
   if (!loginData.value.loginForm.tenantName) {
   if (!loginData.value.loginForm.tenantName) {
     ElMessage.warning('请先输入学校名称')
     ElMessage.warning('请先输入学校名称')
@@ -191,173 +141,47 @@ const getSmsCode = async () => {
     return
     return
   }
   }
   // 验证租户是否存在
   // 验证租户是否存在
-  const tenantValid = await getTenantId()
-  if (!tenantValid) {
+  const tenantId = await getTenantId(loginData.value.loginForm.tenantName)
+  if (!tenantId) {
     return
     return
   }
   }
-  sendingCode.value = true
-  try {
-    // 获取验证码
-    const res = await smsCode(
-      { 'Tenant-Id': tenantId.value },{
-      tenantName: loginData.value.loginForm.tenantName,
-      mobile: loginData.value.loginForm.phoneNumber,
-      scene: import.meta.env.VITE_APP_LOGIN_SMS_TEMPLATE_ID,
-    })
-    console.log('发送短信验证码:', res)
-    if (res.code === 0) {
-      ElMessage.success('验证码发送成功')
-      // 开始倒计时
-      startCountDown()
-    } else {
-      ElMessage.error(res.message || '验证码发送失败')
-    }
-  } catch (error) {
-    ElMessage.error('验证码发送失败,请重试')
-    console.error('发送验证码错误:', error)
-  } finally {
-    sendingCode.value = false
-  }
-}
-
-// 验证码倒计时函数
-const startCountDown = () => {
-  countingDown.value = true
-  countDown.value = 60
 
 
-  if (countDownTimer) {
-    clearInterval(countDownTimer)
-  }
-  countDownTimer = setInterval(() => {
-    countDown.value--
-    if (countDown.value <= 0) {
-      clearInterval(countDownTimer)
-      countingDown.value = false
-    }
-  }, 1000)
+  // 调用验证码逻辑
+  getSmsCode(tenantId, loginData.value.loginForm.tenantName, loginData.value.loginForm.phoneNumber)
 }
 }
 
 
 // 登录
 // 登录
-const handleLogin = async params => {
+const handleLogin = async () => {
   if (!loginFormRef.value) return
   if (!loginFormRef.value) return
   await loginFormRef.value.validate(async valid => {
   await loginFormRef.value.validate(async valid => {
     if (valid) {
     if (valid) {
-      loginLoading.value = true
-      try {
-        // 先验证租户是否存在
-        const tenantValid = await getTenantId()
-        if (!tenantValid) {
-          // 租户验证失败,不执行登录
-          loginLoading.value = false
-          return
-        }
-        const loginDataLoginForm = { ...loginData.value.loginForm }
-
-        // 根据授权状态选择不同的登录接口
-        let res
-        if (!isAuthorized.value) {
-          // 未授权状态,使用短信验证码登录
-          res = await smsLogin(
-            { 'Tenant-Id': tenantId.value },{
-            mobile: loginData.value.loginForm.phoneNumber,
-            code: loginData.value.loginForm.smsCode,
-          })
-        } else {
-          // 授权状态,使用账号密码登录
-          res = await login(
-            { 'Tenant-Id': tenantId.value },
-            loginDataLoginForm
-          )
-        }
-
-        if (!res) {
-          return
-        }
-        // 校验登录状态
-        if (res.code === 0) {
-          ElMessage.success('登录成功')
-          isLoggedIn.value = true
-          // 存储登录状态
-          localStorage.setItem('isLoggedIn', 'true')
-          localStorage.setItem('token', res.data.accessToken)
-
-          // 总是存储用户名和租户名称
-          localStorage.setItem('userName', loginData.value.loginForm.username)
-          localStorage.setItem('tenantName', loginData.value.loginForm.tenantName)
-          // 存储记住我状态
-          localStorage.setItem('rememberMe', loginData.value.loginForm.rememberMe)
-          
-          if (loginData.value.loginForm.rememberMe) {
-            // 保存密码
-            localStorage.setItem('password', loginData.value.loginForm.password)
-          } else {
-            // 如果没有勾选记住我,清除密码
-            localStorage.removeItem('password')
-            localStorage.removeItem('maxCourseSections')
-          }
-          loading.value = ElLoading.service({
-            lock: true,
-            text: '正在加载系统中...',
-            background: 'rgba(0, 0, 0, 0.7)'
-          })
-          // 登录成功后,跳转到指定的页面
-          router.push(!tenantNameQuery.value?'/home':'/programming')
-
-        } else if (res.code === 1002000009)  {
-          // 未授权状态,切换到短信验证码登录
-          ElMessage.warning(res.msg || '登录IP未被授权,请使用手机号短信验证码登录!')
-          isAuthorized.value = false
-        } else {
-          ElMessage.error(res.msg || '登录失败,请检查账号密码!')
-        }
-      } catch (error) {
-        ElMessage.error('登录出错,请重试!')
-        console.error('登录错误:', error)
-      } finally {
-        loginLoading.value = false
-        if (loading.value) {
-          loading.value.close()
-        }
+      // 先验证租户是否存在
+      const tenantId = await getTenantId(loginData.value.loginForm.tenantName)
+      if (!tenantId) {
+        // 租户验证失败,不执行登录
+        return
       }
       }
+
+      // 调用登录逻辑
+      await loginLogic(loginData.value.loginForm, tenantId, isAuthorized, router, '/home')
     }
     }
   })
   })
 }
 }
 
 
 // 在组件挂载时检查登录状态和恢复登录信息
 // 在组件挂载时检查登录状态和恢复登录信息
 onMounted(() => {
 onMounted(() => {
-  const storedStatus = localStorage.getItem('isLoggedIn') // isLoggedIn
-  const storedTenantName = localStorage.getItem('tenantName')
-  const storedUserName = localStorage.getItem('userName')
-  const storedPassword = localStorage.getItem('password')
-
-  // 恢复登录信息到输入框
-  if (storedTenantName) {
-    loginData.value.loginForm.tenantName = storedTenantName
-  }
-  if (storedUserName) {
-    loginData.value.loginForm.username = storedUserName
-  }
-  if (storedPassword) {
-    loginData.value.loginForm.password = storedPassword
-    loginData.value.loginForm.rememberMe = true
-  }
+  // 加载本地存储的登录数据
+  loadLoginData(loginData)
 
 
-  // 检查地址栏是否有tenantName参数
-  let tenantName = router.currentRoute.value.meta?.TENANT;
-  if (tenantName && tenantName === "内部测试租户") {
-    loginData.value.loginForm.tenantName = tenantNameQuery.value = tenantName
-  } else if (Object.keys(router.currentRoute.value.query).length > 0) {
+  if (Object.keys(router.currentRoute.value.query).length > 0) {
     // 其他参数,重定向到登录页
     // 其他参数,重定向到登录页
     router.replace('/login')
     router.replace('/login')
   }
   }
 
 
   // 检查登录状态,如果已登录则直接跳转到首页
   // 检查登录状态,如果已登录则直接跳转到首页
-  if (storedStatus === 'true') {
-    isLoggedIn.value = true
-    router.push(!tenantNameQuery.value?'/home':'/programming')
-  }
+  checkLoginStatus(router, '/home')
 
 
-   const handleKeyPress = (event) => {
+  const handleKeyPress = (event) => {
     // 检查是否按下回车键(keyCode 13)
     // 检查是否按下回车键(keyCode 13)
     if (event.key === 'Enter' || event.keyCode === 13) {
     if (event.key === 'Enter' || event.keyCode === 13) {
       handleLogin()
       handleLogin()
@@ -369,11 +193,8 @@ onMounted(() => {
   // 在组件卸载时移除事件监听
   // 在组件卸载时移除事件监听
   onUnmounted(() => {
   onUnmounted(() => {
     document.removeEventListener('keydown', handleKeyPress)
     document.removeEventListener('keydown', handleKeyPress)
-    if (countDownTimer) {
-      clearInterval(countDownTimer)
-    }
+    clearCountDownTimer()
   })
   })
-
 })
 })
 </script>
 </script>
 
 
@@ -450,7 +271,7 @@ onMounted(() => {
   color: black;
   color: black;
   font-size: rpx(8);
   font-size: rpx(8);
   letter-spacing: rpx(10);
   letter-spacing: rpx(10);
-   border-radius: rpx(5);
+  border-radius: rpx(5);
   margin: rpx(15) 0 auto;
   margin: rpx(15) 0 auto;
   border: none;
   border: none;
   background: linear-gradient(to bottom, #fee78a, #ffce1b);
   background: linear-gradient(to bottom, #fee78a, #ffce1b);
@@ -505,8 +326,8 @@ onMounted(() => {
   margin: 0 !important;
   margin: 0 !important;
   padding: 0;
   padding: 0;
   font-size: rpx(5);
   font-size: rpx(5);
-   border-radius: rpx(5);
-   border-top-left-radius: 0;
+  border-radius: rpx(5);
+  border-top-left-radius: 0;
   border-bottom-left-radius: 0;
   border-bottom-left-radius: 0;
   letter-spacing: normal;
   letter-spacing: normal;
   display: flex;
   display: flex;
@@ -517,4 +338,4 @@ onMounted(() => {
   // background: linear-gradient(to bottom, #78c0ff, #0070f3);
   // background: linear-gradient(to bottom, #78c0ff, #0070f3);
   border: none;
   border: none;
 }
 }
-</style>
+</style>

+ 11 - 77
src/views/PromotionLogin.vue

@@ -12,11 +12,9 @@
 <script setup>
 <script setup>
 import { onMounted } from 'vue'
 import { onMounted } from 'vue'
 import { useRouter } from 'vue-router'
 import { useRouter } from 'vue-router'
-import { getTenantIdByName, login } from '@/api/login/login.js'
-import { ElLoading, ElMessage } from 'element-plus'
+import { autoLogin } from '@/utils/loginUtils.js'
 
 
 const router = useRouter()
 const router = useRouter()
-let loading = null
 
 
 // 测试账号信息
 // 测试账号信息
 const testAccount = {
 const testAccount = {
@@ -25,86 +23,22 @@ const testAccount = {
   password: import.meta.env.VITE_APP_PROPAGATION_LOGIN_PASSWORD
   password: import.meta.env.VITE_APP_PROPAGATION_LOGIN_PASSWORD
 }
 }
 
 
-// 获取租户 ID
-const getTenantId = async (tenantName) => {
-  try {
-    const res = await getTenantIdByName(tenantName)
-    if (res && res.data) {
-      return res.data
-    } else {
-      ElMessage.error('租户验证失败!')
-      return null
-    }
-  } catch (error) {
-    ElMessage.error('获取租户信息失败!')
-    console.error('获取租户 ID 错误:', error)
-    return null
-  }
-}
-
-// 自动登录函数
-const autoLogin = async () => {
-  try {
-    // 显示全局加载状态
-    loading = ElLoading.service({
-      lock: true,
-      text: '登录中...',
-      background: 'rgba(0, 0, 0, 0.7)'
-    })
-
-    // 获取租户ID
-    const tenantId = await getTenantId(testAccount.tenantName)
-    if (!tenantId) {
-      // 租户验证失败
-      return
-    }
-
-    // 执行登录
-    const res = await login(
-      { 'Tenant-Id': tenantId },
-      testAccount
-    )
-
-    if (res && res.code === 0) {
-      // 登录成功,保存登录状态
-      localStorage.setItem('isLoggedIn', 'true')
-      localStorage.setItem('token', res.data.accessToken)
-      localStorage.setItem('userName', testAccount.username)
-      localStorage.setItem('tenantName', testAccount.tenantName)
-      localStorage.setItem('password', testAccount.password)
-      localStorage.setItem('rememberMe', 'true')
-
-      ElMessage.success('信息校验成功')
-      // 跳转到课程界面
-      router.push({
+// 组件挂载时立即执行自动登录
+onMounted(async () => {
+  await autoLogin(
+      testAccount.tenantName,
+      testAccount.username,
+      testAccount.password,
+      router,
+      {
         path: '/ai-develop',
         path: '/ai-develop',
         state: {
         state: {
           typeId: 5,
           typeId: 5,
           typeName: 'AI时光旅行',
           typeName: 'AI时光旅行',
           typeSort: '02'
           typeSort: '02'
         }
         }
-      })
-    } else {
-      ElMessage.error(res?.message || '信息校验失败')
-      // 如果登录失败,跳转到正常登录页面
-      router.push('/login')
-    }
-  } catch (error) {
-    ElMessage.error('信息校验过程中发生错误')
-    console.error('信息校验错误:', error)
-    // 错误时跳转到登录页面
-    router.push('/login')
-  } finally {
-    // 关闭加载状态
-    if (loading) {
-      loading.close()
-    }
-  }
-}
-
-// 组件挂载时立即执行自动登录
-onMounted(() => {
-  autoLogin()
+      }
+  )
 })
 })
 </script>
 </script>
 
 

+ 9 - 75
src/views/QuickLogin.vue

@@ -12,11 +12,9 @@
 <script setup>
 <script setup>
 import { onMounted } from 'vue'
 import { onMounted } from 'vue'
 import { useRouter } from 'vue-router'
 import { useRouter } from 'vue-router'
-import { getTenantIdByName, login } from '@/api/login/login.js'
-import { ElLoading, ElMessage } from 'element-plus'
+import { autoLogin } from '@/utils/loginUtils.js'
 
 
 const router = useRouter()
 const router = useRouter()
-let loading = null
 
 
 // 测试账号信息
 // 测试账号信息
 const testAccount = {
 const testAccount = {
@@ -25,79 +23,15 @@ const testAccount = {
   password: import.meta.env.VITE_APP_GUANGDIAN_LOGIN_PASSWORD
   password: import.meta.env.VITE_APP_GUANGDIAN_LOGIN_PASSWORD
 }
 }
 
 
-// 获取租户 ID
-const getTenantId = async (tenantName) => {
-  try {
-    const res = await getTenantIdByName(tenantName)
-    if (res && res.data) {
-      return res.data
-    } else {
-      ElMessage.error('租户验证失败!')
-      return null
-    }
-  } catch (error) {
-    ElMessage.error('获取租户信息失败!')
-    console.error('获取租户 ID 错误:', error)
-    return null
-  }
-}
-
-// 自动登录函数
-const autoLogin = async () => {
-  try {
-    // 显示全局加载状态
-    loading = ElLoading.service({
-      lock: true,
-      text: '登录中...',
-      background: 'rgba(0, 0, 0, 0.7)'
-    })
-
-    // 获取租户ID
-    const tenantId = await getTenantId(testAccount.tenantName)
-    if (!tenantId) {
-      // 租户验证失败
-      return
-    }
-
-    // 执行登录
-    const res = await login(
-      { 'Tenant-Id': tenantId },
-      testAccount
-    )
-
-    if (res && res.code === 0) {
-      // 登录成功,保存登录状态
-      localStorage.setItem('isLoggedIn', 'true')
-      localStorage.setItem('token', res.data.accessToken)
-      localStorage.setItem('userName', testAccount.username)
-      localStorage.setItem('tenantName', testAccount.tenantName)
-      localStorage.setItem('password', testAccount.password)
-      localStorage.setItem('rememberMe', 'true')
-
-      ElMessage.success('信息校验成功')
-      // 跳转到课程界面
-      router.push('/ai-general-course')
-    } else {
-      ElMessage.error(res?.message || '信息校验失败')
-      // 如果登录失败,跳转到正常登录页面
-      router.push('/login')
-    }
-  } catch (error) {
-    ElMessage.error('信息校验过程中发生错误')
-    console.error('信息校验错误:', error)
-    // 错误时跳转到登录页面
-    router.push('/login')
-  } finally {
-    // 关闭加载状态
-    if (loading) {
-      loading.close()
-    }
-  }
-}
-
 // 组件挂载时立即执行自动登录
 // 组件挂载时立即执行自动登录
-onMounted(() => {
-  autoLogin()
+onMounted(async () => {
+  await autoLogin(
+      testAccount.tenantName,
+      testAccount.username,
+      testAccount.password,
+      router,
+      '/ai-general-course'
+  )
 })
 })
 </script>
 </script>