Login.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  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
  22. v-model="loginData.loginForm.tenantName"
  23. :prefix-icon="HomeFilled"
  24. placeholder="学校"
  25. />
  26. </el-form-item>
  27. <!-- 条件显示手机号和短信验证码或账号和密码 -->
  28. <template v-if="isAuthorized">
  29. <el-form-item prop="username">
  30. <el-input
  31. v-model="loginData.loginForm.username"
  32. :prefix-icon="Avatar"
  33. placeholder="账号"
  34. />
  35. </el-form-item>
  36. <el-form-item prop="password">
  37. <el-input
  38. v-model="loginData.loginForm.password"
  39. class="password-input"
  40. type="password"
  41. :prefix-icon="Lock"
  42. placeholder="密码"
  43. show-password
  44. />
  45. </el-form-item>
  46. </template>
  47. <template v-else>
  48. <el-form-item prop="phoneNumber">
  49. <el-input
  50. v-model="loginData.loginForm.phoneNumber"
  51. :prefix-icon="Iphone"
  52. placeholder="手机号"
  53. />
  54. </el-form-item>
  55. <!-- 短信验证码输入框和获取验证码按钮 -->
  56. <el-form-item prop="smsCode">
  57. <div class="sms-code-container">
  58. <el-input
  59. v-model="loginData.loginForm.smsCode"
  60. placeholder="短信验证码"
  61. class="sms-input"
  62. />
  63. <el-button
  64. type="primary"
  65. @click="handleGetSmsCode"
  66. :disabled="countingDown"
  67. class="get-code-btn"
  68. :loading="sendingCode"
  69. >
  70. {{ countingDown ? `${countDown}秒后重新获取` : '获取验证码' }}
  71. </el-button>
  72. </div>
  73. </el-form-item>
  74. </template>
  75. <!-- 登录按钮 -->
  76. <el-form-item>
  77. <el-button class="login-btn" type="primary" @click="handleLogin">登录</el-button>
  78. </el-form-item>
  79. </el-form>
  80. <!-- 多选框 -->
  81. <div class="check-box">
  82. <router-link to="/register-login" class="register-link">没有账号?立即注册</router-link>
  83. <el-checkbox
  84. v-model="loginData.loginForm.rememberMe"
  85. label="记住我"
  86. />
  87. </div>
  88. </div>
  89. </div>
  90. </div>
  91. </template>
  92. <script setup>
  93. import { ref, onMounted, onUnmounted } from 'vue'
  94. import { useRouter } from 'vue-router'
  95. import { HomeFilled, Avatar, Lock, Iphone } from '@element-plus/icons-vue'
  96. import { ElMessage } from 'element-plus'
  97. import BGImages from '@/assets/images/homeBG.png'
  98. import {
  99. createLoginData,
  100. createVerificationCodeLogic,
  101. getTenantId,
  102. loginLogic,
  103. loadLoginData,
  104. checkLoginStatus,
  105. generateRules
  106. } from '@/utils/loginUtils.js'
  107. import {homeRoutes} from "@/router/index.js";
  108. const router = useRouter()
  109. // 获取环境变量
  110. const appTitle = import.meta.env.VITE_APP_TITLE
  111. const loginFormRef = ref(null)
  112. // 初始化登录数据
  113. const loginData = createLoginData()
  114. // 初始化验证码逻辑
  115. const { countingDown, countDown, sendingCode, getSmsCode, clearCountDownTimer } = createVerificationCodeLogic()
  116. // 授权状态 默认授权
  117. const isAuthorized = ref(true)
  118. // 生成表单验证规则
  119. const rules = generateRules(isAuthorized)
  120. // 获取短信验证码
  121. const handleGetSmsCode = async () => {
  122. // 先验证租户和手机号是否填写
  123. if (!loginData.value.loginForm.tenantName) {
  124. ElMessage.warning('请先输入学校名称')
  125. return
  126. }
  127. if (!loginData.value.loginForm.phoneNumber) {
  128. ElMessage.warning('请先输入手机号')
  129. return
  130. }
  131. // 验证租户是否存在
  132. const tenantId = await getTenantId(loginData.value.loginForm.tenantName)
  133. if (!tenantId) {
  134. return
  135. }
  136. // 调用验证码逻辑
  137. getSmsCode(tenantId, loginData.value.loginForm.tenantName, loginData.value.loginForm.phoneNumber)
  138. }
  139. // 登录
  140. const handleLogin = async () => {
  141. if (!loginFormRef.value) return
  142. await loginFormRef.value.validate(async valid => {
  143. if (valid) {
  144. // 先验证租户是否存在
  145. const tenantId = await getTenantId(loginData.value.loginForm.tenantName)
  146. if (!tenantId) {
  147. // 租户验证失败,不执行登录
  148. return
  149. }
  150. // 调用登录逻辑
  151. await loginLogic(loginData.value.loginForm, tenantId, isAuthorized, router, homeRoutes.home)
  152. }
  153. })
  154. }
  155. // 在组件挂载时检查登录状态和恢复登录信息
  156. onMounted(() => {
  157. // 加载本地存储的登录数据
  158. loadLoginData(loginData)
  159. if (Object.keys(router.currentRoute.value.query).length > 0) {
  160. // 其他参数,重定向到登录页
  161. router.replace(homeRoutes.login)
  162. }
  163. // 检查登录状态,如果已登录则直接跳转到首页
  164. checkLoginStatus(router, homeRoutes.home)
  165. const handleKeyPress = (event) => {
  166. // 检查是否按下回车键(keyCode 13)
  167. if (event.key === 'Enter' || event.keyCode === 13) {
  168. handleLogin()
  169. }
  170. }
  171. document.addEventListener('keydown', handleKeyPress)
  172. // 在组件卸载时移除事件监听
  173. onUnmounted(() => {
  174. document.removeEventListener('keydown', handleKeyPress)
  175. clearCountDownTimer()
  176. })
  177. })
  178. </script>
  179. <style scoped lang="scss">
  180. @use 'sass:math';
  181. // 定义rpx转换函数
  182. @function rpx($px) {
  183. @return math.div($px, 750) * 100vw;
  184. }
  185. .login-content {
  186. position: fixed;
  187. top: 0;
  188. left: 0;
  189. right: 0;
  190. bottom: 0;
  191. display: flex;
  192. flex-direction: row; // 修改为水平布局
  193. }
  194. .bg-image-container {
  195. flex: 3; // 背景图占比为 3
  196. background-size: cover;
  197. background-position: center;
  198. }
  199. .login-wrapper {
  200. flex: 1; // 登录框占比为 1
  201. background: linear-gradient(to bottom, #001169, #8a78d0);
  202. padding: 20px;
  203. position: static;
  204. transform: none;
  205. display: flex; // 添加 Flexbox 布局
  206. justify-content: center; // 水平居中
  207. align-items: center; // 垂直居中
  208. }
  209. .login-input {
  210. width: rpx(190);
  211. height: rpx(240);
  212. display: flex;
  213. justify-content: center; // 水平居中
  214. align-items: center; // 垂直居中
  215. flex-direction: column; // 子元素垂直排列
  216. text-align: center; // 文本居中
  217. }
  218. .login-input span{
  219. color: white;
  220. font-size: rpx(11);
  221. padding-bottom: rpx(5);
  222. letter-spacing: rpx(1);
  223. }
  224. .input-item {
  225. display: flex;
  226. flex-direction: column; // 子元素垂直排列
  227. justify-content: center; // 内容垂直居中
  228. align-items: center; // 内容水平居中
  229. }
  230. .el-input ::v-deep(.el-input__wrapper){
  231. border-radius: rpx(5);
  232. }
  233. .input-item .el-form-item {
  234. margin-bottom: 0;
  235. }
  236. .input-item .el-input {
  237. width: rpx(150);
  238. height: rpx(22);
  239. margin-bottom: rpx(15);
  240. font-size: rpx(7);
  241. }
  242. .el-form-item ::v-deep(.el-form-item__error) {
  243. top: rpx(25);
  244. font-size: rpx(7);
  245. }
  246. .login-btn {
  247. width: rpx(150);
  248. height: rpx(22);
  249. color: black;
  250. font-size: rpx(8);
  251. letter-spacing: rpx(10);
  252. border-radius: rpx(5);
  253. margin: rpx(15) 0 auto;
  254. border: none;
  255. background: linear-gradient(to bottom, #fee78a, #ffce1b);
  256. box-shadow: 0 8px 8px rgb(0, 0, 0, 0.2);
  257. }
  258. .password-input {
  259. margin-bottom: rpx(0) !important;
  260. }
  261. .check-box {
  262. width: rpx(150);
  263. height: rpx(18);
  264. margin: rpx(5) auto;
  265. display: flex;
  266. justify-content: space-between;
  267. align-items: center;
  268. }
  269. .check-box .el-checkbox {
  270. color: white;
  271. // padding-right: rpx(10);
  272. font-size: rpx(6);
  273. }
  274. .check-box .register-link {
  275. color: white;
  276. font-size: rpx(6);
  277. text-decoration: none;
  278. margin-right: rpx(5);
  279. }
  280. .check-box .register-link:hover {
  281. text-decoration: underline;
  282. }
  283. .el-checkbox ::v-deep(.el-checkbox__label){
  284. font-size: rpx(6);
  285. }
  286. .check-box .forgot-password {
  287. color: white;
  288. font-size: rpx(6);
  289. text-decoration: none;
  290. }
  291. // 短信验证码容器样式
  292. .sms-code-container {
  293. display: flex;
  294. align-items: center;
  295. width: rpx(150);
  296. height: rpx(22);
  297. }
  298. .sms-input {
  299. flex: 1;
  300. height: rpx(22);
  301. margin-bottom: 0 !important;
  302. }
  303. .sms-input ::v-deep(.el-input__wrapper) {
  304. border-top-right-radius: 0;
  305. border-bottom-right-radius: 0;
  306. }
  307. .get-code-btn {
  308. width: rpx(40);
  309. height: rpx(22);
  310. margin: 0 !important;
  311. padding: 0;
  312. font-size: rpx(5);
  313. border-radius: rpx(5);
  314. border-top-left-radius: 0;
  315. border-bottom-left-radius: 0;
  316. letter-spacing: normal;
  317. display: flex;
  318. align-items: center;
  319. justify-content: center;
  320. background-color: #fff;
  321. color: #919191;
  322. // background: linear-gradient(to bottom, #78c0ff, #0070f3);
  323. border: none;
  324. }
  325. // 移动端响应式设计
  326. @media screen and (max-width: 768px) {
  327. .login-content {
  328. flex-direction: column;
  329. }
  330. .bg-image-container {
  331. position: fixed;
  332. top: 0;
  333. left: 0;
  334. width: 100%;
  335. height: 100%;
  336. z-index: -1;
  337. background-size: cover;
  338. background-position: center;
  339. }
  340. .login-wrapper {
  341. flex: none;
  342. width: 70%;
  343. margin: 0 auto;
  344. padding: 30px;
  345. background: rgba(0, 17, 105, 0.85);
  346. border-radius: 15px;
  347. position: absolute;
  348. top: 50%;
  349. left: 50%;
  350. transform: translate(-50%, -50%);
  351. min-height: 40%;
  352. overflow-y: auto;
  353. }
  354. .login-input {
  355. width: 100%;
  356. height: 100%;
  357. display: flex;
  358. flex-direction: column;
  359. justify-content: center;
  360. }
  361. .login-input span {
  362. font-size: rpx(18);
  363. padding-bottom: rpx(20);
  364. }
  365. .input-item {
  366. width: 100%;
  367. align-items: stretch;
  368. }
  369. .input-item .el-input {
  370. width: 100% !important;
  371. max-width: none;
  372. height: rpx(45) !important;
  373. font-size: rpx(14) !important;
  374. margin-bottom: rpx(25) !important;
  375. }
  376. .el-input ::v-deep(.el-input__wrapper) {
  377. height: rpx(45) !important;
  378. }
  379. .el-input ::v-deep(.el-input__inner) {
  380. height: rpx(45) !important;
  381. font-size: rpx(14) !important;
  382. }
  383. .login-btn {
  384. width: 100% !important;
  385. max-width: none;
  386. height: rpx(45) !important;
  387. font-size: rpx(16) !important;
  388. margin: rpx(25) 0 !important;
  389. letter-spacing: rpx(15);
  390. }
  391. .check-box {
  392. width: 100% !important;
  393. max-width: none;
  394. height: auto;
  395. margin: rpx(15) 0 !important;
  396. }
  397. .check-box .el-checkbox {
  398. font-size: rpx(12) !important;
  399. }
  400. .check-box .register-link {
  401. font-size: rpx(12) !important;
  402. margin-right: rpx(15) !important;
  403. }
  404. .el-checkbox ::v-deep(.el-checkbox__label) {
  405. font-size: rpx(12) !important;
  406. }
  407. .sms-code-container {
  408. width: 100% !important;
  409. max-width: none;
  410. height: rpx(45) !important;
  411. }
  412. .sms-input {
  413. width: calc(100% - rpx(75)) !important;
  414. height: rpx(45) !important;
  415. }
  416. .get-code-btn {
  417. width: rpx(75) !important;
  418. height: rpx(45) !important;
  419. font-size: rpx(10) !important;
  420. }
  421. .el-form-item ::v-deep(.el-form-item__error) {
  422. top: rpx(50) !important;
  423. font-size: rpx(10) !important;
  424. }
  425. }
  426. </style>