mirror of
				https://github.com/continew-org/continew-admin.git
				synced 2025-10-31 10:57:13 +08:00 
			
		
		
		
	refactor: 重构登录页面 UI 以适配多因子认证、第三方登录等场景
This commit is contained in:
		| @@ -5,6 +5,7 @@ import { UserState } from '@/store/modules/login/types'; | |||||||
| const BASE_URL = '/auth'; | const BASE_URL = '/auth'; | ||||||
|  |  | ||||||
| export interface LoginReq { | export interface LoginReq { | ||||||
|  |   phone?: string; | ||||||
|   username: string; |   username: string; | ||||||
|   password: string; |   password: string; | ||||||
|   captcha: string; |   captcha: string; | ||||||
|   | |||||||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 245 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 257 KiB | 
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 353 KiB | 
							
								
								
									
										
											BIN
										
									
								
								continew-admin-ui/src/assets/images/login/login-bg.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								continew-admin-ui/src/assets/images/login/login-bg.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 551 KiB | 
| @@ -11,7 +11,7 @@ export default function useUser() { | |||||||
|   const logout = async (logoutTo?: string) => { |   const logout = async (logoutTo?: string) => { | ||||||
|     await loginStore.logout(); |     await loginStore.logout(); | ||||||
|     const currentRoute = router.currentRoute.value; |     const currentRoute = router.currentRoute.value; | ||||||
|     Notification.success(t('login.form.logout.success')); |     Notification.success(t('login.logout.success')); | ||||||
|     router.push({ |     router.push({ | ||||||
|       name: logoutTo && typeof logoutTo === 'string' ? logoutTo : 'login', |       name: logoutTo && typeof logoutTo === 'string' ? logoutTo : 'login', | ||||||
|       query: { |       query: { | ||||||
|   | |||||||
							
								
								
									
										210
									
								
								continew-admin-ui/src/views/login/components/account-login.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								continew-admin-ui/src/views/login/components/account-login.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,210 @@ | |||||||
|  | <template> | ||||||
|  |   <a-form | ||||||
|  |     ref="formRef" | ||||||
|  |     :model="form" | ||||||
|  |     :rules="rules" | ||||||
|  |     layout="vertical" | ||||||
|  |     size="large" | ||||||
|  |     class="login-form" | ||||||
|  |     @submit="handleLogin" | ||||||
|  |   > | ||||||
|  |     <a-form-item field="username" hide-label> | ||||||
|  |       <a-input | ||||||
|  |         v-model="form.username" | ||||||
|  |         :placeholder="$t('login.account.placeholder.username')" | ||||||
|  |         :max-length="64" | ||||||
|  |         allow-clear | ||||||
|  |       /> | ||||||
|  |     </a-form-item> | ||||||
|  |     <a-form-item field="password" hide-label> | ||||||
|  |       <a-input-password | ||||||
|  |         v-model="form.password" | ||||||
|  |         :placeholder="$t('login.account.placeholder.password')" | ||||||
|  |         :max-length="32" | ||||||
|  |       /> | ||||||
|  |     </a-form-item> | ||||||
|  |     <a-form-item field="captcha" hide-label> | ||||||
|  |       <a-input | ||||||
|  |         v-model="form.captcha" | ||||||
|  |         :placeholder="$t('login.account.placeholder.captcha')" | ||||||
|  |         :max-length="4" | ||||||
|  |         allow-clear | ||||||
|  |         style="flex: 1 1" | ||||||
|  |       /> | ||||||
|  |       <img | ||||||
|  |         :src="captchaImgBase64" | ||||||
|  |         :alt="$t('login.captcha')" | ||||||
|  |         class="captcha" | ||||||
|  |         @click="getCaptcha" | ||||||
|  |       /> | ||||||
|  |     </a-form-item> | ||||||
|  |     <div class="remember-me"> | ||||||
|  |       <a-checkbox | ||||||
|  |         :model-value="loginConfig.rememberMe" | ||||||
|  |         @change="setRememberMe as any" | ||||||
|  |       > | ||||||
|  |         {{ $t('login.rememberMe') }} | ||||||
|  |       </a-checkbox> | ||||||
|  |     </div> | ||||||
|  |     <a-button class="btn" :loading="loading" type="primary" html-type="submit" | ||||||
|  |       >{{ $t('login.button') }} | ||||||
|  |     </a-button> | ||||||
|  |   </a-form> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts" setup> | ||||||
|  |   import { getCurrentInstance, ref, toRefs, reactive } from 'vue'; | ||||||
|  |   import { useI18n } from 'vue-i18n'; | ||||||
|  |   import { useStorage } from '@vueuse/core'; | ||||||
|  |   import { useLoginStore } from '@/store'; | ||||||
|  |   import { LoginReq } from '@/api/auth/login'; | ||||||
|  |   import { ValidatedError } from '@arco-design/web-vue'; | ||||||
|  |   import { encryptByRsa } from '@/utils/encrypt'; | ||||||
|  |   import { useRouter } from 'vue-router'; | ||||||
|  |  | ||||||
|  |   const { proxy } = getCurrentInstance() as any; | ||||||
|  |   const { t } = useI18n(); | ||||||
|  |   const router = useRouter(); | ||||||
|  |   const loginStore = useLoginStore(); | ||||||
|  |   const loading = ref(false); | ||||||
|  |   const captchaImgBase64 = ref(); | ||||||
|  |   const loginConfig = useStorage('login-config', { | ||||||
|  |     rememberMe: true, | ||||||
|  |     username: 'admin', // 演示默认值 | ||||||
|  |     password: 'admin123', // 演示默认值 | ||||||
|  |     // username: debug ? 'admin' : '', // 演示默认值 | ||||||
|  |     // password: debug ? 'admin123' : '', // 演示默认值 | ||||||
|  |   }); | ||||||
|  |   const data = reactive({ | ||||||
|  |     form: { | ||||||
|  |       username: loginConfig.value.username, | ||||||
|  |       password: loginConfig.value.password, | ||||||
|  |       captcha: '', | ||||||
|  |       uuid: '', | ||||||
|  |     } as LoginReq, | ||||||
|  |     rules: { | ||||||
|  |       username: [ | ||||||
|  |         { required: true, message: t('login.account.error.required.username') }, | ||||||
|  |       ], | ||||||
|  |       password: [ | ||||||
|  |         { required: true, message: t('login.account.error.required.password') }, | ||||||
|  |       ], | ||||||
|  |       captcha: [ | ||||||
|  |         { required: true, message: t('login.account.error.required.captcha') }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  |   const { form, rules } = toRefs(data); | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 获取验证码 | ||||||
|  |    */ | ||||||
|  |   const getCaptcha = () => { | ||||||
|  |     loginStore.getImgCaptcha().then((res) => { | ||||||
|  |       form.value.uuid = res.data.uuid; | ||||||
|  |       captchaImgBase64.value = res.data.img; | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  |   getCaptcha(); | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 登录 | ||||||
|  |    * | ||||||
|  |    * @param errors 表单验证错误 | ||||||
|  |    * @param values 表单数据 | ||||||
|  |    */ | ||||||
|  |   const handleLogin = ({ | ||||||
|  |     errors, | ||||||
|  |     values, | ||||||
|  |   }: { | ||||||
|  |     errors: Record<string, ValidatedError> | undefined; | ||||||
|  |     values: Record<string, any>; | ||||||
|  |   }) => { | ||||||
|  |     if (loading.value) return; | ||||||
|  |     if (!errors) { | ||||||
|  |       loading.value = true; | ||||||
|  |       loginStore | ||||||
|  |         .login({ | ||||||
|  |           username: values.username, | ||||||
|  |           password: encryptByRsa(values.password) || '', | ||||||
|  |           captcha: values.captcha, | ||||||
|  |           uuid: values.uuid, | ||||||
|  |         }) | ||||||
|  |         .then(() => { | ||||||
|  |           const { redirect, ...othersQuery } = router.currentRoute.value.query; | ||||||
|  |           router.push({ | ||||||
|  |             name: (redirect as string) || 'Workplace', | ||||||
|  |             query: { | ||||||
|  |               ...othersQuery, | ||||||
|  |             }, | ||||||
|  |           }); | ||||||
|  |           const { rememberMe } = loginConfig.value; | ||||||
|  |           const { username } = values; | ||||||
|  |           loginConfig.value.username = rememberMe ? username : ''; | ||||||
|  |           proxy.$notification.success(t('login.success')); | ||||||
|  |         }) | ||||||
|  |         .catch(() => { | ||||||
|  |           getCaptcha(); | ||||||
|  |           form.value.captcha = ''; | ||||||
|  |         }) | ||||||
|  |         .finally(() => { | ||||||
|  |           loading.value = false; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 记住我 | ||||||
|  |    * | ||||||
|  |    * @param value 是否记住我 | ||||||
|  |    */ | ||||||
|  |   const setRememberMe = (value: boolean) => { | ||||||
|  |     loginConfig.value.rememberMe = value; | ||||||
|  |   }; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="less" scoped> | ||||||
|  |   .login-form { | ||||||
|  |     box-sizing: border-box; | ||||||
|  |     padding: 0 5px; | ||||||
|  |     margin-top: 16px; | ||||||
|  |     .arco-input-wrapper, | ||||||
|  |     :deep(.arco-select-view-single) { | ||||||
|  |       background-color: var(--color-bg-white); | ||||||
|  |       border: 1px solid var(--color-border-3); | ||||||
|  |       height: 40px; | ||||||
|  |       border-radius: 4px; | ||||||
|  |       font-size: 13px; | ||||||
|  |     } | ||||||
|  |     .arco-input-wrapper.arco-input-error { | ||||||
|  |       background-color: var(--color-danger-light-1); | ||||||
|  |       border-color: var(--color-danger-light-4); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     .captcha { | ||||||
|  |       width: 111px; | ||||||
|  |       height: 36px; | ||||||
|  |       margin: 0 0 0 5px; | ||||||
|  |       cursor: pointer; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     .remember-me { | ||||||
|  |       display: flex; | ||||||
|  |       justify-content: space-between; | ||||||
|  |       .arco-checkbox { | ||||||
|  |         padding-left: 0; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     .btn { | ||||||
|  |       border-radius: 4px; | ||||||
|  |       box-shadow: 0 0 0 1px #05f, 0 2px 1px rgba(0, 0, 0, 0.15); | ||||||
|  |       font-size: 14px; | ||||||
|  |       font-weight: 500; | ||||||
|  |       height: 40px; | ||||||
|  |       line-height: 22px; | ||||||
|  |       margin: 20px 0 12px; | ||||||
|  |       width: 100%; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | </style> | ||||||
| @@ -1,87 +0,0 @@ | |||||||
| <template> |  | ||||||
|   <div class="banner"> |  | ||||||
|     <div class="banner-inner"> |  | ||||||
|       <a-carousel class="carousel" animation-name="fade"> |  | ||||||
|         <a-carousel-item v-for="item in carouselItem" :key="item.slogan"> |  | ||||||
|           <div :key="item.slogan" class="carousel-item"> |  | ||||||
|             <div class="carousel-title">{{ item.slogan }}</div> |  | ||||||
|             <div class="carousel-sub-title">{{ item.subSlogan }}</div> |  | ||||||
|             <img class="carousel-image" :src="item.image" /> |  | ||||||
|           </div> |  | ||||||
|         </a-carousel-item> |  | ||||||
|       </a-carousel> |  | ||||||
|     </div> |  | ||||||
|   </div> |  | ||||||
| </template> |  | ||||||
|  |  | ||||||
| <script lang="ts" setup> |  | ||||||
|   import { computed } from 'vue'; |  | ||||||
|   import { useI18n } from 'vue-i18n'; |  | ||||||
|   import bannerImage1 from '@/assets/images/login/banner1.png'; |  | ||||||
|   import bannerImage2 from '@/assets/images/login/banner2.png'; |  | ||||||
|   import bannerImage3 from '@/assets/images/login/banner3.png'; |  | ||||||
|  |  | ||||||
|   const { t } = useI18n(); |  | ||||||
|   const carouselItem = computed(() => [ |  | ||||||
|     { |  | ||||||
|       slogan: t('login.banner.slogan1'), |  | ||||||
|       subSlogan: t('login.banner.subSlogan1'), |  | ||||||
|       image: bannerImage1, |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       slogan: t('login.banner.slogan2'), |  | ||||||
|       subSlogan: t('login.banner.subSlogan2'), |  | ||||||
|       image: bannerImage2, |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       slogan: t('login.banner.slogan3'), |  | ||||||
|       subSlogan: t('login.banner.subSlogan3'), |  | ||||||
|       image: bannerImage3, |  | ||||||
|     }, |  | ||||||
|   ]); |  | ||||||
| </script> |  | ||||||
|  |  | ||||||
| <style lang="less" scoped> |  | ||||||
|   .banner { |  | ||||||
|     display: flex; |  | ||||||
|     align-items: center; |  | ||||||
|     justify-content: center; |  | ||||||
|  |  | ||||||
|     &-inner { |  | ||||||
|       flex: 1; |  | ||||||
|       height: 100%; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   .carousel { |  | ||||||
|     height: 100%; |  | ||||||
|  |  | ||||||
|     &-item { |  | ||||||
|       display: flex; |  | ||||||
|       flex-direction: column; |  | ||||||
|       align-items: center; |  | ||||||
|       justify-content: center; |  | ||||||
|       height: 100%; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     &-title { |  | ||||||
|       color: var(--color-fill-1); |  | ||||||
|       font-weight: 500; |  | ||||||
|       font-size: 20px; |  | ||||||
|       line-height: 28px; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     &-sub-title { |  | ||||||
|       margin-top: 8px; |  | ||||||
|       margin-left: 30px; |  | ||||||
|       color: var(--color-text-3); |  | ||||||
|       font-size: 14px; |  | ||||||
|       line-height: 22px; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     &-image { |  | ||||||
|       width: 360px; |  | ||||||
|       margin-top: 30px; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| </style> |  | ||||||
| @@ -1,216 +0,0 @@ | |||||||
| <template> |  | ||||||
|   <div class="login-form-wrapper"> |  | ||||||
|     <div class="login-form-title">登录 {{ appStore.getTitle }}</div> |  | ||||||
|     <a-form |  | ||||||
|       ref="formRef" |  | ||||||
|       :model="form" |  | ||||||
|       :rules="rules" |  | ||||||
|       layout="vertical" |  | ||||||
|       size="large" |  | ||||||
|       class="login-form" |  | ||||||
|       @submit="handleLogin" |  | ||||||
|     > |  | ||||||
|       <a-form-item field="username" hide-label> |  | ||||||
|         <a-input |  | ||||||
|           v-model="form.username" |  | ||||||
|           :placeholder="$t('login.form.placeholder.username')" |  | ||||||
|           :max-length="64" |  | ||||||
|         > |  | ||||||
|           <template #prefix><icon-user /></template> |  | ||||||
|         </a-input> |  | ||||||
|       </a-form-item> |  | ||||||
|       <a-form-item field="password" hide-label> |  | ||||||
|         <a-input-password |  | ||||||
|           v-model="form.password" |  | ||||||
|           :placeholder="$t('login.form.placeholder.password')" |  | ||||||
|           :max-length="32" |  | ||||||
|           allow-clear |  | ||||||
|         > |  | ||||||
|           <template #prefix><icon-lock /></template> |  | ||||||
|         </a-input-password> |  | ||||||
|       </a-form-item> |  | ||||||
|       <a-form-item class="login-form-captcha" field="captcha" hide-label> |  | ||||||
|         <a-input |  | ||||||
|           v-model="form.captcha" |  | ||||||
|           :placeholder="$t('login.form.placeholder.captcha')" |  | ||||||
|           :max-length="4" |  | ||||||
|           allow-clear |  | ||||||
|           style="width: 63%" |  | ||||||
|         > |  | ||||||
|           <template #prefix><icon-check-circle /></template> |  | ||||||
|         </a-input> |  | ||||||
|         <img |  | ||||||
|           :src="captchaImgBase64" |  | ||||||
|           :alt="$t('login.form.captcha')" |  | ||||||
|           @click="getCaptcha" |  | ||||||
|         /> |  | ||||||
|       </a-form-item> |  | ||||||
|       <a-space :size="16" direction="vertical"> |  | ||||||
|         <div class="login-form-remember-me"> |  | ||||||
|           <a-checkbox |  | ||||||
|             checked="rememberMe" |  | ||||||
|             :model-value="loginConfig.rememberMe" |  | ||||||
|             @change="setRememberMe as any" |  | ||||||
|           > |  | ||||||
|             {{ $t('login.form.rememberMe') }} |  | ||||||
|           </a-checkbox> |  | ||||||
|         </div> |  | ||||||
|         <a-button :loading="loading" type="primary" long html-type="submit"> |  | ||||||
|           {{ $t('login.form.login') }} |  | ||||||
|         </a-button> |  | ||||||
|       </a-space> |  | ||||||
|     </a-form> |  | ||||||
|   </div> |  | ||||||
| </template> |  | ||||||
|  |  | ||||||
| <script lang="ts" setup> |  | ||||||
|   import { getCurrentInstance, ref, toRefs, reactive, computed } from 'vue'; |  | ||||||
|   import { FieldRule, ValidatedError } from '@arco-design/web-vue'; |  | ||||||
|   import { LoginReq } from '@/api/auth/login'; |  | ||||||
|   import { useI18n } from 'vue-i18n'; |  | ||||||
|   import { useRouter } from 'vue-router'; |  | ||||||
|   import { useStorage } from '@vueuse/core'; |  | ||||||
|   import { useLoginStore, useAppStore } from '@/store'; |  | ||||||
|   import { encryptByRsa } from '@/utils/encrypt'; |  | ||||||
|   // import debug from '@/utils/env'; |  | ||||||
|  |  | ||||||
|   const { proxy } = getCurrentInstance() as any; |  | ||||||
|  |  | ||||||
|   const captchaImgBase64 = ref(''); |  | ||||||
|   const loginStore = useLoginStore(); |  | ||||||
|   const appStore = useAppStore(); |  | ||||||
|   const loading = ref(false); |  | ||||||
|   const { t } = useI18n(); |  | ||||||
|   const router = useRouter(); |  | ||||||
|   const loginConfig = useStorage('login-config', { |  | ||||||
|     rememberMe: true, |  | ||||||
|     username: 'admin', // 演示默认值 |  | ||||||
|     password: 'admin123', // 演示默认值 |  | ||||||
|     // username: debug ? 'admin' : '', // 演示默认值 |  | ||||||
|     // password: debug ? 'admin123' : '', // 演示默认值 |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   const data = reactive({ |  | ||||||
|     // 表单数据 |  | ||||||
|     form: { |  | ||||||
|       username: loginConfig.value.username, |  | ||||||
|       password: loginConfig.value.password, |  | ||||||
|       captcha: '', |  | ||||||
|       uuid: '', |  | ||||||
|     } as LoginReq, |  | ||||||
|     // 表单验证规则 |  | ||||||
|     rules: computed((): Record<string, FieldRule[]> => { |  | ||||||
|       return { |  | ||||||
|         username: [ |  | ||||||
|           { required: true, message: t('login.form.error.required.username') }, |  | ||||||
|         ], |  | ||||||
|         password: [ |  | ||||||
|           { required: true, message: t('login.form.error.required.password') }, |  | ||||||
|         ], |  | ||||||
|         captcha: [ |  | ||||||
|           { required: true, message: t('login.form.error.required.captcha') }, |  | ||||||
|         ], |  | ||||||
|       }; |  | ||||||
|     }), |  | ||||||
|   }); |  | ||||||
|   const { form, rules } = toRefs(data); |  | ||||||
|  |  | ||||||
|   /** |  | ||||||
|    * 获取验证码 |  | ||||||
|    */ |  | ||||||
|   const getCaptcha = () => { |  | ||||||
|     loginStore.getImgCaptcha().then((res) => { |  | ||||||
|       form.value.uuid = res.data.uuid; |  | ||||||
|       captchaImgBase64.value = res.data.img; |  | ||||||
|     }); |  | ||||||
|   }; |  | ||||||
|   getCaptcha(); |  | ||||||
|  |  | ||||||
|   /** |  | ||||||
|    * 登录 |  | ||||||
|    * |  | ||||||
|    * @param errors 表单验证错误 |  | ||||||
|    * @param values 表单数据 |  | ||||||
|    */ |  | ||||||
|   const handleLogin = ({ |  | ||||||
|     errors, |  | ||||||
|     values, |  | ||||||
|   }: { |  | ||||||
|     errors: Record<string, ValidatedError> | undefined; |  | ||||||
|     values: Record<string, any>; |  | ||||||
|   }) => { |  | ||||||
|     if (loading.value) return; |  | ||||||
|     if (!errors) { |  | ||||||
|       loading.value = true; |  | ||||||
|       loginStore |  | ||||||
|         .login({ |  | ||||||
|           username: values.username, |  | ||||||
|           password: encryptByRsa(values.password) || '', |  | ||||||
|           captcha: values.captcha, |  | ||||||
|           uuid: values.uuid, |  | ||||||
|         }) |  | ||||||
|         .then(() => { |  | ||||||
|           const { redirect, ...othersQuery } = router.currentRoute.value.query; |  | ||||||
|           router.push({ |  | ||||||
|             name: (redirect as string) || 'Workplace', |  | ||||||
|             query: { |  | ||||||
|               ...othersQuery, |  | ||||||
|             }, |  | ||||||
|           }); |  | ||||||
|           const { rememberMe } = loginConfig.value; |  | ||||||
|           const { username } = values; |  | ||||||
|           loginConfig.value.username = rememberMe ? username : ''; |  | ||||||
|           proxy.$notification.success(t('login.form.login.success')); |  | ||||||
|         }) |  | ||||||
|         .catch(() => { |  | ||||||
|           getCaptcha(); |  | ||||||
|           form.value.captcha = ''; |  | ||||||
|         }) |  | ||||||
|         .finally(() => { |  | ||||||
|           loading.value = false; |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   /** |  | ||||||
|    * 记住我 |  | ||||||
|    * |  | ||||||
|    * @param value 是否记住我 |  | ||||||
|    */ |  | ||||||
|   const setRememberMe = (value: boolean) => { |  | ||||||
|     loginConfig.value.rememberMe = value; |  | ||||||
|   }; |  | ||||||
| </script> |  | ||||||
|  |  | ||||||
| <style lang="less" scoped> |  | ||||||
|   .login-form { |  | ||||||
|     margin-top: 15px; |  | ||||||
|     &-wrapper { |  | ||||||
|       width: 320px; |  | ||||||
|     } |  | ||||||
|     &-title { |  | ||||||
|       color: var(--color-text-1); |  | ||||||
|       font-weight: 500; |  | ||||||
|       font-size: 24px; |  | ||||||
|       line-height: 32px; |  | ||||||
|     } |  | ||||||
|     &-sub-title { |  | ||||||
|       color: var(--color-text-3); |  | ||||||
|       font-size: 16px; |  | ||||||
|       line-height: 24px; |  | ||||||
|     } |  | ||||||
|     &-captcha img { |  | ||||||
|       width: 111px; |  | ||||||
|       height: 36px; |  | ||||||
|       margin: 0 0 0 10px; |  | ||||||
|       cursor: pointer; |  | ||||||
|     } |  | ||||||
|     &-remember-me { |  | ||||||
|       display: flex; |  | ||||||
|       justify-content: space-between; |  | ||||||
|     } |  | ||||||
|     &-register-btn { |  | ||||||
|       color: var(--color-text-3) !important; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| </style> |  | ||||||
							
								
								
									
										158
									
								
								continew-admin-ui/src/views/login/components/phone-login.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								continew-admin-ui/src/views/login/components/phone-login.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,158 @@ | |||||||
|  | <template> | ||||||
|  |   <a-form | ||||||
|  |     ref="formRef" | ||||||
|  |     :model="form" | ||||||
|  |     :rules="rules" | ||||||
|  |     layout="vertical" | ||||||
|  |     size="large" | ||||||
|  |     class="login-form" | ||||||
|  |   > | ||||||
|  |     <a-form-item field="phone" hide-label> | ||||||
|  |       <a-select :options="['+86']" style="flex: 1 1" default-value="+86" /> | ||||||
|  |       <a-input | ||||||
|  |         v-model="form.phone" | ||||||
|  |         :placeholder="$t('login.phone.placeholder.phone')" | ||||||
|  |         :max-length="11" | ||||||
|  |         allow-clear | ||||||
|  |       /> | ||||||
|  |     </a-form-item> | ||||||
|  |     <a-form-item field="captcha" hide-label> | ||||||
|  |       <a-input | ||||||
|  |         v-model="form.captcha" | ||||||
|  |         :placeholder="$t('login.phone.placeholder.captcha')" | ||||||
|  |         :max-length="6" | ||||||
|  |         allow-clear | ||||||
|  |         style="flex: 1 1" | ||||||
|  |       /> | ||||||
|  |       <a-button | ||||||
|  |         class="captcha-btn" | ||||||
|  |         :loading="captchaLoading" | ||||||
|  |         :disabled="captchaDisable" | ||||||
|  |         @click="handleSendCaptcha" | ||||||
|  |       > | ||||||
|  |         {{ captchaBtnName }} | ||||||
|  |       </a-button> | ||||||
|  |     </a-form-item> | ||||||
|  |     <a-button class="btn" :loading="loading" type="primary" html-type="submit" | ||||||
|  |       >{{ $t('login.button') }}(即将开放) | ||||||
|  |     </a-button> | ||||||
|  |   </a-form> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts" setup> | ||||||
|  |   import { getCurrentInstance, ref, toRefs, reactive, computed } from 'vue'; | ||||||
|  |   import { useI18n } from 'vue-i18n'; | ||||||
|  |   import { useLoginStore } from '@/store'; | ||||||
|  |   import { LoginReq } from '@/api/auth/login'; | ||||||
|  |  | ||||||
|  |   const { proxy } = getCurrentInstance() as any; | ||||||
|  |   const { t } = useI18n(); | ||||||
|  |   const loginStore = useLoginStore(); | ||||||
|  |   const loading = ref(false); | ||||||
|  |   const captchaLoading = ref(false); | ||||||
|  |   const captchaDisable = ref(false); | ||||||
|  |   const captchaTime = ref(60); | ||||||
|  |   const captchaTimer = ref(); | ||||||
|  |   const captchaBtnNameKey = ref('login.phone.captcha'); | ||||||
|  |   const captchaBtnName = computed(() => t(captchaBtnNameKey.value)); | ||||||
|  |   const data = reactive({ | ||||||
|  |     form: {} as LoginReq, | ||||||
|  |     rules: { | ||||||
|  |       phone: [ | ||||||
|  |         { required: true, message: t('login.phone.error.required.phone') }, | ||||||
|  |       ], | ||||||
|  |       captcha: [ | ||||||
|  |         { required: true, message: t('login.phone.error.required.captcha') }, | ||||||
|  |       ], | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  |   const { form, rules } = toRefs(data); | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 重置验证码 | ||||||
|  |    */ | ||||||
|  |   const resetCaptcha = () => { | ||||||
|  |     window.clearInterval(captchaTimer.value); | ||||||
|  |     captchaTime.value = 60; | ||||||
|  |     captchaBtnNameKey.value = 'login.phone.captcha'; | ||||||
|  |     captchaDisable.value = false; | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 发送验证码 | ||||||
|  |    */ | ||||||
|  |   const handleSendCaptcha = () => { | ||||||
|  |     if (captchaLoading.value) return; | ||||||
|  |     proxy.$refs.formRef.validateField('phone', (valid: any) => { | ||||||
|  |       if (!valid) { | ||||||
|  |         captchaLoading.value = true; | ||||||
|  |         captchaBtnNameKey.value = 'login.phone.captcha.ing'; | ||||||
|  |         captchaLoading.value = false; | ||||||
|  |         captchaDisable.value = true; | ||||||
|  |         captchaBtnNameKey.value = `${t( | ||||||
|  |           'login.phone.reCaptcha' | ||||||
|  |         )}(${(captchaTime.value -= 1)}s)`; | ||||||
|  |         captchaTimer.value = window.setInterval(() => { | ||||||
|  |           captchaTime.value -= 1; | ||||||
|  |           captchaBtnNameKey.value = `${t('login.phone.reCaptcha')}(${ | ||||||
|  |             captchaTime.value | ||||||
|  |           }s)`; | ||||||
|  |           if (captchaTime.value < 0) { | ||||||
|  |             window.clearInterval(captchaTimer.value); | ||||||
|  |             captchaTime.value = 60; | ||||||
|  |             captchaBtnNameKey.value = t('login.phone.reCaptcha'); | ||||||
|  |             captchaDisable.value = false; | ||||||
|  |           } | ||||||
|  |         }, 1000); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="less" scoped> | ||||||
|  |   .login-form { | ||||||
|  |     box-sizing: border-box; | ||||||
|  |     padding: 0 5px; | ||||||
|  |     margin-top: 16px; | ||||||
|  |     .arco-input-wrapper, | ||||||
|  |     :deep(.arco-select-view-single) { | ||||||
|  |       background-color: var(--color-bg-white); | ||||||
|  |       border: 1px solid var(--color-border-3); | ||||||
|  |       height: 40px; | ||||||
|  |       border-radius: 4px; | ||||||
|  |       font-size: 13px; | ||||||
|  |     } | ||||||
|  |     .arco-input-wrapper.arco-input-error { | ||||||
|  |       background-color: var(--color-danger-light-1); | ||||||
|  |       border-color: var(--color-danger-light-4); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     .captcha-btn { | ||||||
|  |       height: 40px; | ||||||
|  |       margin-left: 12px; | ||||||
|  |       min-width: 98px; | ||||||
|  |       border-radius: 4px; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     .arco-btn-secondary:not(.arco-btn-disabled) { | ||||||
|  |       background-color: #f6f8fa; | ||||||
|  |       border: 1px solid #dde2e9; | ||||||
|  |       color: #41464f; | ||||||
|  |     } | ||||||
|  |     .arco-btn-secondary:not(.arco-btn-disabled):hover { | ||||||
|  |       background-color: transparent; | ||||||
|  |       border: 1px solid rgb(var(--primary-6)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     .btn { | ||||||
|  |       border-radius: 4px; | ||||||
|  |       box-shadow: 0 0 0 1px #05f, 0 2px 1px rgba(0, 0, 0, 0.15); | ||||||
|  |       font-size: 14px; | ||||||
|  |       font-weight: 500; | ||||||
|  |       height: 40px; | ||||||
|  |       line-height: 22px; | ||||||
|  |       margin: 20px 0 12px; | ||||||
|  |       width: 100%; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | </style> | ||||||
| @@ -1,75 +1,264 @@ | |||||||
| <template> | <template> | ||||||
|   <div class="container"> |   <div class="root"> | ||||||
|     <div class="logo"> |     <div class="header"> | ||||||
|       <img :src="getFile(appStore.getLogo)" alt="logo" height="33" /> |       <img | ||||||
|  |         :src="getFile(appStore.getLogo) ?? './logo.svg'" | ||||||
|  |         alt="logo" | ||||||
|  |         height="33" | ||||||
|  |       /> | ||||||
|       <div class="logo-text">{{ appStore.getTitle }}</div> |       <div class="logo-text">{{ appStore.getTitle }}</div> | ||||||
|     </div> |     </div> | ||||||
|     <LoginBanner /> |     <div class="container"> | ||||||
|     <div class="content"> |       <div class="left-banner"></div> | ||||||
|       <div class="content-inner"> |       <div class="login-card"> | ||||||
|         <LoginForm /> |         <div class="title" | ||||||
|  |           >{{ $t('login.welcome') }} {{ appStore.getTitle }}</div | ||||||
|  |         > | ||||||
|  |         <a-tabs class="account-tab" default-active-key="1"> | ||||||
|  |           <a-tab-pane key="1" :title="$t('login.account')" | ||||||
|  |             ><AccountLogin | ||||||
|  |           /></a-tab-pane> | ||||||
|  |           <a-tab-pane key="2" :title="$t('login.phone')" | ||||||
|  |             ><PhoneLogin | ||||||
|  |           /></a-tab-pane> | ||||||
|  |         </a-tabs> | ||||||
|  |         <div class="oauth"> | ||||||
|  |           <a-divider class="text" orientation="center">{{ | ||||||
|  |             $t('login.other') | ||||||
|  |           }}</a-divider> | ||||||
|  |           <div class="idps"> | ||||||
|  |             <a-tooltip content="邮箱登录(即将开放)" mini> | ||||||
|  |               <div class="mail app"> | ||||||
|  |                 <icon-email /> {{ $t('login.email.txt') }} | ||||||
|  |               </div> | ||||||
|  |             </a-tooltip> | ||||||
|  |             <a-tooltip content="Gitee(即将开放)" mini> | ||||||
|  |               <a href="javascript: void(0);" class="app"> | ||||||
|  |                 <svg | ||||||
|  |                   class="icon" | ||||||
|  |                   fill="#C71D23" | ||||||
|  |                   role="img" | ||||||
|  |                   viewBox="0 0 24 24" | ||||||
|  |                   xmlns="http://www.w3.org/2000/svg" | ||||||
|  |                 > | ||||||
|  |                   <path | ||||||
|  |                     d="M11.984 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.016 0zm6.09 5.333c.328 0 .593.266.592.593v1.482a.594.594 0 0 1-.593.592H9.777c-.982 0-1.778.796-1.778 1.778v5.63c0 .327.266.592.593.592h5.63c.982 0 1.778-.796 1.778-1.778v-.296a.593.593 0 0 0-.592-.593h-4.15a.592.592 0 0 1-.592-.592v-1.482a.593.593 0 0 1 .593-.592h6.815c.327 0 .593.265.593.592v3.408a4 4 0 0 1-4 4H5.926a.593.593 0 0 1-.593-.593V9.778a4.444 4.444 0 0 1 4.445-4.444h8.296Z" | ||||||
|  |                   /> | ||||||
|  |                 </svg> | ||||||
|  |               </a> | ||||||
|  |             </a-tooltip> | ||||||
|  |             <a-tooltip content="GitHub(即将开放)" mini> | ||||||
|  |               <a href="javascript: void(0);" class="app"> | ||||||
|  |                 <svg | ||||||
|  |                   class="icon" | ||||||
|  |                   role="img" | ||||||
|  |                   viewBox="0 0 24 24" | ||||||
|  |                   xmlns="http://www.w3.org/2000/svg" | ||||||
|  |                 > | ||||||
|  |                   <path | ||||||
|  |                     d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" | ||||||
|  |                   /> | ||||||
|  |                 </svg> | ||||||
|  |               </a> | ||||||
|  |             </a-tooltip> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|     </div> |     </div> | ||||||
|     <div class="footer"> |     <div class="footer"> | ||||||
|         <Footer /> |       <div class="beian"> | ||||||
|  |         <div class="below text" v-html="appStore.getCopyright"></div> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
|   import Footer from '@/components/footer/index.vue'; |   import { useI18n } from 'vue-i18n'; | ||||||
|   import { useAppStore } from '@/store'; |   import { useAppStore } from '@/store'; | ||||||
|   import getFile from '@/utils/file'; |   import getFile from '@/utils/file'; | ||||||
|   import LoginBanner from './components/banner.vue'; |   import AccountLogin from './components/account-login.vue'; | ||||||
|   import LoginForm from './components/login-form.vue'; |   import PhoneLogin from './components/phone-login.vue'; | ||||||
|  |  | ||||||
|  |   const { t } = useI18n(); | ||||||
|   const appStore = useAppStore(); |   const appStore = useAppStore(); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style lang="less" scoped> | <style lang="less" scoped> | ||||||
|  |   .root { | ||||||
|  |     background-image: url(../../assets/images/login/login-bg.png); | ||||||
|  |     background-repeat: no-repeat; | ||||||
|  |     background-size: cover; | ||||||
|  |     min-height: 100vh; | ||||||
|  |  | ||||||
|  |     a { | ||||||
|  |       color: #3370ff; | ||||||
|  |       cursor: pointer !important; | ||||||
|  |       text-decoration: none; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     a:hover { | ||||||
|  |       color: #6694ff; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     .header { | ||||||
|  |       padding: 32px 40px 0; | ||||||
|  |       img { | ||||||
|  |         vertical-align: middle; | ||||||
|  |       } | ||||||
|  |       .logo-text { | ||||||
|  |         display: inline-block; | ||||||
|  |         margin-right: 4px; | ||||||
|  |         margin-left: 4px; | ||||||
|  |         color: var(--color-text-1); | ||||||
|  |         font-size: 24px; | ||||||
|  |         vertical-align: middle; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     .container { |     .container { | ||||||
|     display: flex; |  | ||||||
|     height: 100vh; |  | ||||||
|     .banner { |  | ||||||
|       width: 550px; |  | ||||||
|       background: linear-gradient(163.85deg, #1d2129 0%, #00308f 100%); |  | ||||||
|     } |  | ||||||
|     .content { |  | ||||||
|       position: relative; |  | ||||||
|       display: flex; |  | ||||||
|       flex: 1; |  | ||||||
|       align-items: center; |       align-items: center; | ||||||
|  |       box-sizing: border-box; | ||||||
|  |       display: flex; | ||||||
|  |       height: calc(100vh - 100px); | ||||||
|       justify-content: center; |       justify-content: center; | ||||||
|       padding-bottom: 40px; |       margin: 0 auto; | ||||||
|     } |       max-width: 1200px; | ||||||
|     .footer { |       min-height: 650px; | ||||||
|  |       .left-banner { | ||||||
|  |         flex: 1 1; | ||||||
|  |         height: 100%; | ||||||
|  |         max-height: 700px; | ||||||
|  |         position: relative; | ||||||
|  |         img { | ||||||
|  |           height: 100%; | ||||||
|  |           left: 0; | ||||||
|  |           max-height: 350px; | ||||||
|  |           max-width: 500px; | ||||||
|  |           object-fit: contain; | ||||||
|           position: absolute; |           position: absolute; | ||||||
|       right: 0; |           top: 5%; | ||||||
|       bottom: 0; |  | ||||||
|           width: 100%; |           width: 100%; | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  |       .login-card { | ||||||
|  |         display: flex; | ||||||
|  |         background: #fff; | ||||||
|  |         border-radius: 20px; | ||||||
|  |         box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05); | ||||||
|  |         box-sizing: border-box; | ||||||
|  |         min-height: 500px; | ||||||
|  |         position: relative; | ||||||
|  |         width: 450px; | ||||||
|  |         flex-direction: column; | ||||||
|  |         margin-bottom: 53px; | ||||||
|  |         padding: 48px 43px 32px; | ||||||
|  |         .title { | ||||||
|  |           color: #020814; | ||||||
|  |           font-size: 24px; | ||||||
|  |           font-weight: 500; | ||||||
|  |           letter-spacing: 0.003em; | ||||||
|  |           line-height: 32px; | ||||||
|  |           padding: 0 5px; | ||||||
|  |         } | ||||||
|  |         .account-tab { | ||||||
|  |           margin-top: 36px; | ||||||
|  |           :deep(.arco-tabs-nav::before) { | ||||||
|  |             display: none; | ||||||
|  |           } | ||||||
|  |           :deep(.arco-tabs-tab-title) { | ||||||
|  |             font-size: 16px; | ||||||
|  |             font-weight: 500; | ||||||
|  |             line-height: 22px; | ||||||
|  |             display: inline-block; | ||||||
|  |             padding: 1px 0; | ||||||
|  |             position: relative; | ||||||
|  |           } | ||||||
|  |           :deep(.arco-tabs-tab-title:hover) { | ||||||
|  |             color: rgb(var(--primary-6)); | ||||||
|  |           } | ||||||
|  |           :deep(.arco-tabs-tab-title:before) { | ||||||
|  |             display: none; | ||||||
|  |           } | ||||||
|  |           :deep(.arco-tabs-tab) { | ||||||
|  |             margin: 0 30px 0 6px; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |  | ||||||
|   .logo { |         .oauth { | ||||||
|     position: fixed; |           margin-top: 20px; | ||||||
|     top: 24px; |           padding: 0 5px; | ||||||
|     left: 22px; |           :deep(.arco-divider-text) { | ||||||
|     z-index: 1; |             color: #80838a; | ||||||
|     display: inline-flex; |             font-size: 12px; | ||||||
|  |             font-weight: 400; | ||||||
|  |             line-height: 20px; | ||||||
|  |           } | ||||||
|  |           :deep(.arco-divider) { | ||||||
|  |             margin-bottom: 25px; | ||||||
|  |           } | ||||||
|  |           .idps { | ||||||
|             align-items: center; |             align-items: center; | ||||||
|  |             display: flex; | ||||||
|     &-text { |             justify-content: center; | ||||||
|       margin-right: 4px; |             width: 100%; | ||||||
|       margin-left: 4px; |             .app { | ||||||
|       color: var(--color-fill-1); |               margin-right: 12px; | ||||||
|       font-size: 20px; |               align-items: center; | ||||||
|  |               border: 1px solid #eaedf1; | ||||||
|  |               border-radius: 32px; | ||||||
|  |               box-sizing: border-box; | ||||||
|  |               display: flex; | ||||||
|  |               height: 32px; | ||||||
|  |               justify-content: center; | ||||||
|  |               width: 32px; | ||||||
|  |               cursor: pointer; | ||||||
|  |               .icon { | ||||||
|  |                 width: 21px; | ||||||
|  |                 height: 20px; | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |             .app:hover { | ||||||
|  |               background: #f3f7ff; | ||||||
|  |               border: 1px solid #97bcff; | ||||||
|  |             } | ||||||
|  |             .mail { | ||||||
|  |               min-width: 81px; | ||||||
|  |               width: 81px; | ||||||
|  |               color: #41464f; | ||||||
|  |               font-size: 12px; | ||||||
|  |               font-weight: 400; | ||||||
|  |               line-height: 20px; | ||||||
|  |               padding: 6px 10px; | ||||||
|  |               svg { | ||||||
|  |                 color: #000; | ||||||
|  |                 font-size: 16px; | ||||||
|  |                 margin-right: 10px; | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   @media (max-width: @screen-lg) { |     .footer { | ||||||
|     .container { |       align-items: center; | ||||||
|       .banner { |       box-sizing: border-box; | ||||||
|         width: 25%; |       display: flex; | ||||||
|  |       justify-content: center; | ||||||
|  |       .beian { | ||||||
|  |         .text { | ||||||
|  |           color: #41464f; | ||||||
|  |           font-size: 12px; | ||||||
|  |           font-weight: 400; | ||||||
|  |           letter-spacing: 0.2px; | ||||||
|  |           line-height: 20px; | ||||||
|  |           text-align: center; | ||||||
|  |         } | ||||||
|  |         .below { | ||||||
|  |           align-items: center; | ||||||
|  |           display: flex; | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -1,29 +1,32 @@ | |||||||
| export default { | export default { | ||||||
|   'login.form.placeholder.username': 'Please enter username', |   'login.welcome': 'Welcome to', | ||||||
|   'login.form.placeholder.password': 'Please enter password', |   'login.account': 'Account Login', | ||||||
|   'login.form.placeholder.captcha': 'Please enter captcha', |   'login.phone': 'Phone Login', | ||||||
|  |   'login.email': 'Email Login', | ||||||
|  |   'login.other': 'Other Login', | ||||||
|  |  | ||||||
|   'login.form.error.required.username': 'Please enter username', |   'login.account.placeholder.username': 'Please enter username', | ||||||
|   'login.form.error.required.password': 'Please enter password', |   'login.account.placeholder.password': 'Please enter password', | ||||||
|   'login.form.error.required.captcha': 'Please enter captcha', |   'login.account.placeholder.captcha': 'Please enter captcha', | ||||||
|  |   'login.phone.placeholder.phone': 'Please enter phone', | ||||||
|  |   'login.phone.placeholder.captcha': 'Please enter captcha', | ||||||
|  |   'login.phone.captcha': 'Get captcha', | ||||||
|  |   'login.phone.captcha.ing': 'Sending...', | ||||||
|  |   'login.phone.reCaptcha': 'Resend captcha', | ||||||
|  |  | ||||||
|   'login.form.captcha': 'Captcha', |   'login.account.error.required.username': 'Please enter username', | ||||||
|   'login.form.rememberMe': 'Remember me', |   'login.account.error.required.password': 'Please enter password', | ||||||
|   'login.form.login': 'Login', |   'login.account.error.required.captcha': 'Please enter captcha', | ||||||
|   'login.form.register': 'Register account', |   'login.phone.error.required.phone': 'Please enter phone', | ||||||
|   'login.form.forgetPassword': 'Forgot password', |   'login.phone.error.required.captcha': 'Please enter captcha', | ||||||
|  |  | ||||||
|   'login.form.login.success': 'Welcome to use', |   'login.captcha': 'Captcha', | ||||||
|   'login.form.login.error': 'Login error, refresh and try again', |   'login.rememberMe': 'Remember me', | ||||||
|   'login.form.logout.success': 'Logout success', |   'login.button': 'Login', | ||||||
|  |   'login.email.txt': 'Email', | ||||||
|  |   'login.account.txt': 'Account/Phone Login', | ||||||
|  |  | ||||||
|   'login.banner.slogan1': 'Middle and background management framework', |   'login.success': 'Welcome to use', | ||||||
|   'login.banner.subSlogan1': |   'login.error': 'Login error, refresh and try again', | ||||||
|     'Continue New Admin, continue to build on the latest popular technology stack', |   'login.logout.success': 'Logout success', | ||||||
|   'login.banner.slogan2': 'Built-in solutions to common problems', |  | ||||||
|   'login.banner.subSlogan2': |  | ||||||
|     'Rich basic functions, low threshold to use, enterprise rapid development scaffolding', |  | ||||||
|   'login.banner.slogan3': 'The code is standard and open source and free', |  | ||||||
|   'login.banner.subSlogan3': |  | ||||||
|     'The backend code is fully compliant with the Alibaba coding specification, and the frontend code is strictly ESLint checked', |  | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,28 +1,32 @@ | |||||||
| export default { | export default { | ||||||
|   'login.form.placeholder.username': '请输入用户名', |   'login.welcome': '欢迎来到', | ||||||
|   'login.form.placeholder.password': '请输入密码', |   'login.account': '账号登录', | ||||||
|   'login.form.placeholder.captcha': '请输入验证码', |   'login.phone': '手机号登录', | ||||||
|  |   'login.email': '邮箱登录', | ||||||
|  |   'login.other': '其他登录方式', | ||||||
|  |  | ||||||
|   'login.form.error.required.username': '请输入用户名', |   'login.account.placeholder.username': '请输入用户名', | ||||||
|   'login.form.error.required.password': '请输入密码', |   'login.account.placeholder.password': '请输入密码', | ||||||
|   'login.form.error.required.captcha': '请输入验证码', |   'login.account.placeholder.captcha': '请输入验证码', | ||||||
|  |   'login.phone.placeholder.phone': '请输入手机号', | ||||||
|  |   'login.phone.placeholder.captcha': '请输入验证码', | ||||||
|  |   'login.phone.captcha': '获取验证码', | ||||||
|  |   'login.phone.captcha.ing': '发送中...', | ||||||
|  |   'login.phone.reCaptcha': '重新发送', | ||||||
|  |  | ||||||
|   'login.form.captcha': '验证码', |   'login.account.error.required.username': '请输入用户名', | ||||||
|   'login.form.rememberMe': '记住我', |   'login.account.error.required.password': '请输入密码', | ||||||
|   'login.form.login': '登录', |   'login.account.error.required.captcha': '请输入验证码', | ||||||
|   'login.form.register': '注册账号', |   'login.phone.error.required.phone': '请输入手机号', | ||||||
|   'login.form.forgetPassword': '忘记密码', |   'login.phone.error.required.captcha': '请输入验证码', | ||||||
|  |  | ||||||
|   'login.form.login.success': '欢迎使用', |   'login.captcha': '验证码', | ||||||
|   'login.form.login.error': '登录出错,请刷新重试', |   'login.rememberMe': '记住我', | ||||||
|   'login.form.logout.success': '退出成功', |   'login.button': '立即登录', | ||||||
|  |   'login.email.txt': '邮箱', | ||||||
|  |   'login.account.txt': '账号/手机号登录', | ||||||
|  |  | ||||||
|   'login.banner.slogan1': '中后台管理框架', |   'login.success': '欢迎使用', | ||||||
|   'login.banner.subSlogan1': |   'login.error': '登录出错,请刷新重试', | ||||||
|     'Continue New Admin,持续以最新流行技术栈构建,拥抱变化,迭代优化', |   'login.logout.success': '退出成功', | ||||||
|   'login.banner.slogan2': '内置了常见问题的解决方案', |  | ||||||
|   'login.banner.subSlogan2': '基础功能丰富,使用门槛低,企业级快速开发脚手架', |  | ||||||
|   'login.banner.slogan3': '代码规范且开源免费', |  | ||||||
|   'login.banner.subSlogan3': |  | ||||||
|     '后端代码完全遵循阿里巴巴编码规范,前端代码使用严格的 ESLint 检查', |  | ||||||
| }; | }; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user