feat: 登录页面框架

This commit is contained in:
landaiqing
2024-04-15 02:01:30 +08:00
parent b4a9ad82b9
commit 73a74ccc7c
24 changed files with 2010 additions and 179 deletions

View File

@@ -1,42 +1,42 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
padding: 2em;
}
.read-the-docs {
color: #888;
color: #888;
}

5
src/api/user/index.ts Normal file
View File

@@ -0,0 +1,5 @@
import service from '@/utils/axios/service.ts'
export function getUserInfo(params: object) {
return service.get('/user/info', params)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

View File

@@ -13,56 +13,32 @@
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
/*定义滚动条宽高及背景,宽高分别对应横竖滚动条的尺寸*/
// 滚动条整体部分
&::-webkit-scrollbar {
width: 6px;
height: 8px;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
// 滚动条的轨道的两端按钮,允许通过点击微调小方块的位置。
&::-webkit-scrollbar-button {
display: none;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
// 滚动条里面的小方块,能向上向下移动(或往左往右移动,取决于是垂直滚动条还是水平滚动条)
&::-webkit-scrollbar-thumb {
background: rgba(144, 147, 153, 0.3);
cursor: pointer;
border-radius: 4px;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
// 边角,即两个滚动条的交汇处
&::-webkit-scrollbar-corner {
display: none;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
// 两个滚动条的交汇处上用于通过拖动调整元素大小的小控件
&::-webkit-resizer {
display: none;
}

View File

@@ -1 +0,0 @@
@primary-color: '#ff7875';

1
src/constants/index.ts Normal file
View File

@@ -0,0 +1 @@
export const DEFAULT_NAME = 'admin'

View File

@@ -4,9 +4,13 @@ import { RouterProvider, createBrowserRouter } from 'react-router-dom'
import './assets/styles/index.less'
import routeConfig from './router'
import 'virtual:svg-icons-register'
import { Provider as MobxProvider } from 'mobx-react'
import { RootStore } from '@/store'
const router = createBrowserRouter(routeConfig)
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<RouterProvider router={router} />
<MobxProvider {...RootStore}>
<RouterProvider router={router} />
</MobxProvider>
</React.StrictMode>,
)

View File

@@ -1,18 +1,12 @@
import type { RouteObject } from 'react-router-dom'
import About from '@/views/about/About'
// import Home from '@/views/home/Home'
import NoFound from '@/views/404/404'
import Login from '@/views/Login/index'
import Login from '@/views/Login'
const routes: RouteObject[] = [
{
path: '/',
Component: Login,
},
{
path: '/about',
Component: About,
},
{
path: '*',
Component: NoFound,

View File

@@ -0,0 +1,6 @@
import { useUserStore } from './modules/user.ts'
/** 将每个Store实例化 */
export const RootStore = {
user: new useUserStore(),
}

40
src/store/modules/user.ts Normal file
View File

@@ -0,0 +1,40 @@
import { action, makeAutoObservable } from 'mobx'
import { makePersistable, isHydrated } from 'mobx-persist-store'
export class useUserStore {
user: object = {}
constructor() {
makeAutoObservable(
this,
{
setUserInfo: action,
},
{ autoBind: true },
)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
makePersistable(this, {
// 在构造函数内使用 makePersistable
name: 'userStore', // 保存的name用于在storage中的名称标识只要不和storage中其他名称重复就可以
properties: ['user'], // 要保存的字段这些字段会被保存在name对应的storage中注意不写在这里面的字段将不会被保存刷新页面也将丢失get字段例外。get数据会在数据返回后再自动计算
storage: window.localStorage, // 保存的位置看自己的业务情况选择可以是localStoragesessionstorage
// 。。还有一些其他配置参数例如数据过期时间等等可以康康文档像storage这种字段可以配置在全局配置里详见文档
}).then(
action((persistStore) => {
// persist 完成的回调在这里可以执行一些拿到数据后需要执行的操作如果在页面上要判断是否完成persist使用 isHydrated
// console.log(persistStore)
}),
)
}
get getUserInfo() {
return this.user
}
get token() {
return this.user ? this.user.token : ''
}
get isHydrated() {
return isHydrated(this)
}
setUserInfo(user: object) {
this.user = user
}
}

13
src/types/user/user.d.ts vendored Normal file
View File

@@ -0,0 +1,13 @@
declare namespace user {
interface userInfo {
name: string
avatar: string
userid: string
role: string
email: string
telephone: string
status: string
token: string
}
}
export { user }

View File

@@ -1,29 +1,146 @@
import axios from 'axios'
import axios, { AxiosInstance, AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'
import { message } from 'antd'
// 数据返回的接口
// 定义请求响应参数不含data
interface Result {
code: number
msg: string
}
const request = axios.create({
baseURL: import.meta.env.VITE_BASE_API, // 域名配置,可添加变量配置文件定义
headers: {
Authorization: `Bearer`, // token从Cookie中获取
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
},
timeout: 5000, // 请求超时时间
})
// 请求响应参数包含data
interface ResultData<T = never> extends Result {
data?: T
}
const URL = import.meta.env.VITE_API_BASE_URL
enum RequestEnums {
TIMEOUT = 20000,
OVERDUE = 600, // 登录失效
FAIL = 999, // 请求失败
SUCCESS = 200, // 请求成功
}
const config = {
// 默认地址
baseURL: URL as string,
// 设置超时时间
timeout: RequestEnums.TIMEOUT as number,
// 跨域时候允许携带凭证
withCredentials: true,
}
class RequestHttp {
// 定义成员变量并指定类型
service: AxiosInstance
public constructor(config: AxiosRequestConfig) {
// 实例化axios
this.service = axios.create(config)
/**
* 请求拦截器
* 客户端发送请求 -> [请求拦截器] -> 服务器
*/
this.service.interceptors.request.use(
(config: AxiosRequestConfig | any) => {
const token = localStorage.getItem('token') || ''
return {
...config,
headers: {
'x-access-token': token, // 请求头中携带token信息
},
}
},
(error: AxiosError) => {
// 请求报错
Promise.reject(error)
},
)
//请求拦截
request.interceptors.request.use(
(config) => config,
(err) => Promise.reject(err.response),
)
// 响应拦截
request.interceptors.response.use(
(response) => {
// 有些情况下接口未必是RESTful风格C相关的接口返回异常时状态码会小于0
if (response.status !== 200) return Promise.reject(response.data)
// 一般会和后端约定一些code分别进行处理,这里直接返回了不做处理
return response.data
},
(err) => Promise.reject(err.response),
)
export default request
/**
* 响应拦截器
* 服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息
*/
this.service.interceptors.response.use(
(response: AxiosResponse) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
const { data, config } = response // 解构
if (data.code === RequestEnums.OVERDUE) {
// 登录信息失效应跳转到登录页面并清空本地的token
localStorage.setItem('token', '')
return Promise.reject(data)
}
// 全局错误信息拦截防止下载文件得时候返回数据流没有code直接报错
if (data.code && data.code !== RequestEnums.SUCCESS) {
message.error(data) // 此处也可以使用组件提示报错信息
return Promise.reject(data)
}
return data
},
(error: AxiosError) => {
const { response } = error
if (response) {
this.handleCode(response.status)
}
if (!window.navigator.onLine) {
message.error('网络连接失败')
// 可以跳转到错误页面,也可以不做操作
// return router.replace({
// path: '/404'
// });
}
},
)
}
handleCode(code: number): void {
switch (code) {
case 400:
message.error('请求错误(400)')
break
case 401:
message.error('未授权,请重新登录(401)')
break
case 403:
message.error('拒绝访问(403)')
break
case 404:
message.error('请求出错(404)')
break
case 408:
message.error('请求超时(408)')
break
case 500:
message.error('服务器错误(500)')
break
case 501:
message.error('服务未实现(501)')
break
case 502:
message.error('网络错误(502)')
break
case 503:
message.error('服务不可用(503)')
break
case 504:
message.error('网络超时(504)')
break
case 505:
message.error('HTTP版本不受支持(505)')
break
default:
message.error(`连接出错(${code})!`)
break
}
}
// 常用方法封装
get<T>(url: string, params?: object): Promise<ResultData<T>> {
return this.service.get(url, { params })
}
post<T>(url: string, params?: object): Promise<ResultData<T>> {
return this.service.post(url, params)
}
put<T>(url: string, params?: object): Promise<ResultData<T>> {
return this.service.put(url, params)
}
delete<T>(url: string, params?: object): Promise<ResultData<T>> {
return this.service.delete(url, { params })
}
}
// 导出一个实例对象
export default new RequestHttp(config)

View File

@@ -0,0 +1,11 @@
import { MobXProviderContext } from 'mobx-react'
import { useContext } from 'react'
import { RootStore } from '@/store'
// 根据RootStore来实现参数的自动获取和返回值的自动推导
function useStore<T extends typeof RootStore, V extends keyof T>(name: V): T[V] {
const store = useContext(MobXProviderContext) as T
return store[name]
}
export default useStore

View File

@@ -1,5 +1,5 @@
import { useNavigate } from 'react-router-dom'
import './index.less'
export default () => {
const navigate = useNavigate()
const goBack = () => {
@@ -7,8 +7,149 @@ export default () => {
}
return (
<>
<h1>About Page</h1>
<button onClick={goBack}></button>
<body translate='no'>
<div className='container container-star'>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-1'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
<div className='star-2'></div>
</div>
<div className='container container-bird'>
<div className='bird bird-anim'>
<div className='bird-container'>
<div className='wing wing-left'>
<div className='wing-left-top'></div>
</div>
<div className='wing wing-right'>
<div className='wing-right-top'></div>
</div>
</div>
</div>
<div className='bird bird-anim'>
<div className='bird-container'>
<div className='wing wing-left'>
<div className='wing-left-top'></div>
</div>
<div className='wing wing-right'>
<div className='wing-right-top'></div>
</div>
</div>
</div>
<div className='bird bird-anim'>
<div className='bird-container'>
<div className='wing wing-left'>
<div className='wing-left-top'></div>
</div>
<div className='wing wing-right'>
<div className='wing-right-top'></div>
</div>
</div>
</div>
<div className='bird bird-anim'>
<div className='bird-container'>
<div className='wing wing-left'>
<div className='wing-left-top'></div>
</div>
<div className='wing wing-right'>
<div className='wing-right-top'></div>
</div>
</div>
</div>
<div className='bird bird-anim'>
<div className='bird-container'>
<div className='wing wing-left'>
<div className='wing-left-top'></div>
</div>
<div className='wing wing-right'>
<div className='wing-right-top'></div>
</div>
</div>
</div>
<div className='bird bird-anim'>
<div className='bird-container'>
<div className='wing wing-left'>
<div className='wing-left-top'></div>
</div>
<div className='wing wing-right'>
<div className='wing-right-top'></div>
</div>
</div>
</div>
<div className='container-title'>
<div className='title'>
<div className='number'>4</div>
<div className='moon'>
<div className='face'>
<div className='mouth'></div>
<div className='eyes'>
<div className='eye-left'></div>
<div className='eye-right'></div>
</div>
</div>
</div>
<div className='number'>4</div>
</div>
<div className='subtitle'>Oops. Looks like you took a wrong turn.</div>
<button onClick={goBack}>Go back</button>
</div>
</div>
</body>
</>
)
}

1393
src/views/404/index.less Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,11 @@
import {
AlipayOutlined,
GithubOutlined,
GitlabOutlined,
LockOutlined,
MobileOutlined,
TaobaoOutlined,
QqOutlined,
UserOutlined,
WeiboOutlined,
WechatOutlined,
} from '@ant-design/icons'
import {
LoginFormPage,
@@ -12,12 +13,14 @@ import {
ProFormCheckbox,
ProFormText,
} from '@ant-design/pro-components'
import { Divider, Space, Tabs, message } from 'antd'
import type { CSSProperties } from 'react'
import { Divider, Space, Tabs, message, Button } from 'antd'
import { CSSProperties } from 'react'
import { useState } from 'react'
// import { history } from 'umi';
type LoginType = 'phone' | 'account'
import logo from '@/assets/icons/schisandra.svg'
import background from '@/assets/images/background.png'
import { observer } from 'mobx-react'
// import useStore from '@/utils/store/useStore.tsx'
type LoginType = 'account' | 'phone'
const iconStyles: CSSProperties = {
color: 'rgba(0, 0, 0, 0.2)',
@@ -26,16 +29,17 @@ const iconStyles: CSSProperties = {
cursor: 'pointer',
}
export default () => {
export default observer(() => {
// const store = useStore('user')
const items = [
{ label: '账户密码登录', key: 'account' },
{ label: '手机号登录', key: 'phone' },
]
const [loginType, setLoginType] = useState<LoginType>('phone')
const [loginType, setLoginType] = useState<LoginType>('account')
const onSubmit = async (formData: unknown) => {
const onSubmit = async (formData: object) => {
console.log(formData)
// history.push('/');
}
return (
<div
@@ -46,33 +50,32 @@ export default () => {
}}>
<LoginFormPage
onFinish={onSubmit}
backgroundImageUrl='https://gw.alipayobjects.com/zos/rmsportal/FfdJeJRQWjEeGTpqgBKj.png'
logo='https://github.githubassets.com/images/modules/logos_page/Octocat.png'
title='Github'
subTitle='全球最大的代码托管平台'
// activityConfig={{
// style: {
// boxShadow: '0px 0px 8px rgba(0, 0, 0, 0.2)',
// color: '#fff',
// borderRadius: 8,
// backgroundColor: '#1677FF',
// },
// title: '活动标题,可配置图片',
// subTitle: '活动介绍说明文字',
// action: (
// <Button
// size="large"
// style={{
// borderRadius: 20,
// background: '#fff',
// color: '#1677FF',
// width: 120,
// }}
// >
// 去看看
// </Button>
// ),
// }}
backgroundImageUrl={background}
logo={logo}
title='五味子云相册'
subTitle='随时随地分享你的美好瞬间'
activityConfig={{
style: {
boxShadow: '0px 0px 8px rgba(0, 0, 0, 0.2)',
color: '#fff',
borderRadius: 8,
backgroundColor: '#1677FF',
},
title: '活动标题,可配置图片',
subTitle: '活动介绍说明文字',
action: (
<Button
size='large'
style={{
borderRadius: 20,
background: '#fff',
color: '#1677FF',
width: 120,
}}>
</Button>
),
}}
actions={
<div
style={{
@@ -98,7 +101,7 @@ export default () => {
border: '1px solid #D4D8DD',
borderRadius: '50%',
}}>
<AlipayOutlined style={{ ...iconStyles, color: '#1677FF' }} />
<QqOutlined style={{ ...iconStyles, color: '#1677FF' }} />
</div>
<div
style={{
@@ -111,7 +114,7 @@ export default () => {
border: '1px solid #D4D8DD',
borderRadius: '50%',
}}>
<TaobaoOutlined style={{ ...iconStyles, color: '#FF6A10' }} />
<WechatOutlined style={{ ...iconStyles, color: '#08a327' }} />
</div>
<div
style={{
@@ -124,7 +127,20 @@ export default () => {
border: '1px solid #D4D8DD',
borderRadius: '50%',
}}>
<WeiboOutlined style={{ ...iconStyles, color: '#333333' }} />
<GithubOutlined style={{ ...iconStyles, color: '#333333' }} />
</div>
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
height: 40,
width: 40,
border: '1px solid #D4D8DD',
borderRadius: '50%',
}}>
<GitlabOutlined style={{ ...iconStyles, color: '#FF6A10' }} />
</div>
</Space>
</div>
@@ -224,4 +240,4 @@ export default () => {
</LoginFormPage>
</div>
)
}
})

View File

@@ -1,14 +0,0 @@
import { useNavigate } from 'react-router-dom'
export default () => {
const navigate = useNavigate()
const goBack = () => {
navigate(-1)
}
return (
<>
<h1>About Page</h1>
<button onClick={goBack}></button>
</>
)
}

View File

@@ -1,16 +0,0 @@
import { Link } from 'react-router-dom'
import { Button, Card, Image } from 'antd'
import SvgIcon from '@/components/SvgIcon/SvgIcon.tsx'
import wallhaven from '@/assets/images/wallhaven.jpg'
export default () => {
return (
<>
<h1>Home Page</h1>
<Link to='/about'></Link>
<Button></Button>
<Card></Card>
<SvgIcon name='schisandra' size={200} />
<Image src={wallhaven}></Image>
</>
)
}