React+Redux+Ant Design+TypeScript 电子商务实战-客户端应用 02 登录注册

本文主要是介绍React+Redux+Ant Design+TypeScript 电子商务实战-客户端应用 02 登录注册,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

创建导航菜单

// src\components\core\Navigation.tsx
import { Menu } from 'antd'
import { Link } from 'react-router-dom'
import { useSelector } from 'react-redux'
import { AppState } from '../../store/reducers'
import { RouterState } from 'connected-react-router'// 判断选中类名的钩子函数
function useActive(currentPath: string, path: string): string {return currentPath === path ? 'ant-menu-item-selected' : ''
}const Navigation = () => {const router = useSelector<AppState, RouterState>(state => state.router)const pathname = router.location.pathnameconst isHome = useActive(pathname, '/')const isShop = useActive(pathname, '/shop')return (<Menu mode="horizontal" selectable={false}><Menu.Item className={isHome}><Link to="/">首页</Link></Menu.Item><Menu.Item className={isShop}><Link to="/shop">商城</Link></Menu.Item></Menu>)
}export default Navigation
// src\components\core\Layout.tsx
import React, { FC } from 'react'
import Navigation from './Navigation'// 定义 Layout 组件参数类型的接口
interface Props {children: React.ReactNode
}// FC 表示函数型组件类型
const Layout: FC<Props> = ({ children }) => {return (<div><Navigation></Navigation><div style={{ width: '85%', minWidth: '980px', margin: '0 auto' }}>{children}</div></div>)
}export default Layout

创建页头

页头组件

// src\components\core\Layout.tsx
import { PageHeader } from 'antd'
import React, { FC } from 'react'
import Navigation from './Navigation'// 定义 Layout 组件参数类型的接口
interface Props {children: React.ReactNodetitle: stringsubTitle: string
}// FC 表示函数型组件类型
const Layout: FC<Props> = ({ children, title, subTitle }) => {return (<div><Navigation></Navigation><PageHeader className="jumbotron" title={title} subTitle={subTitle} /><div style={{ width: '85%', minWidth: '980px', margin: '0 auto' }}>{children}</div></div>)
}export default Layout

页头样式

/* src\style.css */
.jumbotron {background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);background-size: 400% 400%;-webkit-animation: Gradient 15s ease infinite;-moz-animation: Gradient 15s ease infinite;animation: Gradient 15s ease infinite;margin-bottom: 25px;
}.jumbotron span {color: #fff;
}@-webkit-keyframes Gradient {0% {background-position: 0% 50%;}50% {background-position: 100% 50%;}100% {background-position: 0% 50%;}
}@-moz-keyframes Gradient {0% {background-position: 0% 50%;}50% {background-position: 100% 50%;}100% {background-position: 0% 50%;}
}@keyframes Gradient {0% {background-position: 0% 50%;}50% {background-position: 100% 50%;}100% {background-position: 0% 50%;}
}

引入样式表

// src\index.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import 'antd/dist/antd.css'
import './style.css'
import Routes from './Routes'
import { Provider } from 'react-redux'
import store, { history } from './store'
import { ConnectedRouter } from 'connected-react-router'ReactDOM.render(<React.StrictMode><Provider store={store}><ConnectedRouter history={history}><Routes /></ConnectedRouter></Provider></React.StrictMode>,document.getElementById('root')
)

页面组件传递 title 和 subTitle

// src\components\core\Home.tsx
import { useSelector } from 'react-redux'
import Layout from './Layout'const Home = () => {const state = useSelector(state => state)return (<Layout title="RM商城" subTitle="优享品质 惊喜价格">Home {JSON.stringify(state)}</Layout>)
}export default Home
// src\components\core\Shop.tsx
import { useSelector } from 'react-redux'
import Layout from './Layout'const Shop = () => {const state = useSelector(state => state)return (<Layout title="RM商城" subTitle="挑选你喜欢的商品把">Shop {JSON.stringify(state)}</Layout>)
}export default Shop

构建注册和登录表单

注册页面组件

// src\components\core\Signup.tsx
import Layout from './Layout'
import { Button, Form, Input } from 'antd'const Signup = () => {return (<Layout title="注册" subTitle="还没有账号?注册一个吧"><Form><Form.Item name="name" label="昵称"><Input /></Form.Item><Form.Item name="password" label="密码"><Input.Password /></Form.Item><Form.Item name="email" label="邮箱"><Input /></Form.Item><Form.Item><Button type="primary" htmlType="submit">注册</Button></Form.Item></Form></Layout>)
}export default Signup

登录页面组件

