 dark
					
					2 years ago
						dark
					
					2 years ago
					
				
				 14 changed files with 297 additions and 27 deletions
			
			
		- 
					6src/App.tsx
- 
					BINsrc/assets/login.png
- 
					4src/layout/RootLayout.tsx
- 
					11src/locales/lang/en-US.ts
- 
					11src/locales/lang/zh-CN.ts
- 
					123src/pages/login/index.css
- 
					100src/pages/login/index.tsx
- 
					2src/service/base.ts
- 
					7src/service/system.ts
- 
					20src/store/system.ts
- 
					24src/store/user.ts
- 
					1src/types/login.d.ts
- 
					1src/types/menus.d.ts
- 
					14src/utils/index.ts
| @ -0,0 +1,123 @@ | |||||
|  | body { | ||||
|  |     margin: 0; | ||||
|  |     padding: 0; | ||||
|  |     font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif; | ||||
|  | 
 | ||||
|  |     overflow: hidden; | ||||
|  | 
 | ||||
|  |     .login-container { | ||||
|  |         display: flex; | ||||
|  |         align-items: center; | ||||
|  |         height: 100vh; | ||||
|  |         background-image: url("@/assets/login.png"); | ||||
|  |         background-repeat: no-repeat; | ||||
|  |         background-size: cover; | ||||
|  | 
 | ||||
|  | 
 | ||||
|  |         .language { | ||||
|  |             position: absolute; | ||||
|  |             top: 10px; | ||||
|  |             right: 10px; | ||||
|  |             /*color: #fff;*/ | ||||
|  |             font-size: 14px; | ||||
|  |             cursor: pointer; | ||||
|  | 
 | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         .loginBlock { | ||||
|  |             width: 100%; | ||||
|  |             height: 100%; | ||||
|  |             padding: 40px 0; | ||||
|  |             display: flex; | ||||
|  |             align-items: center; | ||||
|  |             justify-content: center; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         .innerBlock { | ||||
|  |             width: 356px; | ||||
|  |             margin: 0 auto; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         .logo { | ||||
|  |             height: 30px; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         .infoLine { | ||||
|  |             display: flex; | ||||
|  |             align-items: center; | ||||
|  |             justify-content: space-between; | ||||
|  |             margin: 0; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         .infoLeft { | ||||
|  |             color: #666; | ||||
|  |             font-size: 14px; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         .desc { | ||||
|  |             margin: 24px 0; | ||||
|  |             color: #999; | ||||
|  |             font-size: 16px; | ||||
|  |             cursor: pointer; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         .active { | ||||
|  |             color: #333; | ||||
|  |             font-weight: bold; | ||||
|  |             font-size: 24px; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         .line { | ||||
|  |             display: inline-block; | ||||
|  |             width: 1px; | ||||
|  |             height: 12px; | ||||
|  |             background: #999; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         .innerBeforeInput { | ||||
|  |             margin-left: 10px; | ||||
|  |             color: #999; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         .innerBeforeInput .line { | ||||
|  |             margin-left: 10px; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         .innerAfterInput { | ||||
|  |             margin-right: 10px; | ||||
|  |             color: #999; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         .innerAfterInput .line { | ||||
|  |             margin-right: 10px; | ||||
|  |             vertical-align: middle; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         .sendCode { | ||||
|  |             max-width: 65px; | ||||
|  |             margin-right: 10px; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         .otherLogin { | ||||
|  |             color: #666; | ||||
|  |             font-size: 14px; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         .icon { | ||||
|  |             margin-left: 10px; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         .link { | ||||
|  |             color: #5584ff; | ||||
|  |             font-size: 14px; | ||||
|  |             text-align: left; | ||||
|  |         } | ||||
|  | 
 | ||||
|  |         .submitBtn { | ||||
|  |             width: 100%; | ||||
|  |         } | ||||
|  |     } | ||||
|  | 
 | ||||
|  | 
 | ||||
|  | } | ||||
|  | 
 | ||||
| @ -1,17 +1,99 @@ | |||||
| import SelectLang from '@/components/select-lang' | import SelectLang from '@/components/select-lang' | ||||
| import { createFileRoute } from '@tanstack/react-router' |  | ||||
|  | import { createFileRoute, useSearch, useNavigate } from '@tanstack/react-router' | ||||
|  | import { Button, Form, Input, message, Space } from 'antd' | ||||
|  | import { useAtom } from 'jotai' | ||||
|  | import './index.css' | ||||
|  | import { useTranslation } from '@/i18n.ts' | ||||
|  | import { loginAtom, loginFormAtom } from '@/store/user.ts' | ||||
|  | import { memo, useEffect } from 'react' | ||||
| 
 | 
 | ||||
| const Login = () => { |  | ||||
| 
 | 
 | ||||
|   return ( |  | ||||
|     <div> |  | ||||
|       <SelectLang/> |  | ||||
|     </div> |  | ||||
|   ) |  | ||||
| } |  | ||||
|  | const Login = memo(() => { | ||||
|  | 
 | ||||
|  |     const navigator = useNavigate() | ||||
|  |     // @ts-ignore 从url中获取redirect参数
 | ||||
|  |     const search = useSearch({ form: '/login' }) | ||||
|  |     const { t } = useTranslation() | ||||
|  |     const [ values, setValues ] = useAtom(loginFormAtom) | ||||
|  |     const [ { isPending, isSuccess, mutate } ] = useAtom(loginAtom) | ||||
|  |     const [ form ] = Form.useForm() | ||||
|  | 
 | ||||
|  |     const handleSubmit = () => { | ||||
|  |         form.validateFields().then(() => { | ||||
|  |             mutate(values) | ||||
|  |         }) | ||||
|  |     } | ||||
|  | 
 | ||||
|  |     useEffect(() => { | ||||
|  |         if (isSuccess) { | ||||
|  |             message.success(t('login.success')) | ||||
|  |             navigator({ | ||||
|  |                 to: search?.redirect ?? '/' | ||||
|  |             }) | ||||
|  |         } | ||||
|  |     }, [ isSuccess ]) | ||||
|  | 
 | ||||
|  |     return ( | ||||
|  |             <div className={'login-container'}> | ||||
|  |                 <div className={'language'}> | ||||
|  |                     <SelectLang/> | ||||
|  |                 </div> | ||||
|  |                 <div className={'loginBlock'}> | ||||
|  |                     <div className={'innerBlock'}> | ||||
|  | 
 | ||||
|  |                         <div className={'desc'}> | ||||
|  |                           <span className={'active'}> | ||||
|  |                             {t('login.title')} | ||||
|  |                           </span> | ||||
|  | 
 | ||||
|  |                         </div> | ||||
|  | 
 | ||||
|  |                         <Form form={form} | ||||
|  |                               initialValues={values} | ||||
|  |                               onValuesChange={(_, allValues) => { | ||||
|  |                                   setValues(allValues) | ||||
|  |                               }} | ||||
|  |                               size="large"> | ||||
|  |                             <Form.Item name={'username'} | ||||
|  |                                        rules={[ { required: true, message: t('login.usernameMsg') } ]}> | ||||
|  |                                 <Input maxLength={20} placeholder={t('login.username')}/> | ||||
|  |                             </Form.Item> | ||||
|  |                             <Form.Item name={'password'} | ||||
|  |                                        rules={[ { required: true, message: t('login.passwordMsg') } ]}> | ||||
|  |                                 <Input.Password placeholder={t('login.password')}/> | ||||
|  |                             </Form.Item> | ||||
|  |                             <Form.Item noStyle> | ||||
|  |                                 <Space direction="horizontal"> | ||||
|  |                                     <Form.Item name={'code'} | ||||
|  |                                                rules={[ { required: true, message: t('login.codeMsg') } ]}> | ||||
|  |                                         <Input placeholder={t('login.code')}/> | ||||
|  |                                         {/*<img src="https://img.alicdn.com/tfs/TB1KtN6mKH2gK0jSZJnXXaT1FXa-1014-200.png" alt="验证码" />*/} | ||||
|  |                                     </Form.Item> | ||||
|  |                                 </Space> | ||||
|  |                             </Form.Item> | ||||
|  |                             <Form.Item style={{ marginBottom: 10 }}> | ||||
|  |                                 <Button | ||||
|  |                                         htmlType={'submit'} | ||||
|  |                                         type="primary" | ||||
|  |                                         onClick={handleSubmit} | ||||
|  |                                         className={'submitBtn'} | ||||
|  |                                         loading={isPending} | ||||
|  |                                         disabled={isPending} | ||||
|  |                                 > | ||||
|  |                                     {t('login.submit')} | ||||
|  |                                 </Button> | ||||
|  |                             </Form.Item> | ||||
|  | 
 | ||||
|  |                         </Form> | ||||
|  |                     </div> | ||||
|  |                 </div> | ||||
|  | 
 | ||||
|  |             </div> | ||||
|  |     ) | ||||
|  | }) | ||||
| 
 | 
 | ||||
| export const Route = createFileRoute('/login')({ | export const Route = createFileRoute('/login')({ | ||||
|   component: Login |  | ||||
|  |     component: Login | ||||
| }) | }) | ||||
| 
 | 
 | ||||
| export default Login | export default Login | ||||
| @ -1,8 +1,30 @@ | |||||
| import { atom } from 'jotai/index' | import { atom } from 'jotai/index' | ||||
| import { IAuth } from '../types' |  | ||||
|  | import { IAuth } from '@/types' | ||||
|  | import { LoginRequest } from '@/types/login' | ||||
|  | import { atomWithMutation } from 'jotai-tanstack-query' | ||||
|  | import systemServ from '@/service/system.ts' | ||||
|  | import { isDev } from '@/utils' | ||||
| 
 | 
 | ||||
| export const authAtom = atom<IAuth>({ | export const authAtom = atom<IAuth>({ | ||||
|     isLogin: false, |     isLogin: false, | ||||
|     authKey: [] |     authKey: [] | ||||
| }) | }) | ||||
| 
 | 
 | ||||
|  | const devLogin = { | ||||
|  |     username: 'SupperAdmin', | ||||
|  |     password: 'kk123456', | ||||
|  |     code: '123456' | ||||
|  | } | ||||
|  | export const loginFormAtom = atom<LoginRequest>({ | ||||
|  |     ...(isDev ? devLogin : {}) | ||||
|  | } as LoginRequest) | ||||
|  | 
 | ||||
|  | export const loginAtom = atomWithMutation<any, LoginRequest>(() => ({ | ||||
|  |     mutationKey: [ 'login' ], | ||||
|  |     mutationFn: async (params) => { | ||||
|  |         return await systemServ.login(params) | ||||
|  |     }, | ||||
|  |     onSuccess: (data) => { | ||||
|  |         console.log('login success', data) | ||||
|  |     } | ||||
|  | })) | ||||
						Write
						Preview
					
					
					Loading…
					
					Cancel
						Save
					
		Reference in new issue