index.vue 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. <template>
  2. <div class="login">
  3. <LoginLeftView></LoginLeftView>
  4. <div class="right-wrap">
  5. <div class="top-right-wrap">
  6. <div class="btn theme-btn" @click="themeAnimation">
  7. <i class="iconfont-sys">
  8. {{ isDark ? '&#xe6b5;' : '&#xe725;' }}
  9. </i>
  10. </div>
  11. <ElDropdown @command="changeLanguage" popper-class="langDropDownStyle">
  12. <div class="btn language-btn">
  13. <i class="iconfont-sys icon-language">&#xe611;</i>
  14. </div>
  15. <template #dropdown>
  16. <ElDropdownMenu>
  17. <div v-for="lang in languageOptions" :key="lang.value" class="lang-btn-item">
  18. <ElDropdownItem
  19. :command="lang.value"
  20. :class="{ 'is-selected': locale === lang.value }"
  21. >
  22. <span class="menu-txt">{{ lang.label }}</span>
  23. <i v-if="locale === lang.value" class="iconfont-sys icon-check">&#xe621;</i>
  24. </ElDropdownItem>
  25. </div>
  26. </ElDropdownMenu>
  27. </template>
  28. </ElDropdown>
  29. </div>
  30. <div class="header">
  31. <ArtLogo class="icon" />
  32. <h1>{{ systemName }}</h1>
  33. </div>
  34. <div class="login-wrap">
  35. <div class="form">
  36. <h3 class="title">{{ $t('login.title') }}</h3>
  37. <p class="sub-title">{{ $t('login.subTitle') }}</p>
  38. <ElForm
  39. ref="formRef"
  40. :model="formData"
  41. :rules="rules"
  42. @keyup.enter="handleSubmit"
  43. style="margin-top: 25px"
  44. >
  45. <ElFormItem prop="username">
  46. <ElInput :placeholder="$t('login.placeholder[0]')" v-model.trim="formData.username" />
  47. </ElFormItem>
  48. <ElFormItem prop="password">
  49. <ElInput
  50. :placeholder="$t('login.placeholder[1]')"
  51. v-model.trim="formData.password"
  52. type="password"
  53. radius="8px"
  54. autocomplete="off"
  55. show-password
  56. />
  57. </ElFormItem>
  58. <div class="drag-verify">
  59. <div class="drag-verify-content" :class="{ error: !isPassing && isClickPass }">
  60. <ArtDragVerify
  61. ref="dragVerify"
  62. v-model:value="isPassing"
  63. :text="$t('login.sliderText')"
  64. textColor="var(--art-gray-800)"
  65. :successText="$t('login.sliderSuccessText')"
  66. :progressBarBg="getCssVar('--el-color-primary')"
  67. background="var(--art-gray-200)"
  68. handlerBg="var(--art-main-bg-color)"
  69. />
  70. </div>
  71. <p class="error-text" :class="{ 'show-error-text': !isPassing && isClickPass }">{{
  72. $t('login.placeholder[2]')
  73. }}</p>
  74. </div>
  75. <div class="forget-password">
  76. <ElCheckbox v-model="formData.rememberPassword">{{
  77. $t('login.rememberPwd')
  78. }}</ElCheckbox>
  79. <RouterLink :to="RoutesAlias.ForgetPassword">{{ $t('login.forgetPwd') }}</RouterLink>
  80. </div>
  81. <div style="margin-top: 30px">
  82. <ElButton
  83. class="login-btn"
  84. type="primary"
  85. @click="handleSubmit"
  86. :loading="loading"
  87. v-ripple
  88. >
  89. {{ $t('login.btnText') }}
  90. </ElButton>
  91. </div>
  92. <!-- <div class="footer">-->
  93. <!-- <p>-->
  94. <!-- {{ $t('login.noAccount') }}-->
  95. <!-- <RouterLink :to="RoutesAlias.Register">{{ $t('login.register') }}</RouterLink>-->
  96. <!-- </p>-->
  97. <!-- </div>-->
  98. </ElForm>
  99. </div>
  100. </div>
  101. </div>
  102. </div>
  103. </template>
  104. <script setup lang="ts">
  105. import AppConfig from '@/config'
  106. import { RoutesAlias } from '@/router/routesAlias'
  107. import { ElNotification, ElMessage } from 'element-plus'
  108. import { useUserStore } from '@/store/modules/user'
  109. import { getCssVar } from '@/utils/ui'
  110. import { languageOptions } from '@/locales'
  111. import { LanguageEnum } from '@/enums/appEnum'
  112. import { useI18n } from 'vue-i18n'
  113. import { HttpError } from '@/utils/http/error'
  114. import { themeAnimation } from '@/utils/theme/animation'
  115. import { UserService } from '@/api/usersApi'
  116. defineOptions({ name: 'Login' })
  117. const { t } = useI18n()
  118. import { useSettingStore } from '@/store/modules/setting'
  119. import type { FormInstance, FormRules } from 'element-plus'
  120. const settingStore = useSettingStore()
  121. const { isDark } = storeToRefs(settingStore)
  122. const dragVerify = ref()
  123. const userStore = useUserStore()
  124. const router = useRouter()
  125. const isPassing = ref(false)
  126. const isClickPass = ref(false)
  127. const systemName = AppConfig.systemInfo.name
  128. const formRef = ref<FormInstance>()
  129. const formData = reactive({
  130. username: '',
  131. password: '',
  132. rememberPassword: true
  133. })
  134. const rules = computed<FormRules>(() => ({
  135. username: [{ required: true, message: t('login.placeholder[0]'), trigger: 'blur' }],
  136. password: [{ required: true, message: t('login.placeholder[1]'), trigger: 'blur' }]
  137. }))
  138. const loading = ref(false)
  139. // 登录
  140. const handleSubmit = async () => {
  141. if (!formRef.value) return
  142. try {
  143. // 表单验证
  144. const valid = await formRef.value.validate()
  145. if (!valid) return
  146. // 拖拽验证
  147. if (!isPassing.value) {
  148. isClickPass.value = true
  149. return
  150. }
  151. loading.value = true
  152. // 登录请求
  153. const { username, password } = formData
  154. const { token, refreshToken } = await UserService.login({
  155. username,
  156. password
  157. })
  158. // 验证token
  159. if (!token) {
  160. throw new Error('Login failed - no token received')
  161. }
  162. // 存储token和用户信息
  163. userStore.setToken(token, refreshToken)
  164. const userInfo = await UserService.getUserInfo()
  165. userStore.setUserInfo(userInfo)
  166. userStore.setLoginStatus(true)
  167. // 登录成功处理
  168. showLoginSuccessNotice()
  169. await router.push('/')
  170. } catch (error) {
  171. // 处理 HttpError
  172. if (error instanceof HttpError) {
  173. // console.log(error.code)
  174. } else {
  175. // 处理非 HttpError
  176. ElMessage.error('登录失败,请稍后重试')
  177. console.error('[Login] Unexpected error:', error)
  178. }
  179. } finally {
  180. loading.value = false
  181. resetDragVerify()
  182. }
  183. }
  184. // 重置拖拽验证
  185. const resetDragVerify = () => {
  186. dragVerify.value.reset()
  187. }
  188. // 登录成功提示
  189. const showLoginSuccessNotice = () => {
  190. setTimeout(() => {
  191. ElNotification({
  192. title: t('login.success.title'),
  193. type: 'success',
  194. duration: 2500,
  195. zIndex: 10000,
  196. message: `${t('login.success.message')}, ${userStore.info.username}!`
  197. })
  198. }, 150)
  199. }
  200. // 切换语言
  201. const { locale } = useI18n()
  202. const changeLanguage = (lang: LanguageEnum) => {
  203. if (locale.value === lang) return
  204. locale.value = lang
  205. userStore.setLanguage(lang)
  206. }
  207. </script>
  208. <style lang="scss" scoped>
  209. @use './index';
  210. </style>