login.vue 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. <template>
  2. <div class="login">
  3. <el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
  4. <h3 class="title">{{ title }}</h3>
  5. <div class="language-select-wrapper">
  6. <label class="language-label">{{ t('common.language') }}</label>
  7. <lang-select :reload="false" size="large" style="width: 100%;" />
  8. </div>
  9. <el-form-item prop="username">
  10. <el-input
  11. v-model="loginForm.username"
  12. type="text"
  13. size="large"
  14. auto-complete="off"
  15. :placeholder="t('login.username')"
  16. >
  17. <template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
  18. </el-input>
  19. </el-form-item>
  20. <el-form-item prop="password">
  21. <el-input
  22. v-model="loginForm.password"
  23. type="password"
  24. size="large"
  25. auto-complete="off"
  26. :placeholder="t('login.password')"
  27. @keyup.enter="handleLogin"
  28. >
  29. <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
  30. </el-input>
  31. </el-form-item>
  32. <el-form-item prop="code" v-if="captchaEnabled">
  33. <el-input
  34. v-model="loginForm.code"
  35. size="large"
  36. auto-complete="off"
  37. :placeholder="t('login.code')"
  38. style="width: 63%"
  39. @keyup.enter="handleLogin"
  40. >
  41. <template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
  42. </el-input>
  43. <div class="login-code">
  44. <img :src="codeUrl" @click="getCode" class="login-code-img"/>
  45. </div>
  46. </el-form-item>
  47. <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">{{ t('login.rememberMe') }}</el-checkbox>
  48. <el-form-item style="width:100%;">
  49. <el-button
  50. :loading="loading"
  51. size="large"
  52. type="primary"
  53. style="width:100%;"
  54. @click.prevent="handleLogin"
  55. >
  56. <span v-if="!loading">{{ t('login.login') }}</span>
  57. <span v-else>{{ t('login.logging') }}</span>
  58. </el-button>
  59. <div style="float: right;" v-if="register">
  60. <router-link class="link-type" :to="'/register'">立即注册</router-link>
  61. </div>
  62. </el-form-item>
  63. </el-form>
  64. <!-- 底部 -->
  65. <div class="el-login-footer">
  66. <span>Copyright © 2018-2025 ruoyi.vip All Rights Reserved.</span>
  67. </div>
  68. </div>
  69. </template>
  70. <script setup>
  71. import { getCodeImg } from "@/api/login"
  72. import Cookies from "js-cookie"
  73. import { encrypt, decrypt } from "@/utils/jsencrypt"
  74. import useUserStore from '@/store/modules/user'
  75. import { useI18n } from '@/composables/useI18n'
  76. import LangSelect from '@/components/LangSelect'
  77. import { ref, watch, computed } from 'vue'
  78. const { t, locale } = useI18n()
  79. const title = computed(() => t('login.title'))
  80. const userStore = useUserStore()
  81. const route = useRoute()
  82. const router = useRouter()
  83. const { proxy } = getCurrentInstance()
  84. const loginForm = ref({
  85. username: "",
  86. password: "",
  87. rememberMe: false,
  88. code: "",
  89. uuid: ""
  90. })
  91. const loginRules = ref({
  92. username: [{ required: true, trigger: "blur", message: t('login.pleaseEnterUsername') }],
  93. password: [{ required: true, trigger: "blur", message: t('login.pleaseEnterPassword') }],
  94. code: [{ required: true, trigger: "change", message: t('login.pleaseEnterCode') }]
  95. })
  96. // 监听语言变化,更新验证规则
  97. watch(locale, () => {
  98. loginRules.value = {
  99. username: [{ required: true, trigger: "blur", message: t('login.pleaseEnterUsername') }],
  100. password: [{ required: true, trigger: "blur", message: t('login.pleaseEnterPassword') }],
  101. code: [{ required: true, trigger: "change", message: t('login.pleaseEnterCode') }]
  102. }
  103. })
  104. const codeUrl = ref("")
  105. const loading = ref(false)
  106. // 验证码开关
  107. const captchaEnabled = ref(true)
  108. // 注册开关
  109. const register = ref(false)
  110. const redirect = ref(undefined)
  111. watch(route, (newRoute) => {
  112. redirect.value = newRoute.query && newRoute.query.redirect
  113. }, { immediate: true })
  114. function handleLogin() {
  115. proxy.$refs.loginRef.validate(valid => {
  116. if (valid) {
  117. loading.value = true
  118. // 勾选了需要记住密码设置在 cookie 中设置记住用户名和密码
  119. if (loginForm.value.rememberMe) {
  120. Cookies.set("username", loginForm.value.username, { expires: 30 })
  121. Cookies.set("password", encrypt(loginForm.value.password), { expires: 30 })
  122. Cookies.set("rememberMe", loginForm.value.rememberMe, { expires: 30 })
  123. } else {
  124. // 否则移除
  125. Cookies.remove("username")
  126. Cookies.remove("password")
  127. Cookies.remove("rememberMe")
  128. }
  129. // 调用action的登录方法
  130. userStore.login(loginForm.value).then(() => {
  131. // 登录成功后获取用户信息
  132. return userStore.getInfo()
  133. }).then(() => {
  134. // 获取用户信息成功后,跳转到目标页面
  135. // 路由守卫会自动处理路由加载
  136. const redirectPath = route.query.redirect || '/user/myuser'
  137. router.push(redirectPath).catch(() => {
  138. // 如果跳转失败,可能是路由还未加载,等待一下再跳转
  139. setTimeout(() => {
  140. router.push(redirectPath)
  141. }, 100)
  142. })
  143. }).catch((error) => {
  144. console.error('登录失败:', error)
  145. loading.value = false
  146. // 重新获取验证码
  147. if (captchaEnabled.value) {
  148. getCode()
  149. }
  150. })
  151. }
  152. })
  153. }
  154. function getCode() {
  155. getCodeImg().then(res => {
  156. captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled
  157. if (captchaEnabled.value) {
  158. codeUrl.value = "data:image/gif;base64," + res.img
  159. loginForm.value.uuid = res.uuid
  160. }
  161. })
  162. }
  163. function getCookie() {
  164. const username = Cookies.get("username")
  165. const password = Cookies.get("password")
  166. const rememberMe = Cookies.get("rememberMe")
  167. loginForm.value = {
  168. username: username === undefined ? loginForm.value.username : username,
  169. password: password === undefined ? loginForm.value.password : decrypt(password),
  170. rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
  171. }
  172. }
  173. getCode()
  174. getCookie()
  175. </script>
  176. <style lang='scss' scoped>
  177. .login {
  178. display: flex;
  179. justify-content: center;
  180. align-items: center;
  181. height: 100%;
  182. background-image: url("../assets/images/background.png");
  183. background-size: cover;
  184. }
  185. .title {
  186. margin: 0px auto 30px auto;
  187. text-align: center;
  188. color: #707070;
  189. }
  190. .login-form {
  191. border-radius: 6px;
  192. background: #ffffff;
  193. width: 400px;
  194. padding: 25px 25px 5px 25px;
  195. z-index: 1;
  196. .el-input {
  197. height: 40px;
  198. input {
  199. height: 40px;
  200. }
  201. }
  202. .input-icon {
  203. height: 39px;
  204. width: 14px;
  205. margin-left: 0px;
  206. }
  207. }
  208. .language-select-wrapper {
  209. margin-bottom: 20px;
  210. .language-label {
  211. display: block;
  212. margin-bottom: 8px;
  213. font-size: 14px;
  214. color: #606266;
  215. font-weight: normal;
  216. }
  217. }
  218. .login-tip {
  219. font-size: 13px;
  220. text-align: center;
  221. color: #bfbfbf;
  222. }
  223. .login-code {
  224. width: 33%;
  225. height: 40px;
  226. float: right;
  227. img {
  228. cursor: pointer;
  229. vertical-align: middle;
  230. }
  231. }
  232. .el-login-footer {
  233. height: 40px;
  234. line-height: 40px;
  235. position: fixed;
  236. bottom: 0;
  237. width: 100%;
  238. text-align: center;
  239. color: #fff;
  240. font-family: Arial;
  241. font-size: 12px;
  242. letter-spacing: 1px;
  243. }
  244. .login-code-img {
  245. height: 40px;
  246. padding-left: 12px;
  247. }
  248. </style>