// src\components\core\Signin.tsx
import Layout from './Layout'
import { Button, Form, Input } from 'antd'const Signin = () => {return (<Layout title="登录" subTitle=""><Form><Form.Item name="email" label="邮箱"><Input /></Form.Item><Form.Item name="password" label="密码"><Input.Password /></Form.Item><Form.Item><Button type="primary" htmlType="submit">登录</Button></Form.Item></Form></Layout>)
}export default Signin

配置路由

// src\Routes.tsx
import { HashRouter, Route, Switch } from 'react-router-dom'
import Home from './components/core/Home'
import Shop from './components/core/Shop'
import Signin from './components/core/Signin'
import Signup from './components/core/Signup'const Routes = () => {return (<HashRouter><Switch><Route path="/" component={Home} exact /><Route path="/shop" component={Shop} /><Route path="/signin" component={Signin} /><Route path="/signup" component={Signup} /></Switch></HashRouter>)
}export default Routes

配置导航菜单

// src\components\core\Navigation.tsx
import { Menu } from 'antd'
import { Link } from 'react-router-dom'
import { useSelector } from 'react-redux'
import { AppState } from '../../store/reducers'
import { RouterState } from 'connected-react-router'// 判断选中类名的钩子函数
function useActive(currentPath: string, path: string): string {return currentPath === path ? 'ant-menu-item-selected' : ''
}const Navigation = () => {const router = useSelector<AppState, RouterState>(state => state.router)const pathname = router.location.pathnameconst isHome = useActive(pathname, '/')const isShop = useActive(pathname, '/shop')const isSignin = useActive(pathname, '/signin')const isSignup = useActive(pathname, '/signup')return (<Menu mode="horizontal" selectable={false}><Menu.Item className={isHome}><Link to="/">首页</Link></Menu.Item><Menu.Item className={isShop}><Link to="/shop">商城</Link></Menu.Item><Menu.Item className={isSignin}><Link to="/signin">登录</Link></Menu.Item><Menu.Item className={isSignup}><Link to="/signup">注册</Link></Menu.Item></Menu>)
}export default Navigation

实现注册的 Redux 流程

和注册相关的 action

// src\store\actions\auth.action.ts
// action.type 常量
export const SIGNUP = 'SIGNUP' // 发送注册请求
export const SIGNUP_SUCCESS = 'SIGNUP_SUCCESS' // 注册成功
export const SIGNUP_FAIL = 'SIGNUP_FAIL' // 注册失败// action.payload 接口类型
export interface SignupPayload {email: stringname: stringpassword: string
}// action 对象接口类型
export interface SignupAction {type: typeof SIGNUPpayload: SignupPayload
}
export interface SignupSuccessAction {type: typeof SIGNUP_SUCCESS
}
export interface SignupFailAction {type: typeof SIGNUP_FAILmessage: string
}// actionCreator
export const signup = (payload: SignupPayload): SignupAction => ({type: SIGNUP,payload
})
export const signupSuccess = (): SignupSuccessAction => ({type: SIGNUP_SUCCESS
})
export const signupFail = (message: string): SignupFailAction => ({type: SIGNUP_FAIL,message
})// action 的联合类型
export type AuthUnionType = SignupAction | SignupSuccessAction | SignupFailAction

定义 reducer

