AiCourseLogin.vue 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. <template>
  2. <!-- 登录页面 -->
  3. <div class="login-content">
  4. <!-- 背景图容器 -->
  5. <div
  6. class="bg-image-container"
  7. :style="{ backgroundImage: `url(${BGImages})`, backgroundSize: 'cover' }"
  8. ></div>
  9. <!-- 登录输入框 -->
  10. <div class="login-wrapper">
  11. <div class="login-input">
  12. <span>{{ appTitle }}</span>
  13. <el-form
  14. ref="loginFormRef"
  15. :model="loginData.loginForm"
  16. :rules="rules"
  17. label-width="0px"
  18. class="input-item"
  19. >
  20. <el-form-item prop="tenantName">
  21. <el-input v-model="loginData.loginForm.tenantName"
  22. :prefix-icon="HomeFilled"
  23. placeholder="学校"
  24. />
  25. </el-form-item>
  26. <!-- 条件显示手机号和短信验证码或账号和密码 -->
  27. <template v-if="isAuthorized">
  28. <el-form-item prop="username">
  29. <el-input
  30. v-model="loginData.loginForm.username"
  31. :prefix-icon="Avatar"
  32. placeholder="账号"
  33. />
  34. </el-form-item>
  35. <el-form-item prop="password">
  36. <el-input
  37. v-model="loginData.loginForm.password"
  38. class="password-input"
  39. type="password"
  40. :prefix-icon="Lock"
  41. placeholder="密码"
  42. show-password
  43. />
  44. </el-form-item>
  45. </template>
  46. <template v-else>
  47. <el-form-item prop="phoneNumber">
  48. <el-input
  49. v-model="loginData.loginForm.phoneNumber"
  50. :prefix-icon="Iphone"
  51. placeholder="手机号"
  52. />
  53. </el-form-item>
  54. <!-- 短信验证码输入框和获取验证码按钮 -->
  55. <el-form-item prop="smsCode">
  56. <div class="sms-code-container">
  57. <el-input
  58. v-model="loginData.loginForm.smsCode"
  59. placeholder="短信验证码"
  60. class="sms-input"
  61. />
  62. <el-button
  63. type="primary"
  64. @click="handleGetSmsCode"
  65. :disabled="countingDown"
  66. class="get-code-btn"
  67. :loading="sendingCode"
  68. >
  69. {{ countingDown ? `${countDown}秒后重新获取` : '获取验证码' }}
  70. </el-button>
  71. </div>
  72. </el-form-item>
  73. </template>
  74. <!-- 登录按钮 -->
  75. <el-form-item>
  76. <el-button class="login-btn" type="primary" @click="handleLogin">登录</el-button>
  77. </el-form-item>
  78. </el-form>
  79. <!-- 多选框 -->
  80. <div class="check-box">
  81. <el-checkbox
  82. v-model="loginData.loginForm.rememberMe"
  83. label="记住我"
  84. size="large"
  85. />
  86. </div>
  87. </div>
  88. </div>
  89. </div>
  90. </template>
  91. <script setup>
  92. import { ref, onMounted, onUnmounted } from 'vue'
  93. import { useRouter } from 'vue-router'
  94. import { HomeFilled, Avatar, Lock, Iphone } from '@element-plus/icons-vue'
  95. import { ElMessage } from 'element-plus'
  96. import BGImages from '@/assets/images/aiCourseBG.png'
  97. import {
  98. createLoginData,
  99. createVerificationCodeLogic,
  100. getTenantId,
  101. loginLogic,
  102. loadLoginData,
  103. checkLoginStatus,
  104. generateRules
  105. } from '@/utils/loginUtils.js'
  106. import {aiCourseRoutes} from "@/router/index.js";
  107. const router = useRouter()
  108. // 获取环境变量
  109. const appTitle = import.meta.env.VITE_APP_TITLE
  110. const loginFormRef = ref(null)
  111. // 初始化登录数据
  112. const loginData = createLoginData()
  113. // 初始化验证码逻辑
  114. const { countingDown, countDown, sendingCode, getSmsCode, clearCountDownTimer } = createVerificationCodeLogic()
  115. // 登录状态标识
  116. const isLoggedIn = ref(false)
  117. // 授权状态 默认授权
  118. const isAuthorized = ref(true)
  119. // 生成表单验证规则
  120. const rules = generateRules(isAuthorized)
  121. // 获取短信验证码
  122. const handleGetSmsCode = async () => {
  123. // 先验证租户和手机号是否填写
  124. if (!loginData.value.loginForm.tenantName) {
  125. ElMessage.warning('请先输入学校名称')
  126. return
  127. }
  128. if (!loginData.value.loginForm.phoneNumber) {
  129. ElMessage.warning('请先输入手机号')
  130. return
  131. }
  132. // 验证租户是否存在
  133. const tenantId = await getTenantId(loginData.value.loginForm.tenantName)
  134. if (!tenantId) {
  135. return
  136. }
  137. // 调用验证码逻辑
  138. getSmsCode(tenantId, loginData.value.loginForm.tenantName, loginData.value.loginForm.phoneNumber)
  139. }
  140. // 登录
  141. const handleLogin = async () => {
  142. if (!loginFormRef.value) return
  143. await loginFormRef.value.validate(async valid => {
  144. if (valid) {
  145. // 先验证租户是否存在
  146. const tenantId = await getTenantId(loginData.value.loginForm.tenantName)
  147. if (!tenantId) {
  148. // 租户验证失败,不执行登录
  149. return
  150. }
  151. // 调用登录逻辑
  152. await loginLogic(loginData.value.loginForm, tenantId, isAuthorized, router, aiCourseRoutes.home)
  153. }
  154. })
  155. }
  156. // 在组件挂载时检查登录状态和恢复登录信息
  157. onMounted(() => {
  158. // 加载本地存储的登录数据
  159. loadLoginData(loginData)
  160. // 检查地址栏是否有tenantName参数
  161. if (Object.keys(router.currentRoute.value.query).length > 0) {
  162. // 其他参数,重定向到登录页
  163. router.replace('/ai-login')
  164. }
  165. // 检查登录状态,如果已登录则直接跳转到首页
  166. checkLoginStatus(router, '/aiCourseHome')
  167. const handleKeyPress = (event) => {
  168. // 检查是否按下回车键(keyCode 13)
  169. if (event.key === 'Enter' || event.keyCode === 13) {
  170. handleLogin()
  171. }
  172. }
  173. document.addEventListener('keydown', handleKeyPress)
  174. // 在组件卸载时移除事件监听
  175. onUnmounted(() => {
  176. document.removeEventListener('keydown', handleKeyPress)
  177. clearCountDownTimer()
  178. })
  179. })
  180. </script>
  181. <style scoped lang="scss">
  182. @use 'sass:math';
  183. // 定义rpx转换函数
  184. @function rpx($px) {
  185. @return math.div($px, 750) * 100vw;
  186. }
  187. .login-content {
  188. position: fixed;
  189. top: 0;
  190. left: 0;
  191. right: 0;
  192. bottom: 0;
  193. display: flex;
  194. flex-direction: row; // 修改为水平布局
  195. }
  196. .bg-image-container {
  197. flex: 3; // 背景图占比为 3
  198. background-size: cover;
  199. background-position: center;
  200. }
  201. .login-wrapper {
  202. flex: 1; // 登录框占比为 1
  203. background: linear-gradient(to bottom, #001169, #8a78d0);
  204. padding: 20px;
  205. position: static;
  206. transform: none;
  207. display: flex; // 添加 Flexbox 布局
  208. justify-content: center; // 水平居中
  209. align-items: center; // 垂直居中
  210. }
  211. .login-input {
  212. width: rpx(190);
  213. height: rpx(240);
  214. display: flex;
  215. justify-content: center; // 水平居中
  216. align-items: center; // 垂直居中
  217. flex-direction: column; // 子元素垂直排列
  218. text-align: center; // 文本居中
  219. }
  220. .login-input span{
  221. color: white;
  222. font-size: rpx(11);
  223. padding-bottom: rpx(5);
  224. letter-spacing: rpx(1);
  225. }
  226. .input-item {
  227. display: flex;
  228. flex-direction: column; // 子元素垂直排列
  229. justify-content: center; // 内容垂直居中
  230. align-items: center; // 内容水平居中
  231. }
  232. .el-input ::v-deep(.el-input__wrapper){
  233. border-radius: rpx(5);
  234. }
  235. .input-item .el-form-item {
  236. margin-bottom: 0;
  237. }
  238. .input-item .el-input {
  239. width: rpx(150);
  240. height: rpx(22);
  241. margin-bottom: rpx(15);
  242. font-size: rpx(7);
  243. }
  244. .el-form-item ::v-deep(.el-form-item__error) {
  245. top: rpx(25);
  246. }
  247. .login-btn {
  248. width: rpx(150);
  249. height: rpx(22);
  250. color: black;
  251. font-size: rpx(8);
  252. letter-spacing: rpx(10);
  253. border-radius: rpx(5);
  254. margin: rpx(15) 0 auto;
  255. border: none;
  256. background: linear-gradient(to bottom, #fee78a, #ffce1b);
  257. box-shadow: 0 8px 8px rgb(0, 0, 0, 0.2);
  258. }
  259. .password-input {
  260. margin-bottom: rpx(0) !important;
  261. }
  262. .check-box {
  263. width: rpx(150);
  264. height: rpx(18);
  265. margin: rpx(5) auto;
  266. display: flex;
  267. justify-content: flex-end;
  268. align-items: center;
  269. }
  270. .check-box .el-checkbox {
  271. color: white;
  272. padding-right: rpx(10);
  273. font-size: rpx(6);
  274. }
  275. .el-checkbox ::v-deep(.el-checkbox__label){
  276. font-size: rpx(6);
  277. }
  278. .check-box .forgot-password {
  279. color: white;
  280. font-size: rpx(6);
  281. text-decoration: none;
  282. }
  283. // 短信验证码容器样式
  284. .sms-code-container {
  285. display: flex;
  286. align-items: center;
  287. width: rpx(150);
  288. height: rpx(22);
  289. }
  290. .sms-input {
  291. flex: 1;
  292. height: rpx(22);
  293. margin-bottom: 0 !important;
  294. }
  295. .sms-input ::v-deep(.el-input__wrapper) {
  296. border-top-right-radius: 0;
  297. border-bottom-right-radius: 0;
  298. }
  299. .get-code-btn {
  300. width: rpx(40);
  301. height: rpx(22);
  302. margin: 0 !important;
  303. padding: 0;
  304. font-size: rpx(5);
  305. border-radius: rpx(5);
  306. border-top-left-radius: 0;
  307. border-bottom-left-radius: 0;
  308. letter-spacing: normal;
  309. display: flex;
  310. align-items: center;
  311. justify-content: center;
  312. background-color: #fff;
  313. color: #919191;
  314. // background: linear-gradient(to bottom, #78c0ff, #0070f3);
  315. border: none;
  316. }
  317. </style>