dark
7 months 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