BlocklyLogin.vue 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  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-show="!tenantNameQuery"
  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. <el-checkbox
  83. v-model="loginData.loginForm.rememberMe"
  84. label="记住我"
  85. size="large"
  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. 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 tenantNameQuery = ref()
  119. // 授权状态 默认授权
  120. const isAuthorized = ref(true)
  121. // 生成表单验证规则
  122. const rules = generateRules(isAuthorized)
  123. // 获取短信验证码
  124. const handleGetSmsCode = async () => {
  125. // 先验证租户和手机号是否填写
  126. if (!loginData.value.loginForm.tenantName) {
  127. ElMessage.warning('请先输入学校名称')
  128. return
  129. }
  130. if (!loginData.value.loginForm.phoneNumber) {
  131. ElMessage.warning('请先输入手机号')
  132. return
  133. }
  134. // 验证租户是否存在
  135. const tenantId = await getTenantId(loginData.value.loginForm.tenantName)
  136. if (!tenantId) {
  137. return
  138. }
  139. // 调用验证码逻辑
  140. getSmsCode(tenantId, loginData.value.loginForm.tenantName, loginData.value.loginForm.phoneNumber)
  141. }
  142. // 登录
  143. const handleLogin = async () => {
  144. if (!loginFormRef.value) return
  145. await loginFormRef.value.validate(async valid => {
  146. if (valid) {
  147. // 先验证租户是否存在
  148. const tenantId = await getTenantId(loginData.value.loginForm.tenantName)
  149. if (!tenantId) {
  150. // 租户验证失败,不执行登录
  151. return
  152. }
  153. // 调用登录逻辑
  154. await loginLogic(loginData.value.loginForm, tenantId, isAuthorized, router, '/blocklyHome')
  155. }
  156. })
  157. }
  158. // 在组件挂载时检查登录状态和恢复登录信息
  159. onMounted(() => {
  160. // 加载本地存储的登录数据
  161. loadLoginData(loginData)
  162. // 检查地址栏是否有tenantName参数
  163. let tenantName = router.currentRoute.value.meta?.TENANT;
  164. if (tenantName && tenantName === "内部测试租户") {
  165. loginData.value.loginForm.tenantName = tenantNameQuery.value = tenantName
  166. } else if (Object.keys(router.currentRoute.value.query).length > 0) {
  167. // 其他参数,重定向到登录页
  168. router.replace('/blockly-login')
  169. }
  170. // 检查登录状态,如果已登录则直接跳转到首页
  171. checkLoginStatus(router, '/blocklyHome')
  172. const handleKeyPress = (event) => {
  173. // 检查是否按下回车键(keyCode 13)
  174. if (event.key === 'Enter' || event.keyCode === 13) {
  175. handleLogin()
  176. }
  177. }
  178. document.addEventListener('keydown', handleKeyPress)
  179. // 在组件卸载时移除事件监听
  180. onUnmounted(() => {
  181. document.removeEventListener('keydown', handleKeyPress)
  182. clearCountDownTimer()
  183. })
  184. })
  185. </script>
  186. <style scoped lang="scss">
  187. @use 'sass:math';
  188. // 定义rpx转换函数
  189. @function rpx($px) {
  190. @return math.div($px, 750) * 100vw;
  191. }
  192. .login-content {
  193. position: fixed;
  194. top: 0;
  195. left: 0;
  196. right: 0;
  197. bottom: 0;
  198. display: flex;
  199. flex-direction: row; // 修改为水平布局
  200. }
  201. .bg-image-container {
  202. flex: 3; // 背景图占比为 3
  203. background-size: cover;
  204. background-position: center;
  205. }
  206. .login-wrapper {
  207. flex: 1; // 登录框占比为 1
  208. background: linear-gradient(to bottom, #001169, #8a78d0);
  209. padding: 20px;
  210. position: static;
  211. transform: none;
  212. display: flex; // 添加 Flexbox 布局
  213. justify-content: center; // 水平居中
  214. align-items: center; // 垂直居中
  215. }
  216. .login-input {
  217. width: rpx(190);
  218. height: rpx(240);
  219. display: flex;
  220. justify-content: center; // 水平居中
  221. align-items: center; // 垂直居中
  222. flex-direction: column; // 子元素垂直排列
  223. text-align: center; // 文本居中
  224. }
  225. .login-input span{
  226. color: white;
  227. font-size: rpx(11);
  228. padding-bottom: rpx(5);
  229. letter-spacing: rpx(1);
  230. }
  231. .input-item {
  232. display: flex;
  233. flex-direction: column; // 子元素垂直排列
  234. justify-content: center; // 内容垂直居中
  235. align-items: center; // 内容水平居中
  236. }
  237. .el-input ::v-deep(.el-input__wrapper){
  238. border-radius: rpx(5);
  239. }
  240. .input-item .el-form-item {
  241. margin-bottom: 0;
  242. }
  243. .input-item .el-input {
  244. width: rpx(150);
  245. height: rpx(22);
  246. margin-bottom: rpx(15);
  247. font-size: rpx(7);
  248. }
  249. .el-form-item ::v-deep(.el-form-item__error) {
  250. top: rpx(25);
  251. }
  252. .login-btn {
  253. width: rpx(150);
  254. height: rpx(22);
  255. color: black;
  256. font-size: rpx(8);
  257. letter-spacing: rpx(10);
  258. border-radius: rpx(5);
  259. margin: rpx(15) 0 auto;
  260. border: none;
  261. background: linear-gradient(to bottom, #fee78a, #ffce1b);
  262. box-shadow: 0 8px 8px rgb(0, 0, 0, 0.2);
  263. }
  264. .password-input {
  265. margin-bottom: rpx(0) !important;
  266. }
  267. .check-box {
  268. width: rpx(150);
  269. height: rpx(18);
  270. margin: rpx(5) auto;
  271. display: flex;
  272. justify-content: flex-end;
  273. align-items: center;
  274. }
  275. .check-box .el-checkbox {
  276. color: white;
  277. padding-right: rpx(10);
  278. font-size: rpx(6);
  279. }
  280. .el-checkbox ::v-deep(.el-checkbox__label){
  281. font-size: rpx(6);
  282. }
  283. .check-box .forgot-password {
  284. color: white;
  285. font-size: rpx(6);
  286. text-decoration: none;
  287. }
  288. // 短信验证码容器样式
  289. .sms-code-container {
  290. display: flex;
  291. align-items: center;
  292. width: rpx(150);
  293. height: rpx(22);
  294. }
  295. .sms-input {
  296. flex: 1;
  297. height: rpx(22);
  298. margin-bottom: 0 !important;
  299. }
  300. .sms-input ::v-deep(.el-input__wrapper) {
  301. border-top-right-radius: 0;
  302. border-bottom-right-radius: 0;
  303. }
  304. .get-code-btn {
  305. width: rpx(40);
  306. height: rpx(22);
  307. margin: 0 !important;
  308. padding: 0;
  309. font-size: rpx(5);
  310. border-radius: rpx(5);
  311. border-top-left-radius: 0;
  312. border-bottom-left-radius: 0;
  313. letter-spacing: normal;
  314. display: flex;
  315. align-items: center;
  316. justify-content: center;
  317. background-color: #fff;
  318. color: #919191;
  319. // background: linear-gradient(to bottom, #78c0ff, #0070f3);
  320. border: none;
  321. }
  322. </style>