| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- <template>
- <div class="login">
- <LoginLeftView></LoginLeftView>
- <div class="right-wrap">
- <div class="top-right-wrap">
- <div class="btn theme-btn" @click="themeAnimation">
- <i class="iconfont-sys">
- {{ isDark ? '' : '' }}
- </i>
- </div>
- <ElDropdown @command="changeLanguage" popper-class="langDropDownStyle">
- <div class="btn language-btn">
- <i class="iconfont-sys icon-language"></i>
- </div>
- <template #dropdown>
- <ElDropdownMenu>
- <div v-for="lang in languageOptions" :key="lang.value" class="lang-btn-item">
- <ElDropdownItem
- :command="lang.value"
- :class="{ 'is-selected': locale === lang.value }"
- >
- <span class="menu-txt">{{ lang.label }}</span>
- <i v-if="locale === lang.value" class="iconfont-sys icon-check"></i>
- </ElDropdownItem>
- </div>
- </ElDropdownMenu>
- </template>
- </ElDropdown>
- </div>
- <div class="header">
- <ArtLogo class="icon" />
- <h1>{{ systemName }}</h1>
- </div>
- <div class="login-wrap">
- <div class="form">
- <h3 class="title">{{ $t('login.title') }}</h3>
- <p class="sub-title">{{ $t('login.subTitle') }}</p>
- <ElForm
- ref="formRef"
- :model="formData"
- :rules="rules"
- @keyup.enter="handleSubmit"
- style="margin-top: 25px"
- >
- <ElFormItem prop="username">
- <ElInput :placeholder="$t('login.placeholder[0]')" v-model.trim="formData.username" />
- </ElFormItem>
- <ElFormItem prop="password">
- <ElInput
- :placeholder="$t('login.placeholder[1]')"
- v-model.trim="formData.password"
- type="password"
- radius="8px"
- autocomplete="off"
- show-password
- />
- </ElFormItem>
- <div class="drag-verify">
- <div class="drag-verify-content" :class="{ error: !isPassing && isClickPass }">
- <ArtDragVerify
- ref="dragVerify"
- v-model:value="isPassing"
- :text="$t('login.sliderText')"
- textColor="var(--art-gray-800)"
- :successText="$t('login.sliderSuccessText')"
- :progressBarBg="getCssVar('--el-color-primary')"
- background="var(--art-gray-200)"
- handlerBg="var(--art-main-bg-color)"
- />
- </div>
- <p class="error-text" :class="{ 'show-error-text': !isPassing && isClickPass }">{{
- $t('login.placeholder[2]')
- }}</p>
- </div>
- <div class="forget-password">
- <ElCheckbox v-model="formData.rememberPassword">{{
- $t('login.rememberPwd')
- }}</ElCheckbox>
- <RouterLink :to="RoutesAlias.ForgetPassword">{{ $t('login.forgetPwd') }}</RouterLink>
- </div>
- <div style="margin-top: 30px">
- <ElButton
- class="login-btn"
- type="primary"
- @click="handleSubmit"
- :loading="loading"
- v-ripple
- >
- {{ $t('login.btnText') }}
- </ElButton>
- </div>
- <!-- <div class="footer">-->
- <!-- <p>-->
- <!-- {{ $t('login.noAccount') }}-->
- <!-- <RouterLink :to="RoutesAlias.Register">{{ $t('login.register') }}</RouterLink>-->
- <!-- </p>-->
- <!-- </div>-->
- </ElForm>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import AppConfig from '@/config'
- import { RoutesAlias } from '@/router/routesAlias'
- import { ElNotification, ElMessage } from 'element-plus'
- import { useUserStore } from '@/store/modules/user'
- import { getCssVar } from '@/utils/ui'
- import { languageOptions } from '@/locales'
- import { LanguageEnum } from '@/enums/appEnum'
- import { useI18n } from 'vue-i18n'
- import { HttpError } from '@/utils/http/error'
- import { themeAnimation } from '@/utils/theme/animation'
- import { UserService } from '@/api/usersApi'
- defineOptions({ name: 'Login' })
- const { t } = useI18n()
- import { useSettingStore } from '@/store/modules/setting'
- import type { FormInstance, FormRules } from 'element-plus'
- const settingStore = useSettingStore()
- const { isDark } = storeToRefs(settingStore)
- const dragVerify = ref()
- const userStore = useUserStore()
- const router = useRouter()
- const isPassing = ref(false)
- const isClickPass = ref(false)
- const systemName = AppConfig.systemInfo.name
- const formRef = ref<FormInstance>()
- const formData = reactive({
- username: '',
- password: '',
- rememberPassword: true
- })
- const rules = computed<FormRules>(() => ({
- username: [{ required: true, message: t('login.placeholder[0]'), trigger: 'blur' }],
- password: [{ required: true, message: t('login.placeholder[1]'), trigger: 'blur' }]
- }))
- const loading = ref(false)
- // 登录
- const handleSubmit = async () => {
- if (!formRef.value) return
- try {
- // 表单验证
- const valid = await formRef.value.validate()
- if (!valid) return
- // 拖拽验证
- if (!isPassing.value) {
- isClickPass.value = true
- return
- }
- loading.value = true
- // 登录请求
- const { username, password } = formData
- const { token, refreshToken } = await UserService.login({
- username,
- password
- })
- // 验证token
- if (!token) {
- throw new Error('Login failed - no token received')
- }
- // 存储token和用户信息
- userStore.setToken(token, refreshToken)
- const userInfo = await UserService.getUserInfo()
- userStore.setUserInfo(userInfo)
- userStore.setLoginStatus(true)
- // 登录成功处理
- showLoginSuccessNotice()
- await router.push('/')
- } catch (error) {
- // 处理 HttpError
- if (error instanceof HttpError) {
- // console.log(error.code)
- } else {
- // 处理非 HttpError
- ElMessage.error('登录失败,请稍后重试')
- console.error('[Login] Unexpected error:', error)
- }
- } finally {
- loading.value = false
- resetDragVerify()
- }
- }
- // 重置拖拽验证
- const resetDragVerify = () => {
- dragVerify.value.reset()
- }
- // 登录成功提示
- const showLoginSuccessNotice = () => {
- setTimeout(() => {
- ElNotification({
- title: t('login.success.title'),
- type: 'success',
- duration: 2500,
- zIndex: 10000,
- message: `${t('login.success.message')}, ${userStore.info.username}!`
- })
- }, 150)
- }
- // 切换语言
- const { locale } = useI18n()
- const changeLanguage = (lang: LanguageEnum) => {
- if (locale.value === lang) return
- locale.value = lang
- userStore.setLanguage(lang)
- }
- </script>
- <style lang="scss" scoped>
- @use './index';
- </style>
|