// src\store\reducers\auth.reducer.ts
import { AuthUnionType, SIGNUP, SIGNUP_FAIL, SIGNUP_SUCCESS } from '../actions/auth.action'// state 接口类型
export interface AuthState {signup: {loaded: booleansuccess: booleanmessage: string}
}// state 默认值
const initialState: AuthState = {signup: {loaded: false, // 注册请求是否结束success: false, // 注册是否成功message: '' // 注册失败提示}
}export default function authReducer(state = initialState, action: AuthUnionType) {switch (action.type) {// 发送注册请求case SIGNUP:return {...state,signup: {loaded: false,success: false}}// 注册成功case SIGNUP_SUCCESS:return {...state,signup: {loaded: true,success: true}}// 注册失败case SIGNUP_FAIL:return {...state,signup: {loaded: true,success: false,message: action.message}}default:return state}
}
// src\store\reducers\index.ts
import { connectRouter, RouterState } from 'connected-react-router'
import { History } from 'history'
import { combineReducers } from 'redux'
import authReducer, { AuthState } from './auth.reducer'
// import testReducer from './test.reducer'// 定义一个包含 router 的 store 类型接口 供外部使用
export interface AppState {router: RouterStateauth: AuthState
}const createRootReducer = (history: History) =>combineReducers({// test: testReducer,router: connectRouter(history),auth: authReducer})export default createRootReducer

定义 saga 接收请求

// src\store\sagas\auth.saga.ts
import axios from 'axios'
import { takeEvery, put } from 'redux-saga/effects'
import { API } from '../../config'
import { SIGNUP, SignupAction, signupFail, signupSuccess } from '../actions/auth.action'function* handleSignup(action: SignupAction) {try {const response = yield axios.post(`${API}/signup`, action.payload)yield put(signupSuccess())} catch (error) {yield put(signupFail(error.response.data.errors[0]))}
}export default function* authSaga() {yield takeEvery(SIGNUP, handleSignup)
}
// src\store\sagas\index.ts
import { all } from 'redux-saga/effects'
import authSaga from './auth.saga'export default function* rootSaga() {yield all([authSaga()])
}
// src\store\index.ts
import { applyMiddleware, createStore } from 'redux'
import createRootReducer from './reducers'
import { createHashHistory } from 'history'
import { routerMiddleware } from 'connected-react-router'
import { composeWithDevTools } from 'redux-devtools-extension'
import createSagaMiddleware from '@redux-saga/core'
import rootSaga from './sagas'export const history = createHashHistory()const sagaMiddleware = createSagaMiddleware()const store = createStore(createRootReducer(history),composeWithDevTools(applyMiddleware(routerMiddleware(history), sagaMiddleware))
)sagaMiddleware.run(rootSaga)export default store

修改页面组件

// src\components\core\Signup.tsx
import Layout from './Layout'
import { Button, Form, Input } from 'antd'
import { signup, SignupPayload } from '../../store/actions/auth.action'
import { useDispatch } from 'react-redux'const Signup = () => {// 获取 dispatch 方法const dispatch = useDispatch()// 注册表单提交const onFinish = (value: SignupPayload) => {// 发送注册请求dispatch(signup(value))}return (<Layout title="注册" subTitle="还没有账号?注册一个吧"><Form onFinish={onFinish}><Form.Item name="name" label="昵称"><Input /></Form.Item><Form.Item name="password" label="密码"><Input.Password /></Form.Item><Form.Item name="email" label="邮箱"><Input /></Form.Item><Form.Item><Button type="primary" htmlType="submit">注册</Button></Form.Item></Form></Layout>)
}export default Signup

处理注册结果

实现内容

  1. 注册成功 清空表单
  2. 注册成功 显示成功的提示信息
  3. 注册失败 显示失败的提示信息
  4. 离开页面之前 重置状态
    • 注册状态如果未重置,返回该页面仍然会显示注册结果和提示

刷新表单和显示提示信息

// src\components\core\Signup.tsx
import Layout from './Layout'
import { Button, Form, Input, Result } from 'antd'
import { signup, SignupPayload } from '../../store/actions/auth.action'
import { useDispatch, useSelector } from 'react-redux'
import { AppState } from '../../store/reducers'
import { AuthState } from '../../store/reducers/auth.reducer'
import { useEffect } from 'react'
import { Link } from 'react-router-dom'const Signup = () => {// 获取 dispatch 方法const dispatch = useDispatch()// 获取注册结果const auth = useSelector<AppState, AuthState>(state => state.auth)// 创建表单数据域// 用于绑定到 Form 组件上操作表单const [form] = Form.useForm()// 注册表单提交const onFinish = (value: SignupPayload) => {// 发送注册请求dispatch(signup(value))}// 监听状态useEffect(() => {// 1. 注册成功 清空表单if (auth.signup.loaded && auth.signup.success) {form.resetFields()}}, [auth])// 2. 注册成功 显示成功的提示信息const showSuccess = () => {if (auth.signup.loaded && auth.signup.success) {return (<Resultstatus="success"title="注册成功"extra={[<Button type="primary"><Link to="/signin">登录</Link></Button>]}/>)}}// 3. 注册失败 显示失败的提示信息const showError = () => {if (auth.signup.loaded && !auth.signup.success) {return <Result status="warning" title="注册失败" subTitle={auth.signup.message} />}}// 4. 离开页面之前 重置页面状态useEffect(() => {// 离开页面时会执行这个方法return () => {// 稍后写}}, [])// 注册表单const signupForm = () => (<Form form={form} onFinish={onFinish}><Form.Item name="name" label="昵称"><Input /></Form.Item><Form.Item name="password" label="密码"><Input.Password /></Form.Item><Form.Item name="email" label="邮箱"><Input /></Form.Item><Form.Item><Button type="primary" htmlType="submit">注册</Button></Form.Item></Form>)return (<Layout title="注册" subTitle="还没有账号?注册一个吧">{showSuccess()}{showError()}{signupForm()}</Layout>)
}export default Signup

重置注册状态

定义 action

// src\store\actions\auth.action.ts
// action.type 常量
export const SIGNUP = 'SIGNUP' // 发送注册请求
export const SIGNUP_SUCCESS = 'SIGNUP_SUCCESS' // 注册成功
export const SIGNUP_FAIL = 'SIGNUP_FAIL' // 注册失败
export const RESET_SIGNUP = 'RESET_SIGNUP' // 重置注册状态// action.payload 接口类型
export interface SignupPayload {email: stringname: stringpassword: string
}// action 对象接口类型
export interface SignupAction {type: typeof SIGNUPpayload: SignupPayload
}
export interface SignupSuccessAction {type: typeof SIGNUP_SUCCESS
}
export interface SignupFailAction {type: typeof SIGNUP_FAILmessage: string
}
export interface ResetSignupAction {type: typeof RESET_SIGNUP
}// actionCreator
export const signup = (payload: SignupPayload): SignupAction => ({type: SIGNUP,payload
})
export const signupSuccess = (): SignupSuccessAction => ({type: SIGNUP_SUCCESS
})
export const signupFail = (message: string): SignupFailAction => ({type: SIGNUP_FAIL,message
})
export const resetSignup = (): ResetSignupAction => ({type: RESET_SIGNUP
})// action 的联合类型
export type AuthUnionType = SignupAction | SignupSuccessAction | SignupFailAction | ResetSignupAction

定义 reducer

// src\store\reducers\auth.reducer.ts
import { AuthUnionType, RESET_SIGNUP, SIGNUP, SIGNUP_FAIL, SIGNUP_SUCCESS } from '../actions/auth.action'// state 接口类型
export interface AuthState {signup: {loaded: booleansuccess: booleanmessage: string}
}// state 默认值
const initialState: AuthState = {signup: {loaded: false, // 注册请求是否结束success: false, // 注册是否成功message: '' // 注册失败提示}
}export default function authReducer(state = initialState, action: AuthUnionType) {switch (action.type) {// 发送注册请求case SIGNUP:return {...state,signup: {loaded: false,success: false}}// 注册成功case SIGNUP_SUCCESS:return {...state,signup: {loaded: true,success: true}}// 注册失败case SIGNUP_FAIL:return {...state,signup: {loaded: true,success: false,message: action.message}}// 重置注册状态case RESET_SIGNUP:return {...state,signup: {loaded: false,success: false,message: ''}}default:return state}
}

执行重置操作

// src\components\core\Signup.tsx
import Layout from './Layout'
import { Button, Form, Input, Result } from 'antd'
import { resetSignup, signup, SignupPayload } from '../../store/actions/auth.action'
import { useDispatch, useSelector } from 'react-redux'
import { AppState } from '../../store/reducers'
import { AuthState } from '../../store/reducers/auth.reducer'
import { useEffect } from 'react'
import { Link } from 'react-router-dom'const Signup = () => {// 获取 dispatch 方法const dispatch = useDispatch()// 获取注册结果const auth = useSelector<AppState, AuthState>(state => state.auth)// 创建表单数据域// 用于绑定到 Form 组件上操作表单const [form] = Form.useForm()// 注册表单提交const onFinish = (value: SignupPayload) => {// 发送注册请求dispatch(signup(value))}// 监听状态useEffect(() => {// 1. 注册成功 清空表单if (auth.signup.loaded && auth.signup.success) {form.resetFields()}}, [auth])// 2. 注册成功 显示成功的提示信息const showSuccess = () => {if (auth.signup.loaded && auth.signup.success) {return (<Resultstatus="success"title="注册成功"extra={[<Button type="primary"><Link to="/signin">登录</Link></Button>]}/>)}}// 3. 注册失败 显示失败的提示信息const showError = () => {if (auth.signup.loaded && !auth.signup.success) {return <Result status="warning" title="注册失败" subTitle={auth.signup.message} />}}// 4. 离开页面之前 重置页面状态useEffect(() => {// 离开页面时会执行这个方法return () => {dispatch(resetSignup())}}, [])// 注册表单const signupForm = () => (<Form form={form} onFinish={onFinish}><Form.Item name="name" label="昵称"><Input /></Form.Item><Form.Item name="password" label="密码"><Input.Password /></Form.Item><Form.Item name="email" label="邮箱"><Input /></Form.Item><Form.Item><Button type="primary" htmlType="submit">注册</Button></Form.Item></Form>)return (<Layout title="注册" subTitle="还没有账号?注册一个吧">{showSuccess()}{showError()}{signupForm()}</Layout>)
}export default Signup

实现登录的 Redux 流程

和登录相关的 action

// src\store\actions\auth.action.ts/*** 注册*/.../*** 登录*/export const SIGNIN = 'SIGNIN' // 发送登录请求
export const SIGNIN_SUCCESS = 'SIGNIN_SUCCESS' // 登录成功
export const SIGNIN_FAIL = 'SIGNIN_FAIL' // 登录失败export interface SigninPayload {email: stringpassword: string
}export interface SigninAction {type: typeof SIGNINpayload: SigninPayload
}export interface SigninSuccessAction {type: typeof SIGNIN_SUCCESS
}export interface SigninFailAction {type: typeof SIGNIN_FAILmessage: string
}export const signin = (payload: SigninPayload): SigninAction => ({type: SIGNIN,payload
})export const signinSuccess = (): SigninSuccessAction => ({type: SIGNIN_SUCCESS
})export const signinFail = (message: string): SigninFailAction => ({type: SIGNIN_FAIL,message
})// action 的联合类型
export type AuthUnionType =| SignupAction| SignupSuccessAction| SignupFailAction| ResetSignupAction| SigninAction| SigninSuccessAction| SigninFailAction

定义 reducer

// src\store\reducers\auth.reducer.ts
import {AuthUnionType,RESET_SIGNUP,SIGNIN,SIGNIN_FAIL,SIGNIN_SUCCESS,SIGNUP,SIGNUP_FAIL,SIGNUP_SUCCESS
} from '../actions/auth.action'// state 接口类型
export interface AuthState {signup: {loaded: booleansuccess: booleanmessage: string}signin: {loaded: booleansuccess: booleanmessage: string}
}// state 默认值
const initialState: AuthState = {signup: {loaded: false, // 注册请求是否结束success: false, // 注册是否成功message: '' // 注册失败提示},signin: {loaded: false, // 登录请求是否结束success: false, // 登录是否成功message: '' // 登录失败提示}
}export default function authReducer(state = initialState, action: AuthUnionType) {switch (action.type) {...// 发送登录请求case SIGNIN:return {...state,signin: {loaded: false,success: false,message: ''}}// 登录成功case SIGNIN_SUCCESS:return {...state,signin: {loaded: true,success: true,message: ''}}// 登录失败case SIGNIN_FAIL:return {...state,signin: {loaded: true,success: false,message: action.message}}default:return state}
}

定义 sage 接收请求

// src\store\sagas\auth.saga.ts
import axios, { AxiosResponse } from 'axios'
import { takeEvery, put } from 'redux-saga/effects'
import { API } from '../../config'
import {SIGNIN,SigninAction,signinFail,signinSuccess,SIGNUP,SignupAction,signupFail,signupSuccess
} from '../actions/auth.action'function* handleSignup(action: SignupAction) {try {yield axios.post(`${API}/signup`, action.payload)yield put(signupSuccess())} catch (error) {yield put(signupFail(error.response.data.errors[0]))}
}function* handleSignin(action: SigninAction) {try {const response: AxiosResponse = yield axios.post(`${API}/signin`, action.payload)// 存储令牌localStorage.setItem('jwt', JSON.stringify(response.data))yield put(signinSuccess())} catch (error) {yield put(signinFail(error.response.data.errors[0]))}
}export default function* authSaga() {// 注册yield takeEvery(SIGNUP, handleSignup)// 登录yield takeEvery(SIGNIN, handleSignin)
}

修改页面组件

// src\components\core\Signin.tsx
import Layout from './Layout'
import { Button, Form, Input } from 'antd'
import { signin, SigninPayload } from '../../store/actions/auth.action'
import { useDispatch } from 'react-redux'const Signin = () => {// 获取 dispatchconst dispatch = useDispatch()const onFinish = (value: SigninPayload) => {dispatch(signin(value))}return (<Layout title="登录" subTitle=""><Form onFinish={onFinish}><Form.Item name="email" label="邮箱"><Input /></Form.Item><Form.Item name="password" label="密码"><Input.Password /></Form.Item><Form.Item><Button type="primary" htmlType="submit">登录</Button></Form.Item></Form></Layout>)
}export default Signin

处理登录结果

实现内容

  1. 获取登录结果
  2. 登录失败 显示错误信息
  3. 登录成功 根据角色跳转到对应的管理页面
  4. 处理导航链接
    • 已登录
    • 隐藏[登录,注册]
    • 显示[dashboard]

获取登录结果和显示失败信息

// src\components\core\Signin.tsx
import Layout from './Layout'
import { Button, Form, Input, Result } from 'antd'
import { signin, SigninPayload } from '../../store/actions/auth.action'
import { useDispatch, useSelector } from 'react-redux'
import { AppState } from '../../store/reducers'
import { AuthState } from '../../store/reducers/auth.reducer'const Signin = () => {// 获取 dispatchconst dispatch = useDispatch()const onFinish = (value: SigninPayload) => {dispatch(signin(value))}// 1. 获取登录结果const auth = useSelector<AppState, AuthState>(state => state.auth)// 2. 登录失败 显示错误信息const showError = () => {if (auth.signin.loaded && !auth.signin.success) {return <Result status="warning" title="登录失败" subTitle={auth.signin.message} />}}// 3. 登录成功 根据角色跳转到对应的管理页面// 4. 处理导航链接: 已登录 隐藏[登录,注册] 显示[dashboard]// 登录表单const singinForm = () => (<Form onFinish={onFinish}><Form.Item name="email" label="邮箱"><Input /></Form.Item><Form.Item name="password" label="密码"><Input.Password /></Form.Item><Form.Item><Button type="primary" htmlType="submit">登录</Button></Form.Item></Form>)return (<Layout title="登录" subTitle="">{showError()}{singinForm()}</Layout>)
}export default Signin

登录成功跳转管理页面

定义一个判断是否登录的方法

首先定义一个 User 接口和 Jwt 接口便于其它地方使用:

// src\store\models\auth.ts
export interface User {_id: stringname: stringemail: stringrole: number
}export interface Jwt {token: stringuser: User
}

定义判断方法:

// src\helpers\auth.ts
import { Jwt } from '../store/models/auth'// 是否登录
export function isAuth(): boolean | Jwt {const jwt = localStorage.getItem('jwt')if (jwt) {return JSON.parse(jwt)}return false
}

跳转页面

// src\components\core\Signin.tsx
import Layout from './Layout'
import { Button, Form, Input, Result } from 'antd'
import { signin, SigninPayload } from '../../store/actions/auth.action'
import { useDispatch, useSelector } from 'react-redux'
import { AppState } from '../../store/reducers'
import { AuthState } from '../../store/reducers/auth.reducer'
import { isAuth } from '../../helpers/auth'
import { Jwt } from '../../store/models/auth'
import { Redirect } from 'react-router-dom'const Signin = () => {// 获取 dispatchconst dispatch = useDispatch()const onFinish = (value: SigninPayload) => {dispatch(signin(value))}// 1. 获取登录结果const auth = useSelector<AppState, AuthState>(state => state.auth)// 2. 登录失败 显示错误信息const showError = () => {if (auth.signin.loaded && !auth.signin.success) {return <Result status="warning" title="登录失败" subTitle={auth.signin.message} />}}// 3. 登录成功 根据角色跳转到对应的管理页面const redirectToDashboard = () => {const auth = isAuth()if (auth) {const {user: { role }} = auth as Jwtif (role === 0) {// 普通用户return <Redirect to="/user/dashboard" />} else {// 管理员return <Redirect to="/admin/dashboard" />}}}// 4. 处理导航链接: 已登录 隐藏[登录,注册] 显示[dashboard]// 登录表单const singinForm = () => (<Form onFinish={onFinish}><Form.Item name="email" label="邮箱"><Input /></Form.Item><Form.Item name="password" label="密码"><Input.Password /></Form.Item><Form.Item><Button type="primary" htmlType="submit">登录</Button></Form.Item></Form>)return (<Layout title="登录" subTitle="">{showError()}{redirectToDashboard()}{singinForm()}</Layout>)
}export default Signin

处理导航链接

// src\components\core\Navigation.tsx
import { Menu } from 'antd'
import { Link } from 'react-router-dom'
import { useSelector } from 'react-redux'
import { AppState } from '../../store/reducers'
import { RouterState } from 'connected-react-router'
import { isAuth } from '../../helpers/auth'
import { Jwt } from '../../store/models/auth'// 判断选中类名的钩子函数
function useActive(currentPath: string, path: string): string {return currentPath === path ? 'ant-menu-item-selected' : ''
}const Navigation = () => {const router = useSelector<AppState, RouterState>(state => state.router)const pathname = router.location.pathnameconst isHome = useActive(pathname, '/')const isShop = useActive(pathname, '/shop')const isSignin = useActive(pathname, '/signin')const isSignup = useActive(pathname, '/signup')const isDashboard = useActive(pathname, getDashboardUrl())function getDashboardUrl() {let url = '/user/dashboard'if (isAuth()) {const {user: { role }} = isAuth() as Jwtif (role === 1) {url = '/admin/dashboard'}}return url}return (<Menu mode="horizontal" selectable={false}><Menu.Item className={isHome}><Link to="/">首页</Link></Menu.Item><Menu.Item className={isShop}><Link to="/shop">商城</Link></Menu.Item>{!isAuth() && (<><Menu.Item className={isSignin}><Link to="/signin">登录</Link></Menu.Item><Menu.Item className={isSignup}><Link to="/signup">注册</Link></Menu.Item></>)}{isAuth() && (<Menu.Item className={isDashboard}><Link to={getDashboardUrl()}>dashboard</Link></Menu.Item>)}</Menu>)
}export default Navigation

创建 Dashboard 用户管理页面

创建普通用户管理页面

// src\components\admin\Dashboard.tsx
import Layout from '../core/Layout'const Dashboard = () => {return (<Layout title="用户 dashboard" subTitle="">Dashboard</Layout>)
}export default Dashboard

配置路由

// src\Routes.tsx
import { HashRouter, Route, Switch } from 'react-router-dom'
import Dashboard from './components/admin/Dashboard'
import Home from './components/core/Home'
import Shop from './components/core/Shop'
import Signin from './components/core/Signin'
import Signup from './components/core/Signup'const Routes = () => {return (<HashRouter><Switch><Route path="/" component={Home} exact /><Route path="/shop" component={Shop} /><Route path="/signin" component={Signin} /><Route path="/signup" component={Signup} /><Route path="/user/dashboard" component={Dashboard} /></Switch></HashRouter>)
}export default Routes

创建受保护的路由

dashboard 页面需要登陆后才能访问,要添加访问权限。

创建一个受保护的路由组件,限制只有登录后才能访问的页面:

// src\components\admin\PrivateRoute.tsx
import { Redirect, Route, RouteProps } from 'react-router-dom'
import { FC } from 'react'
import { isAuth } from '../../helpers/auth'interface PrivateRouteProps extends RouteProps {component: React.ComponentType<any>
}const PrivateRoute: FC<PrivateRouteProps> = ({ component: Component, ...rest }) => {return (<Route{...rest}render={props => {const auth = isAuth()if (auth) {return <Component {...props} />}return <Redirect to="/signin" />}}/>)
}export default PrivateRoute

替换原来的路由:

// src\Routes.tsx
import { HashRouter, Route, Switch } from 'react-router-dom'
import Dashboard from './components/admin/Dashboard'
import PrivateRoute from './components/admin/PrivateRoute'
import Home from './components/core/Home'
import Shop from './components/core/Shop'
import Signin from './components/core/Signin'
import Signup from './components/core/Signup'const Routes = () => {return (<HashRouter><Switch><Route path="/" component={Home} exact /><Route path="/shop" component={Shop} /><Route path="/signin" component={Signin} /><Route path="/signup" component={Signup} /><PrivateRoute path="/user/dashboard" component={Dashboard} /></Switch></HashRouter>)
}export default Routes

创建管理员管理页面

// src\components\admin\AdminDashboard.tsx
import Layout from '../core/Layout'const AdminDashboard = () => {return (<Layout title="管理员 dashboard" subTitle="">Dashboard</Layout>)
}export default AdminDashboard

创建管理员身份判断路由

// src\components\admin\AdminRoute.tsx
import { Redirect, Route, RouteProps } from 'react-router-dom'
import { FC } from 'react'
import { isAuth } from '../../helpers/auth'
import { Jwt } from '../../store/models/auth'interface PrivateRouteProps extends RouteProps {component: React.ComponentType<any>
}const AdminRoute: FC<PrivateRouteProps> = ({ component: Component, ...rest }) => {return (<Route{...rest}render={props => {const auth = isAuth() as Jwtif (auth && auth.user.role === 1) {return <Component {...props} />}return <Redirect to="/signin" />}}/>)
}export default AdminRoute

配置管理员管理页面路由

// src\Routes.tsx
import { HashRouter, Route, Switch } from 'react-router-dom'
import AdminDashboard from './components/admin/AdminDashboard'
import AdminRoute from './components/admin/AdminRoute'
import Dashboard from './components/admin/Dashboard'
import PrivateRoute from './components/admin/PrivateRoute'
import Home from './components/core/Home'
import Shop from './components/core/Shop'
import Signin from './components/core/Signin'
import Signup from './components/core/Signup'const Routes = () => {return (<HashRouter><Switch><Route path="/" component={Home} exact /><Route path="/shop" component={Shop} /><Route path="/signin" component={Signin} /><Route path="/signup" component={Signup} /><PrivateRoute path="/user/dashboard" component={Dashboard} /><AdminRoute path="/admin/dashboard" component={AdminDashboard} /></Switch></HashRouter>)
}export default Routes

完善管理员 Dashboard 页面

// src\components\admin\AdminDashboard.tsx
import { Col, Descriptions, Menu, Row, Typography } from 'antd'
import { Link } from 'react-router-dom'
import Layout from '../core/Layout'
import { ShoppingCartOutlined, UserOutlined, OrderedListOutlined } from '@ant-design/icons'
import { Jwt } from '../../store/models/auth'
import { isAuth } from '../../helpers/auth'const { Title } = Typographyconst AdminDashboard = () => {const {user: { name, email }} = isAuth() as Jwtconst adminLinks = () => (<><Title level={5}>管理员链接</Title><Menu style={{ borderRight: 0 }}><Menu.Item><ShoppingCartOutlined style={{ marginRight: '5px' }} /><Link to="">添加分类</Link></Menu.Item><Menu.Item><UserOutlined style={{ marginRight: '5px' }} /><Link to="">添加产品</Link></Menu.Item><Menu.Item><OrderedListOutlined style={{ marginRight: '5px' }} /><Link to="">订单列表</Link></Menu.Item></Menu></>)const adminInfo = () => (<Descriptions title="管理员信息" bordered><Descriptions.Item label="昵称">{name}</Descriptions.Item><Descriptions.Item label="邮箱">{email}</Descriptions.Item><Descriptions.Item label="角色">管理员</Descriptions.Item></Descriptions>)return (<Layout title="管理员 dashboard" subTitle=""><Row><Col span="4">{adminLinks()}</Col><Col span="20">{adminInfo()}</Col></Row></Layout>)
}export default AdminDashboard

这篇关于React+Redux+Ant Design+TypeScript 电子商务实战-客户端应用 02 登录注册的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1097935

相关文章

vite搭建vue3项目的搭建步骤

《vite搭建vue3项目的搭建步骤》本文主要介绍了vite搭建vue3项目的搭建步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录1.确保Nodejs环境2.使用vite-cli工具3.进入项目安装依赖1.确保Nodejs环境

Python版本信息获取方法详解与实战

《Python版本信息获取方法详解与实战》在Python开发中,获取Python版本号是调试、兼容性检查和版本控制的重要基础操作,本文详细介绍了如何使用sys和platform模块获取Python的主... 目录1. python版本号获取基础2. 使用sys模块获取版本信息2.1 sys模块概述2.1.1

Nginx搭建前端本地预览环境的完整步骤教学

《Nginx搭建前端本地预览环境的完整步骤教学》这篇文章主要为大家详细介绍了Nginx搭建前端本地预览环境的完整步骤教学,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录项目目录结构核心配置文件:nginx.conf脚本化操作:nginx.shnpm 脚本集成总结:对前端的意义很多

前端缓存策略的自解方案全解析

《前端缓存策略的自解方案全解析》缓存从来都是前端的一个痛点,很多前端搞不清楚缓存到底是何物,:本文主要介绍前端缓存的自解方案,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录一、为什么“清缓存”成了技术圈的梗二、先给缓存“把个脉”:浏览器到底缓存了谁?三、设计思路:把“发版”做成“自愈”四、代码

通过React实现页面的无限滚动效果

《通过React实现页面的无限滚动效果》今天我们来聊聊无限滚动这个现代Web开发中不可或缺的技术,无论你是刷微博、逛知乎还是看脚本,无限滚动都已经渗透到我们日常的浏览体验中,那么,如何优雅地实现它呢?... 目录1. 早期的解决方案2. 交叉观察者:IntersectionObserver2.1 Inter

Vue3视频播放组件 vue3-video-play使用方式

《Vue3视频播放组件vue3-video-play使用方式》vue3-video-play是Vue3的视频播放组件,基于原生video标签开发,支持MP4和HLS流,提供全局/局部引入方式,可监听... 目录一、安装二、全局引入三、局部引入四、基本使用五、事件监听六、播放 HLS 流七、更多功能总结在 v

Python爬虫HTTPS使用requests,httpx,aiohttp实战中的证书异步等问题

《Python爬虫HTTPS使用requests,httpx,aiohttp实战中的证书异步等问题》在爬虫工程里,“HTTPS”是绕不开的话题,HTTPS为传输加密提供保护,同时也给爬虫带来证书校验、... 目录一、核心问题与优先级检查(先问三件事)二、基础示例:requests 与证书处理三、高并发选型:

利用Python操作Word文档页码的实际应用

《利用Python操作Word文档页码的实际应用》在撰写长篇文档时,经常需要将文档分成多个节,每个节都需要单独的页码,下面:本文主要介绍利用Python操作Word文档页码的相关资料,文中通过代码... 目录需求:文档详情:要求:该程序的功能是:总结需求:一次性处理24个文档的页码。文档详情:1、每个

JS纯前端实现浏览器语音播报、朗读功能的完整代码

《JS纯前端实现浏览器语音播报、朗读功能的完整代码》在现代互联网的发展中,语音技术正逐渐成为改变用户体验的重要一环,下面:本文主要介绍JS纯前端实现浏览器语音播报、朗读功能的相关资料,文中通过代码... 目录一、朗读单条文本:① 语音自选参数,按钮控制语音:② 效果图:二、朗读多条文本:① 语音有默认值:②

vue监听属性watch的用法及使用场景详解

《vue监听属性watch的用法及使用场景详解》watch是vue中常用的监听器,它主要用于侦听数据的变化,在数据发生变化的时候执行一些操作,:本文主要介绍vue监听属性watch的用法及使用场景... 目录1. 监听属性 watch2. 常规用法3. 监听对象和route变化4. 使用场景附Watch 的