创建应用路由及 Layput 组件

  1. src
  2. ├─ components
  3. ├─ Routes.js ------------------- 路由组件
  4. ├─ admin ----------------------- 后台文件夹
  5. └─ core ------------------------ 前台文件夹
  6. ├─ Home.js ------------------ 首页
  7. ├─ Layout.js ---------------- 公共组件
  8. └─ Shop.js ------------------ 商品列表
  9. ├─ config.js ---------------------- 全局变量配置文件
  10. └─ index.js ----------------------- 入口文件

全局 store 初始化

  • src/store/reducers/test.js

    1. export default function testReducer(state = 0) {
    2. return state
    3. }
  • src/store/reducers/index.js ```javascript import { combineReducers } from “redux”; import testReducer from “./test”;

const rootReducer = combineReducers({ test: testReducer })

export default rootReducer

  1. - src/store/index.js
  2. ```javascript
  3. import { createStore } from "redux";
  4. import rootReducer from "./reducers";
  5. const store = createStore(rootReducer)
  6. export default store

将路由状态同步到全局 store

  • 安装插件 npm install --save connected-react-router
  • https://www.npmjs.com/package/connected-react-router

  • src/store/reducers/index.js ```javascript import { connectRouter } from “connected-react-router”; import { combineReducers } from “redux”; import testReducer from “./test”;

const createRootReducer = (history) => combineReducers({ test: testReducer, router: connectRouter(history) })

export default createRootReducer

  1. - src/store/index.js
  2. ```javascript
  3. import { applyMiddleware , createStore } from "redux";
  4. import { createBrowserHistory } from 'history'
  5. import { routerMiddleware } from 'connected-react-router'
  6. import createRootReducer from "./reducers";
  7. export const history = createBrowserHistory()
  8. const store = createStore(
  9. createRootReducer(history),
  10. applyMiddleware(routerMiddleware(history))
  11. )
  12. export default store
  • src/index.js ```jsx import React from ‘react’; import ReactDOM from ‘react-dom’; import Routes from ‘./components/Routes’; import { Provider } from ‘react-redux’ import store, {history} from ‘./store’; import { ConnectedRouter } from ‘connected-react-router’

ReactDOM.render( , document.getElementById(‘root’) );

  1. - src/components/Routers.js
  2. ```jsx
  3. function Routes() {
  4. return (
  5. <Switch>
  6. <Route path='/' component={Home} exact/>
  7. <Route path='/shop' component={Shop} />
  8. </Switch>
  9. )
  10. }
  11. export default Routes

创建菜单栏

  • src/components/core/Navigation.js ```jsx import { Menu } from “antd” import { useSelector } from “react-redux” import { Link } from ‘react-router-dom’ function Navigation() { const router = useSelector(state => state.router) return (
    1. <Menu.Item key='/'>
    2. <Link to='/'>首页</Link>
    3. </Menu.Item>
    4. <Menu.Item key='/shop'>
    5. <Link to='/shop'>商城</Link>
    6. </Menu.Item>
    ) }

export default Navigation

  1. - src/components/core/Layout.js
  2. ```jsx
  3. import Navigation from "./Navigation";
  4. function Layout({children}) {
  5. return (
  6. <div>
  7. <Navigation />
  8. {children}
  9. </div>
  10. )
  11. }
  12. export default Layout

创建页头

  • src/components/core/Layout.js ```javascript import { PageHeader } from “antd”; import Navigation from “./Navigation”;

function Layout({children, title, subTitle}) { return (

{children}
) }

export default Layout

  1. - src/components/core/Home.js
  2. ```jsx
  3. import Layout from "./Layout"
  4. function Shop() {
  5. return <Layout title="拉勾严选商城列表" subTitle="挑选你喜欢的商品吧">Shop</Layout>
  6. }
  7. export default Shop

构建注册和登陆表单

  • src/components/core/Signup.js ```jsx import {Button, Form, Input }from ‘antd’ import Layout from ‘./Layout’ function Signup() { return (
    1. <Form>
    2. <Form.Item name='name' label='昵称'>
    3. <Input />
    4. </Form.Item>
    5. <Form.Item name='password' label='密码'>
    6. <Input.Password />
    7. </Form.Item>
    8. <Form.Item name='email' label='邮箱'>
    9. <Input />
    10. </Form.Item>
    11. <Form.Item>
    12. <Button type='primary' htmlType='submit'>注册</Button>
    13. </Form.Item>
    14. </Form>
    ) }

export default Signup

  1. - src/components/core/Signim.js
  2. ```jsx
  3. import { Button, Form, Input } from 'antd'
  4. import Layout from './Layout'
  5. function Signin() {
  6. return (
  7. <Layout title='登陆' subTitle=''>
  8. <Form>
  9. <Form.Item name='email' label='邮箱'>
  10. <Input />
  11. </Form.Item>
  12. <Form.Item name='password' label='密码'>
  13. <Input.Password />
  14. </Form.Item>
  15. <Form.Item>
  16. <Button type='primary' htmlType='submit'>登陆</Button>
  17. </Form.Item>
  18. </Form>
  19. </Layout>
  20. )
  21. }
  22. export default Signin
  • src/components/core/Layout.js ```jsx import { PageHeader } from “antd”; import Navigation from “./Navigation”;

function Layout({children, title, subTitle}) { return (

{children}
) }

export default Layout

  1. - src/components/Routes.js
  2. ```jsx
  3. import { Switch, Route} from 'react-router-dom'
  4. import Home from './core/Home'
  5. import Shop from './core/Shop'
  6. import Signin from './core/Signin'
  7. import Signup from './core/Signup'
  8. function Routes() {
  9. return (
  10. <Switch>
  11. <Route path='/' component={Home} exact/>
  12. <Route path='/shop' component={Shop} />
  13. <Route path='/signin' component={Signin} />
  14. <Route path='/signup' component={Signup} />
  15. </Switch>
  16. )
  17. }
  18. export default Routes
  • src/components/core/Navigation.js ```jsx import { Menu } from “antd” import { useSelector } from “react-redux” import { Link } from ‘react-router-dom’ function Navigation() { const router = useSelector(state => state.router) return (
    1. <Menu.Item key='/'>
    2. <Link to='/'>首页</Link>
    3. </Menu.Item>
    4. <Menu.Item key='/shop'>
    5. <Link to='/shop'>商城</Link>
    6. </Menu.Item>
    7. <Menu.Item key='/signin'>
    8. <Link to='/signin'>登陆</Link>
    9. </Menu.Item>
    10. <Menu.Item key='/signup'>
    11. <Link to='/signup'>注册</Link>
    12. </Menu.Item>
    ) }

export default Navigation

  1. <a name="Fpled"></a>
  2. ## 实现注册 Redux工作流
  3. <a name="HdMA2"></a>
  4. ### 创建 Action和 Reducer
  5. - src/store/actions/signup.js
  6. ```javascript
  7. import { createAction } from 'redux-actions'
  8. // 发起注册请求
  9. export const signup = createAction('signup')
  10. // 注册成功
  11. export const signup_success = createAction('signup_success')
  12. // 注册失败
  13. export const signup_fail = createAction('signup_fail')
  14. // 重置注册状态
  15. export const signup_reset = createAction('signup_reset')
  • src/store/reducers/signup.js ```javascript import { handleActions } from “redux-actions” import { signup, signup_fail, signup_reset, signup_success } from “../actions/signup”

const initialState = { loading: false, loaded: false, success: false, message: “” }

const signupReducer = handleActions( { [signup]: () => ({ loading: true, loaded: false, success: false, message: “” }), [signup_success]: () => ({ loading: false, loaded: true, success: true, message: “” }), [signup_fail]: (state, action) => ({ loading: false, loaded: true, success: false, message: action.payload.message }), [signup_reset]: () => ({ loading: false, loaded: false, success: false, message: “” }) }, initialState )

export default signupReducer

  1. - src/store/reducers/index.js
  2. ```javascript
  3. import signupReducer from "./signup";
  4. const createRootReducer = (history) =>
  5. combineReducers({
  6. signup: signupReducer
  7. })
  8. export default createRootReducer

配置 sagaMiddleware

启动本地服务器

  1. # 进入本地服务器更目录
  2. cd server
  3. # 安装依赖
  4. npm install
  5. # 启动数据库
  6. npm run serve
  7. # 启动服务端引用程序
  8. npm start
  • src/store/sagas/signup.js ```javascript import axios from ‘axios’ import { takeEvery, put} from ‘redux-saga/effects’ import {signup, signup_fail, signup_success} from ‘../actions/signup’ import { API } from ‘../../config’

function * handleSignup(action) { try { yield axios.post(${API}/signup, action.payload) yield put(signup_success()) } catch(ex) { yield put(signup_fail({message: ex.response.data.error})) } }

export default function * signupSaga() { yield takeEvery(signup, handleSignup) }

  1. - src/store/sagas/rootSaga.js
  2. ```javascript
  3. import { all } from 'redux-saga/effects'
  4. import signupSaga from './signup'
  5. export default function * rootSaga() {
  6. yield all([
  7. signupSaga()
  8. ])
  9. }
  • src/store/index.js ```javascript import { applyMiddleware, createStore } from “redux” import { createBrowserHistory } from “history” import createRootReducer from “./reducers” import { routerMiddleware } from “connected-react-router” import createSagaMiddleware from “redux-saga” import rootSaga from “./sagas/rootSaga”

export const history = createBrowserHistory()

const sagaMiddleware = createSagaMiddleware()

const store = createStore( createRootReducer(history), applyMiddleware(routerMiddleware(history), sagaMiddleware)

)

sagaMiddleware.run(rootSaga)

export default store

  1. <a name="vsnBR"></a>
  2. ## 验证注册 Redux工作流是否可以跑通
  3. - src/components/core/signup.js
  4. ```jsx
  5. import {Button, Form, Input }from 'antd'
  6. import Layout from './Layout'
  7. import { useDispatch } from 'react-redux'
  8. import {signup} from '../../store/actions/signup'
  9. function Signup() {
  10. const dispatch = useDispatch()
  11. // 获取状态
  12. // const {loading, loaded, sunccess, message} = useSelector(state => state.Signup)
  13. // 表单提交
  14. const handleOnFinish = value => {
  15. dispatch(signup(value))
  16. }
  17. return (
  18. <Layout title='注册' subTitle="">
  19. <Form onFinish={handleOnFinish}>
  20. </Form>
  21. </Layout>
  22. )
  23. }
  24. export default Signup
  • 添加 redux 调试工具所需代码 src/store/index.js ```javascript

import { composeWithDevTools } from ‘redux-devtools-extension’

const store = createStore(

composeWithDevTools( applyMiddleware(routerMiddleware(history), sagaMiddleware) ) )

  1. <a name="LOBT7"></a>
  2. ##
  3. <a name="w2s9O"></a>
  4. ## 将状态与注册组件进行结合
  5. - src/components/core/Signup.js
  6. ```jsx
  7. import {Button, Form, Input, Result, Spin }from 'antd'
  8. import Layout from './Layout'
  9. import { useDispatch, useSelector } from 'react-redux'
  10. import {signup, signup_reset} from '../../store/actions/signup'
  11. import { useEffect } from 'react'
  12. import { Link } from 'react-router-dom'
  13. function Signup() {
  14. const dispatch = useDispatch()
  15. // 获取状态
  16. const {loading, loaded, success, message} = useSelector(state => state.signup)
  17. // 表单提交
  18. const handleOnFinish = value => {
  19. dispatch(signup(value))
  20. }
  21. // 获取表单实例对象
  22. const [form] = Form.useForm()
  23. // 正在发送注册请求,显示loading
  24. const showLoading = () => {
  25. if (loading) return <Spin />
  26. }
  27. //注册成功 显示提示信息
  28. useEffect(() => {
  29. if (loaded && success) form.resetFields()
  30. }, [loaded, success])
  31. // 注册成功 显示成功的提示信息
  32. const showSuccess = () => {
  33. if(loaded && success) {
  34. return (
  35. <Result
  36. status='success'
  37. title="注册成功"
  38. subTitle=""
  39. extra={[
  40. <Button type='primary'>
  41. <Link to='/singin'>登陆</Link>
  42. </Button>
  43. ]}
  44. />
  45. )
  46. }
  47. }
  48. // 注册失败 显示失败提示信息
  49. const showError = () => {
  50. if(loaded && !success) {
  51. return <Result status='warning' title="注册失败" subTitle={message} />
  52. }
  53. }
  54. // 离开页面 重置注册
  55. useEffect(() => {
  56. return () => {
  57. dispatch(signup_reset())
  58. }
  59. }, [])
  60. // 注册表单
  61. const signupForm = () => (
  62. <Form form={form} onFinish={handleOnFinish}>
  63. <Form.Item name='name' label='昵称'>
  64. <Input />
  65. </Form.Item>
  66. <Form.Item name='password' label='密码'>
  67. <Input.Password />
  68. </Form.Item>
  69. <Form.Item name='email' label='邮箱'>
  70. <Input />
  71. </Form.Item>
  72. <Form.Item>
  73. <Button type='primary' htmlType='submit'>注册</Button>
  74. </Form.Item>
  75. </Form>
  76. )
  77. return (
  78. <Layout title='注册' subTitle="">
  79. {showLoading()}
  80. {showSuccess()}
  81. {showError()}
  82. {signupForm()}
  83. </Layout>
  84. )
  85. }
  86. export default Signup

实现用户登陆功能

  • src/components/core/Signin.js ```jsx import { Button, Form, Input } from “antd”; import Layout from “./Layout”; import axios from “axios”; import { API } from “../../config”; import { useHistory } from “react-router-dom”;

function Signin() { const history = useHistory() // 用户登陆 const handleOnFinish = async (value) => { // 发送登陆请求 let response = await axios.post(${API}/signin, value); // 将服务器端返回的信息存储到本地 localStorage.setItem(“jwt”, JSON.stringify(response.data)); // 根据用户角色返回跳转地址 const url = response.data.user.role === 1 ? “/admin/dashboard” : “/user/dashboard”; // 跳转页面 history.push(url); };

return (

); }

export default Signin;

  1. <a name="N8CN4"></a>
  2. ## 根据用户登陆状态显示菜单栏选项
  3. - src/helpers/auth.js
  4. ```javascript
  5. export function isAuth() {
  6. const jwt = localStorage.getItem('jwt')
  7. if(jwt) return JSON.parse(jwt)
  8. return false
  9. }
  • src/components/core/Navigation.js ```jsx import { Menu } from “antd” import { useSelector } from “react-redux” import { Link } from ‘react-router-dom’ import { isAuth } from ‘../../helpers/auth’ function Navigation() { const router = useSelector(state => state.router) const auth = isAuth() const showAuth = () => ( <>

    1. <Menu.Item key='/signin'>
    2. <Link to='/signin'>登陆</Link>
    3. </Menu.Item>
    4. <Menu.Item key='/signup'>
    5. <Link to='/signup'>注册</Link>
    6. </Menu.Item>

    </> )

    const showDashboard = () => { const url = auth.user.role === 1 ? ‘/admin/dashboard’ : ‘/user/dashboard’ return (

    1. <Menu.Item key={url}>
    2. <Link to={url}>Dashboard</Link>
    3. </Menu.Item>

    ) }

    return (

    1. <Menu.Item key='/'>
    2. <Link to='/'>首页</Link>
    3. </Menu.Item>
    4. <Menu.Item key='/shop'>
    5. <Link to='/shop'>商城</Link>
    6. </Menu.Item>
    7. {auth ? showDashboard() : showAuth()}

    ) }

export default Navigation

  1. <a name="w0aYG"></a>
  2. ## 创建受保护的 Dashboad组件
  3. - src/components/admin/PrivateUser.js
  4. ```jsx
  5. import { Redirect, Route} from 'react-router'
  6. import { isAuth } from '../../helpers/auth'
  7. function PrivateUser({component: Component, ...rest}) {
  8. return (
  9. <Route
  10. {...rest}
  11. render={props => {
  12. console.log(props)
  13. if (isAuth()) return <Component {...props} />
  14. return <Redirect to='/signin' />
  15. }}
  16. ></Route>
  17. )
  18. }
  19. export default PrivateUser
  • src/components/admin/UserDashboard.js ```jsx import Layout from ‘../core/Layout’

function UserDashboard() { return }

export default UserDashboard

  1. - src/components/admin/PrivateAdmin.js
  2. ```jsx
  3. import { Redirect, Route } from "react-router"
  4. import { isAuth } from "../../helpers/auth"
  5. function PrivateAdmin({ component: Component, ...rest }) {
  6. return (
  7. <Route
  8. {...rest}
  9. render={props => {
  10. const auth = isAuth()
  11. if (auth && auth.user.role === 1) return <Component {...props} />
  12. return <Redirect to="/signin" />
  13. }}
  14. ></Route>
  15. )
  16. }
  17. export default PrivateAdmin
  • src/components/admin/AdminDashboard.js ```jsx import Layout from ‘../core/Layout’

function AdminDashboard() { return }

export default AdminDashboard

  1. - src/components/Routes.js
  2. ```jsx
  3. import { Switch, Route} from 'react-router-dom'
  4. import AdminDashboard from './admin/AdminDashboard'
  5. import PrivateAdmin from './admin/PrivateAdmin'
  6. import PrivateUser from './admin/PrivateUser'
  7. import UserDashboard from './admin/UserDashboard'
  8. import Home from './core/Home'
  9. import Shop from './core/Shop'
  10. import Signin from './core/Signin'
  11. import Signup from './core/Signup'
  12. function Routes() {
  13. return (
  14. <Switch>
  15. <Route path='/' component={Home} exact/>
  16. <Route path='/shop' component={Shop} />
  17. <Route path='/signin' component={Signin} />
  18. <Route path='/signup' component={Signup} />
  19. <PrivateUser path='/user/dashboard' component={UserDashboard} />
  20. <PrivateAdmin path='/admin/dashboard' component={AdminDashboard} />
  21. </Switch>
  22. )
  23. }
  24. export default Routes

管理员 Dashboard 组件添加链接和管理员信息

  • src/components/admin/AdminDashboard.js ```jsx import { Col, Descriptions, Menu, Row, Typography } from ‘antd’ import { Link } from ‘react-router-dom’ import Layout from ‘../core/Layout’ import { OrderedListOutlined, ShoppingCartOutlined, UserOutlined } from ‘@ant-design/icons’ import { isAuth } from ‘../../helpers/auth’ const { Title } = Typography function AdminDashboard() {

    const {user: {name, email}} = isAuth() const adminLinks = () => ( <>

    1. <Title level={5}>管理员链接</Title>
    2. <Menu>
    3. <Menu.Item>
    4. <ShoppingCartOutlined />
    5. <Link to='/create/category'>添加分类</Link>
    6. </Menu.Item>
    7. <Menu.Item>
    8. <UserOutlined />
    9. <Link to='/create/product'>添加商品</Link>
    10. </Menu.Item>
    11. <Menu.Item>
    12. <OrderedListOutlined />
    13. <Link to='/admin/order'>订单列表</Link>
    14. </Menu.Item>
    15. </Menu>

    </> )

    const adminInfo = () => (

    1. <Descriptions.Item label='昵称'>{name}</Descriptions.Item>
    2. <Descriptions.Item label='邮箱'>{email}</Descriptions.Item>
    3. <Descriptions.Item label="角色">管理员</Descriptions.Item>

    )

    return

    1. <Col span='4'>{adminLinks()}</Col>
    2. <Col span='20'>{adminInfo()}</Col>

    }

export default AdminDashboard

  1. <a name="dbIO0"></a>
  2. ## 创建添加分类组件
  3. - src/components/admin/AddCategory.js
  4. ```jsx
  5. import {Button, Form, Input} from 'antd'
  6. import Layout from '../core/Layout'
  7. function AddCategory() {
  8. // 表单提交
  9. const handleOnFinish = value => {
  10. console.log(value)
  11. }
  12. return (
  13. <Layout title="添加分类">
  14. <Form onFinish={handleOnFinish}>
  15. <Form.Item name="name" label="分类名称">
  16. <Input />
  17. </Form.Item>
  18. <Form.Item>
  19. <Button type="primary" htmlType="submit">添加分类</Button>
  20. </Form.Item>
  21. </Form>
  22. </Layout>
  23. )
  24. }
  25. export default AddCategory
  • src/components/Routes.js ```javascript

import AddCategory from ‘./admin/AddCategory’

function Routes() { return (

  1. <PrivateAdmin path='/create/category' component={AddCategory} />
  2. </Switch>

) }

export default Routes

  1. <a name="dusfP"></a>
  2. ## 实现添加分类功能
  3. - src/components/admin/AddCategory.js
  4. ```jsx
  5. import {Button, Form, Input, message} from 'antd'
  6. import Layout from '../core/Layout'
  7. import axios from 'axios'
  8. import {API} from '../../config'
  9. import { isAuth } from '../../helpers/auth'
  10. import { Link } from 'react-router-dom'
  11. function AddCategory() {
  12. const {user:{_id}, token} = isAuth()
  13. // 表单提交
  14. const handleOnFinish = value => {
  15. if (value.name) {
  16. axios.post(`${API}/category/create/${_id}`,value, {
  17. headers: {
  18. Authorization: `Bearer ${token}`
  19. }
  20. }).then(response => {
  21. message.success(`${response.data.name} 添加成功`)
  22. })
  23. }
  24. }
  25. return (
  26. <Layout title="添加分类">
  27. <Form onFinish={handleOnFinish}>
  28. <Form.Item name="name" label="分类名称">
  29. <Input />
  30. </Form.Item>
  31. <Form.Item>
  32. <Button type="primary" htmlType="submit">添加分类</Button>
  33. </Form.Item>
  34. </Form>
  35. <Button>
  36. <Link to="/admin/dashboard">返回 Dashboard</Link>
  37. </Button>
  38. </Layout>
  39. )
  40. }
  41. export default AddCategory

创建添加商品组件

  • src/components/admin/AddProduct.js ```jsx import { UploadOutlined } from ‘@ant-design/icons’ import {Button, Form, Input, Select, Upload} from ‘antd’ import Layout from ‘../core/Layout’

function AddProduct() { return (

}>上传商品封面
) }

export default AddProduct

  1. - src/components/Router.js
  2. ```javascript
  3. import AddProduct from './admin/AddProduct'
  4. function Routes() {
  5. return (
  6. <Switch>
  7. <PrivateAdmin path='/create/product' component={AddProduct} />
  8. </Switch>
  9. )
  10. }
  11. export default Routes

获取分类列表

  • src/components/admin/AddProduct.js ```jsx import { UploadOutlined } from ‘@ant-design/icons’ import {Button, Form, Input, Select, Upload} from ‘antd’ import Layout from ‘../core/Layout’

import { useEffect, useState} from ‘react’ import axios from ‘axios’ import {API} from ‘../../config’

function AddProduct() { const [categories, setCategories ] = useState([]) useEffect(() => { async function getCategories() { let response = await axios.get(${API}/categories) setCategories(response.data) } getCategories() }, [])

return (

  1. <Form.Item label="商品分类" name="category">
  2. <Select defaultValue='-1'>
  3. <Select.Option value='-1'>请选择分类</Select.Option>
  4. {
  5. categories.map(({name, _id}) => (
  6. <Select.Option value={_id}>{name}</Select.Option>
  7. ))
  8. }
  9. </Select>
  10. </Form.Item>
  11. </Form>

) }

export default AddProduct

  1. <a name="jU15B"></a>
  2. ## 实现添加商品功能
  3. - src/components/admin/AddProduct.js
  4. ```jsx
  5. import { UploadOutlined } from '@ant-design/icons'
  6. import {Button, Form, Input, message, Select, Upload} from 'antd'
  7. import Layout from '../core/Layout'
  8. import { useEffect, useState} from 'react'
  9. import axios from 'axios'
  10. import {API} from '../../config'
  11. import { isAuth } from '../../helpers/auth'
  12. import { useHistory } from 'react-router-dom'
  13. function AddProduct() {
  14. const [categories, setCategories ] = useState([])
  15. const [file, setFile] = useState([])
  16. const history = useHistory()
  17. useEffect(() => {
  18. async function getCategories() {
  19. let response = await axios.get(`${API}/categories`)
  20. setCategories(response.data)
  21. }
  22. getCategories()
  23. }, [])
  24. const props = {
  25. beforeUpload (file) {
  26. setFile(file)
  27. return false
  28. }
  29. }
  30. const {user, token} = isAuth()
  31. const [form] = Form.useForm()
  32. const onFinish = value => {
  33. const formData = new FormData()
  34. for (let attr in value) {
  35. formData.append(attr, value[attr])
  36. }
  37. formData.append('photo', file)
  38. axios.post(`${API}/product/create/${user._id}`,formData, {
  39. headers: {
  40. Authorization: `Bearer ${token}`
  41. }
  42. }).then(() => {
  43. message.success('商品添加成功 ')
  44. form.resetFields()
  45. history.push("/admin/dashboard")
  46. })
  47. }
  48. return (
  49. <Layout title="添加商品">
  50. <Form form = {form} onFinish={onFinish} initialValues={{category: '-1'}}>
  51. <Form.Item>
  52. <Upload {...props}>
  53. <Button icon={<UploadOutlined />}>上传商品封面</Button>
  54. </Upload>
  55. </Form.Item>
  56. <Form.Item label="商品名称" name="name">
  57. <Input />
  58. </Form.Item>
  59. <Form.Item label="商品描述" name="description">
  60. <Input />
  61. </Form.Item>
  62. <Form.Item label="商品价格" name="price">
  63. <Input />
  64. </Form.Item>
  65. <Form.Item label="商品分类" name="category">
  66. <Select>
  67. <Select.Option value='-1'>请选择分类</Select.Option>
  68. {
  69. categories.map(({name, _id}) => (
  70. <Select.Option value={_id} key={_id}>{name}</Select.Option>
  71. ))
  72. }
  73. </Select>
  74. </Form.Item>
  75. <Form.Item label="商品数量" name="quantity">
  76. <Input />
  77. </Form.Item>
  78. <Form.Item label="是否需要运输" name="shipping">
  79. <Select>
  80. <Select.Option value="1"></Select.Option>
  81. <Select.Option value="0"></Select.Option>
  82. </Select>
  83. </Form.Item>
  84. <Form.Item>
  85. <Button type="primary" htmlType="submit">提交</Button>
  86. </Form.Item>
  87. </Form>
  88. </Layout>
  89. )
  90. }
  91. export default AddProduct

构建首页搜索框布局

  • src/components/core/Search.js ```jsx import { Button, Form, Input, Select } from ‘antd’

function Search() { return (

) }

export default Search

  1. - src/components/core/Home.js
  2. ```jsx
  3. import { useSelector } from "react-redux"
  4. import Layout from "./Layout"
  5. import Search from './Search'
  6. function Home() {
  7. const state = useSelector(state => state)
  8. return (
  9. <Layout title="拉勾严选首页" subTitle="尽情享受吧">
  10. <Search />
  11. </Layout>
  12. )
  13. }
  14. export default Home

构建搜索结果布局

  • src/components/core/ProductItem.js ```jsx import { Button, Card, Col, Row, Typography} from ‘antd’ import {Link} from ‘react-router-dom’

const {Title, Paragraph } = Typography

function ProductItem() { return ( 查看详情, ]} > 描述段落文本 价格: 销量: 所属分类: 上架时间: ) }

export default ProductItem

  1. - src/components/core/Search.js
  2. ```jsx
  3. import { Button, Col, Divider, Form, Input, Row, Select } from 'antd'
  4. import ProductItem from './ProductItem'
  5. function Search() {
  6. return (
  7. <>
  8. <Form layout="inline" initialValues={{category: "All"}}>
  9. </Form>
  10. <Divider />
  11. <Row gutter={[16, 16]}>
  12. <Col span="6">
  13. <ProductItem />
  14. </Col>
  15. </Row>
  16. </>
  17. )
  18. }
  19. export default Search
  • src/components/core/Home.js ```jsx import Layout from “./Layout” import Search from ‘./Search’ import { Col, Row, Typography } from ‘antd’ import ProductItem from ‘./ProductItem’

const {Title} = Typography

function Home() {

return ( ) }

export default Home

  1. <a name="kYxtA"></a>
  2. ## 完成首页获取商品列表的 redux 流程
  3. - src/store/actions/products.js
  4. ```javascript
  5. import { createAction } from 'redux-actions'
  6. export const get_products = createAction('get_products')
  7. export const get_products_success = createAction("get_products_success")
  • src/store/reducers/products.js ```javascript import { handleActions } from ‘redux-actions’ import { get_products_success } from ‘../actions/products’

const initialState = { sold: [], createdAt: [] } const productsReducer = handleActions( { [get_products_success]: (state, action) => ({ …state,

  1. [action.payload.sortBy]: action.payload.products
  2. })

}, initialState )

export default productsReducer

  1. - src/store/reducers/index.js
  2. ```javascript
  3. import productsReducer from "./products";
  4. const createRootReducer = (history) =>
  5. combineReducers({
  6. products: productsReducer
  7. })
  8. export default createRootReducer
  • src/store/sagas/products.js ```javascript import axios from ‘axios’ import { takeEvery, put} from ‘redux-saga/effects’ import {API} from ‘../../config’ import { get_products, get_products_success } from ‘../actions/products’

function * handleGetProducts(action) { let response = yield axios.get(${API}/products, {params: action.payload})

yield put( get_products_success({ sortBy: action.payload.sortBy, products: response.data }) ) }

export default function * productsSaga() { yield takeEvery(get_products, handleGetProducts) }

  1. - src/store/sagas/rootsaga.js
  2. ```javascript
  3. import { all } from 'redux-saga/effects'
  4. import productsSaga from './products'
  5. import signupSaga from './signup'
  6. export default function * rootSaga() {
  7. yield all([
  8. signupSaga(),
  9. productsSaga()
  10. ])
  11. }

商品列表数据展示

  • src/components/core/Home.js ```jsx

import Layout from “./Layout” import Search from ‘./Search’ import { Col, Row, Typography } from ‘antd’ import ProductItem from ‘./ProductItem’

import { useDispatch, useSelector} from ‘react-redux’ import { useEffect } from ‘react’ import { get_products } from “../../store/actions/products”

const {Title} = Typography

function Home() { const dispatch = useDispatch()

const {sold, createdAt} = useSelector(state => state.products)

useEffect(() => { dispatch(get_products({sortBy: ‘sold’, limit: 4, order: ‘desc’})) dispatch(get_products({sortBy: ‘createdAt’, limit: 4, order: ‘desc’})) }, [])

return ( {/ /} { createdAt.map(product => ( )) } { sold.map(product => ( )) } ) }

export default Home

  1. - src/components/core/ProductItem.js
  2. ```jsx
  3. import { Button, Card, Col, Row, Typography, Image} from 'antd'
  4. import {Link} from 'react-router-dom'
  5. import {API} from '../../config'
  6. import dateformat from 'dateformat'
  7. const {Title, Paragraph } = Typography
  8. function ProductItem({product}) {
  9. const { name, description, price, sold, category, createdAt, _id } = product
  10. return (
  11. <Card
  12. cover={
  13. <Image
  14. alt="example"
  15. src={`${API}/product/photo/${_id}`}
  16. />
  17. }
  18. actions={[
  19. <Button type="link"><Link to="">查看详情</Link></Button>,
  20. <Button type="link">加入购物车</Button>
  21. ]}
  22. >
  23. <Title level={5}>{name}</Title>
  24. <Paragraph ellipsis={{rows: 2}}>{description}</Paragraph>
  25. <Row style={{fontSize: 12}}>
  26. <Col span="12">价格:{price}</Col>
  27. <Col span="12">销量:{sold}</Col>
  28. </Row>
  29. <Row>
  30. <Col span="12">所属分类:{category.name}</Col>
  31. <Col span="12">上架时间:{dateformat(createdAt, "yyyy-mm-dd")}</Col>
  32. </Row>
  33. </Card>
  34. )
  35. }
  36. export default ProductItem

将获取商品分类列表抽离为公共逻辑

  • src/helpers/getCategories.js ```javascript import { useState, useEffect} from ‘react’ import axios from ‘axios’ import {API} from ‘../config’

export default function useGetCategories() { const [categories, setCategories] = useState([])

useEffect(() => { async function getCategories() { let response = await axios.get(${API}/categories) setCategories(response.data) } getCategories() }, [])

return categories }

  1. - src/components/admin/AddProduct.js
  2. ```jsx
  3. import useGetCategories from '../../helpers/getCategories'
  4. function AddProduct() {
  5. const [file, setFile] = useState([])
  6. const categories = useGetCategories()
  7. const history = useHistory()
  8. const props = {
  9. beforeUpload (file) {
  10. setFile(file)
  11. return false
  12. }
  13. }
  14. const {user, token} = isAuth()
  15. const [form] = Form.useForm()
  16. const onFinish = value => {
  17. }
  18. return (
  19. )
  20. }
  21. export default AddProduct
  • src/components/core/Search.js ```jsx import { Button, Col, Divider, Form, Input, Row, Select } from ‘antd’ import useGetCategories from “../../helpers/getCategories” import ProductItem from ‘./ProductItem’

function Search() {

const categories = useGetCategories()

return ( <>

{/ /} </> ) }

export default Search

  1. <a name="pCNMT"></a>
  2. ## 实现搜索功能
  3. - src/store/actions/search.js
  4. ```javascript
  5. import { createAction } from 'redux-actions'
  6. export const search_products = createAction("search_products")
  7. export const search_products_success = createAction("search_products_success")
  • src/store/reducers/search.js ```javascript import { handleActions } from ‘redux-actions’ import { search_products_success } from ‘../actions/search’

const initialState = { results: [] }

const searchReducer = handleActions( { [search_products_success]: (state, action) => ({ results: action.payload.results }) }, initialState )

export default searchReducer

  1. - src/store/reducers/index.js
  2. ```javascript
  3. import searchReducer from "./search";
  4. const createRootReducer = (history) =>
  5. combineReducers({
  6. search: searchReducer
  7. })
  8. export default createRootReducer
  • src/store/sagas/search.js ```javascript import {takeEvery, put} from ‘redux-saga/effects’ import axios from ‘axios’ import {API} from ‘../../config’ import { search_products, search_products_success } from ‘../actions/search’

function * handleSearchProducts(action) { const response = yield axios.get(${API}/products/search, {params: action.payload})

yield put(search_products_success({results: response.data})) }

export default function * searchSaga() { yield takeEvery(search_products, handleSearchProducts) }

  1. - src/store/sagas/rootsaga.js
  2. ```javascript
  3. import { all } from 'redux-saga/effects'
  4. import productsSaga from './products'
  5. import searchSaga from './search'
  6. import signupSaga from './signup'
  7. export default function * rootSaga() {
  8. yield all([
  9. signupSaga(),
  10. productsSaga(),
  11. searchSaga()
  12. ])
  13. }
  • src/components/core/Search.js ```jsx import { Button, Col, Divider, Form, Input, Row, Select } from ‘antd’ import useGetCategories from “../../helpers/getCategories” import ProductItem from ‘./ProductItem’

import {useDispatch, useSelector} from ‘react-redux’ import { search_products } from ‘../../store/actions/search’

function Search() {

const dispatch = useDispatch() const { results } = useSelector(state => state.search) const categories = useGetCategories() const [form] = Form.useForm() const onFinish = value => { dispatch(search_products(value)) form.resetFields() }

return ( <>

{ results.map(product => ( )) } </> ) }

export default Search

  1. <a name="J3Hzw"></a>
  2. ## 构建商城页面布局
  3. - src/components/core/Shop.js
  4. ```jsx
  5. import Layout from "./Layout"
  6. import { Col, Row, Space } from 'antd'
  7. import FilterByCategory from "./FilterByCategory"
  8. import FilterByPrice from "./FilterByPrice"
  9. function Shop() {
  10. return (
  11. <Layout title="拉勾严选商城列表" subTitle="挑选你喜欢的商品吧">
  12. <Row>
  13. <Col span="4">
  14. <Space size="middle" direction="vertical">
  15. <FilterByCategory />
  16. <FilterByPrice />
  17. </Space>
  18. </Col>
  19. <Col span="20">right</Col>
  20. </Row>
  21. </Layout>
  22. )
  23. }
  24. export default Shop
  • src/components/core/FilterByCategory.js ```jsx

import {Checkbox, Typography, List} from ‘antd’ import useGetCategories from ‘../../helpers/getCategories’

const { Title } = Typography

function FilterByCategory() { const categories = useGetCategories() return ( <> ( {item.name} )} /> </> ) }

export default FilterByCategory

  1. - src/components/core/FirlterByPrice.js
  2. ```jsx
  3. import {Radio, Typography, List} from 'antd'
  4. const { Title } = Typography
  5. function FilterByPrice() {
  6. const prices = [{name: '0-20', id:1}, {name: '20-40', id:2}]
  7. return (
  8. <>
  9. <Title level={5}>按照价格筛选</Title>
  10. <Radio.Group>
  11. <List
  12. dataSource={prices}
  13. renderItem={item => (
  14. <List.Item>
  15. <Radio value={item.id}>{item.name}</Radio>
  16. </List.Item>
  17. )}
  18. />
  19. </Radio.Group>
  20. </>
  21. )
  22. }
  23. export default FilterByPrice

收集用户选择的筛选条件

  • src/components/core/Shop.js ```jsx import Layout from “./Layout” import { Col, Row, Space } from ‘antd’ import FilterByCategory from “./FilterByCategory” import FilterByPrice from “./FilterByPrice” import { useEffect, useState } from “react”

function Shop() {

const [filters, setFilters] = useState({category: [], price: []})

useEffect(() => { console.log(filters) }, [filters]) return ( setFilters({…filters, category: filter})} /> setFilters({…filters, price: filter})} /> right ) }

export default Shop

  1. - src/components/core/FilterByCategory.js
  2. ```jsx
  3. import {Checkbox, Typography, List} from 'antd'
  4. import useGetCategories from '../../helpers/getCategories'
  5. const { Title } = Typography
  6. function FilterByCategory({ handleFilters }) {
  7. const categories = useGetCategories()
  8. const onChange = checkedValue => handleFilters(checkedValue)
  9. return (
  10. <>
  11. <Title level={5}>按照分类筛选</Title>
  12. <Checkbox.Group onChange={onChange}>
  13. <List
  14. dataSource={categories}
  15. renderItem={item => (
  16. <List.Item>
  17. <Checkbox value={item._id}>{item.name}</Checkbox>
  18. </List.Item>
  19. )}
  20. />
  21. </Checkbox.Group>
  22. </>
  23. )
  24. }
  25. export default FilterByCategory
  • src/components/core/FirlterByPrice.js ```jsx import {Radio, Typography, List} from ‘antd’ import prices from ‘../../helpers/price’

const { Title } = Typography

function FilterByPrice({handleFilters}) { const onChange = event => handleFilters(event.target.value) return ( <> ( {item.name} )} /> </> ) }

export default FilterByPrice

  1. <a name="zfy5V"></a>
  2. ## 展示筛选结果
  3. - src/store/actions/filter.js
  4. ```javascript
  5. import { createAction } from 'redux-actions'
  6. export const filter_products = createAction("filter_products")
  7. export const filter_products_success = createAction("filter_products_success")
  • src/store/reducers/filter.js ```javascript import { handleActions } from ‘redux-actions’ import { filter_products_success } from ‘../actions/filter’

const initialState = { size: 0, data: [] }

const filterReducer = handleActions( { [filter_products_success]: (state, action) => ({ size: action.payload.size, data: action.payload.skip === 0 ? action.payload.data : […state.data, …action.payload.data] }) }, initialState )

export default filterReducer

  1. - src/store/reducers/index.js
  2. ```javascript
  3. import filterReducer from "./filter";
  4. const createRootReducer = (history) =>
  5. combineReducers({
  6. filter: filterReducer
  7. })
  8. export default createRootReducer
  • src/store/sagas/filter.js ```javascript import axios from ‘axios’ import {takeEvery, put} from ‘redux-saga/effects’ import {API} from ‘../../config’ import { filter_products, filter_products_success } from ‘../actions/filter’

function * handleFilterProducts(action) { const response = yield axios.post(${API}/products/filter, action.payload)

yield put(filter_products_success({skip: action.payload.skip, …response.data})) }

export default function * filterSaga() { yield takeEvery(filter_products, handleFilterProducts) }

  1. - src/store/sagas/rootsaga.js
  2. ```javascript
  3. import { all } from 'redux-saga/effects'
  4. import filterSaga from './filter'
  5. import productsSaga from './products'
  6. import searchSaga from './search'
  7. import signupSaga from './signup'
  8. export default function * rootSaga() {
  9. yield all([
  10. signupSaga(),
  11. productsSaga(),
  12. searchSaga(),
  13. filterSaga()
  14. ])
  15. }
  • src/components/core/Shop.js ```jsx import Layout from “./Layout” import { Button, Col, Empty, Row, Space } from ‘antd’ import FilterByCategory from “./FilterByCategory” import FilterByPrice from “./FilterByPrice” import ProductItem from ‘./ProductItem’ import { useEffect, useState } from “react” import { useDispatch, useSelector} from ‘react-redux’ import { filter_products } from “../../store/actions/filter”

function Shop() { const [filters, setFilters] = useState({category: [], price: []}) const [skip, setSkip] = useState(0) const dispatch = useDispatch() const { data, size } = useSelector(state => state.filter)

useEffect(() => { setSkip(0) }, [filters])

useEffect(() => { dispatch(filter_products({filters, skip})) }, [filters, skip])

const loadMore = () => { setSkip(skip + 4) } return ( setFilters({…filters, category: filter})} /> setFilters({…filters, price: filter})} /> { data.map(product => ( )) } { size >= 4 ? () : } ) }

export default Shop

  1. <a name="WBHpt"></a>
  2. ## 构建商品详情组件布局
  3. - src/components/core/Product.js
  4. ```jsx
  5. import Layout from './Layout'
  6. import { Col, Row } from 'antd'
  7. import { useParams } from 'react-router-dom'
  8. function Product() {
  9. const { productId } = useParams()
  10. return (
  11. <Layout title="商品名称" subTitle="商品描述">
  12. <Row>
  13. <Col span="18">{productId}</Col>
  14. <Col span="6">right</Col>
  15. </Row>
  16. </Layout>
  17. )
  18. }
  19. export default Product
  • src/components/Router.js ```jsx

import Product from ‘./core/Product’

function Routes() { return (

  1. <PrivateAdmin path='/product/:productId' component={Product} />
  2. </Switch>

) }

export default Routes

  1. - src/components/core/ProductItem.js
  2. ```jsx
  3. <Button type="link"><Link to={`product/${_id}`}>查看详情</Link></Button>

根据产品 ID 获取产品详情 redux 流程

  • src/store/actions/product.js ```javascript import { createAction } from ‘redux-actions’

export const get_product_by_id = createAction(“get_product_by_id”) export const get_product_by_id_success = createAction(“get_product_by_id_success”)

  1. - src/store/reducers/product.js
  2. ```javascript
  3. import { handleActions } from 'redux-actions'
  4. import { get_product_by_id_success } from '../actions/product'
  5. const initialState = {}
  6. const productReducer = handleActions(
  7. {
  8. [get_product_by_id_success]: (state, action) => action.payload
  9. },
  10. initialState
  11. )
  12. export default productReducer
  • src/store/reducers/index.js ```javascript

import productReducer from “./product”;

const createRootReducer = (history) => combineReducers({

  1. product: productReducer

})

export default createRootReducer

  1. - src/store/sagas/product.js
  2. ```javascript
  3. import axios from 'axios'
  4. import { takeEvery, put } from 'redux-saga/effects'
  5. import { API } from '../../config'
  6. import { get_product_by_id, get_product_by_id_success } from '../actions/product'
  7. function * handleProductById(action) {
  8. const response = yield axios.get(`${API}/product/${action.payload.productId}`)
  9. yield put(get_product_by_id_success(response.data))
  10. }
  11. export default function * productSaga() {
  12. yield takeEvery(get_product_by_id, handleProductById)
  13. }
  • src/store/sagas/rootsaga.js ```javascript import { all } from ‘redux-saga/effects’ import filterSaga from ‘./filter’ import productSaga from ‘./product’ import productsSaga from ‘./products’ import searchSaga from ‘./search’ import signupSaga from ‘./signup’

export default function * rootSaga() { yield all([ signupSaga(), productsSaga(), searchSaga(), filterSaga(), productSaga() ]) }

  1. <a name="SkLKw"></a>
  2. ## 展示商品详情
  3. - src/components/core/Product.js
  4. ```jsx
  5. import Layout from './Layout'
  6. import { Col, Row } from 'antd'
  7. import { useParams } from 'react-router-dom'
  8. import { useDispatch, useSelector } from 'react-redux'
  9. import { useEffect } from 'react'
  10. import { get_product_by_id } from '../../store/actions/product'
  11. import ProductItem from './ProductItem'
  12. function Product() {
  13. const { productId } = useParams()
  14. const dispatch = useDispatch()
  15. const product = useSelector(state => state.product)
  16. useEffect(() => {
  17. dispatch(get_product_by_id({ productId }))
  18. }, [])
  19. return (
  20. <Layout title="商品名称" subTitle="商品描述">
  21. <Row>
  22. <Col span="18">
  23. {
  24. Object.keys(product).length > 0 && (
  25. <ProductItem product={product}
  26. imgStyle={{width: '50%', margin: "0 auto"}}
  27. showView={false}
  28. />
  29. )
  30. }
  31. </Col>
  32. <Col span="6">right</Col>
  33. </Row>
  34. </Layout>
  35. )
  36. }
  37. export default Product
  • src/components/core/ProductItem.js ```jsx

function ProductItem({product, imgStyle, showView=true, showChart=true}) { const { name, description, price, sold, category, createdAt, _id } = product

const showButtons = () => { const button = [] if(showView) { button.push() } if(showChart) { button.push() }

  1. return button

} return ( } actions={showButtons()} >

  1. </Card>

) }

export default ProductItem

  1. <a name="sPyZS"></a>
  2. ## 将商品添加到购物车中
  3. - src/helpers/cart.js
  4. ```javascript
  5. export function addItem(item, next) {
  6. let cart = []
  7. if(typeof window !== "undefined") {
  8. if (localStorage.getItem("cart")) {
  9. cart = JSON.parse(localStorage.getItem('cart'))
  10. }
  11. cart.push({...item, count: 1})
  12. cart = Array.from(new Set(cart.map(product => product._id)))
  13. .map(id => cart.find(product => product._id === id))
  14. localStorage.setItem('cart', JSON.stringify(cart))
  15. next && next()
  16. }
  17. }
  • src/components/core/Productitem.js ```jsx

import { addItem } from ‘../../helpers/cart’ import { useDispatch } from ‘react-redux’ import { push } from ‘connected-react-router’

const {Title, Paragraph } = Typography

function ProductItem({product, imgStyle, showView=true, showChart=true}) { const { name, description, price, sold, category, createdAt, _id } = product

const dispatch = useDispatch() const addToCart = () => { addItem(product, () => dispatch(push(‘/cart’))) }

const showButtons = () => { const button = [] if(showView) { button.push() } if(showChart) { button.push() }

  1. return button

} return (

) }

export default ProductItem

  1. <a name="Mc57k"></a>
  2. ## 展示本地购物车中商品
  3. - src/helpers/cart.js
  4. ```javascript
  5. export function getCart() {
  6. if (typeof window !== "undefined") {
  7. if (localStorage.getItem("cart")) {
  8. return JSON.parse(localStorage.getItem("cart"))
  9. }
  10. }
  11. return []
  12. }
  • src/components/core/Cart.js ```jsx import { useEffect, useState } from ‘react’ import { getCart } from ‘../../helpers/cart’ import Layout from ‘./Layout’ import { Col, Row } from ‘antd’ import CartItem from ‘./CartItem’

function Cart() { const [cart, setCart] = useState([])

useEffect(() => { setCart(getCart()) }, [])

const cartTable = () => (

{cart.map(product => ( ))}
商品封面 商品名称 商品价格 商品分类 商品数量 操作
) return ( {cartTable()} ) }

export default Cart

  1. - src/components/Routers.js
  2. ```jsx
  3. import Cart from './core/Cart'
  4. function Routes() {
  5. return (
  6. <Switch>
  7. <Route path='/cart' component={Cart}></Route>
  8. </Switch>
  9. )
  10. }
  11. export default Routes
  • src/components/core/CartItem.js ```jsx import { Button, Image, Input } from ‘antd’ import {API} from ‘../../config’

function CartItem({product, setCart}) { return ( 实战开发 - 图1 {product.name} {product.price} {product.category.name} ) }

export default CartItem

  1. <a name="rLG2w"></a>
  2. ## 更改购物车中商品的数量
  3. - src/helpers/cart.js
  4. ```javascript
  5. export function updateItem(productId, count) {
  6. let cart = []
  7. if (typeof window !== "undefined") {
  8. if (localStorage.getItem("cart")) {
  9. cart = JSON.parse(localStorage.getItem("cart"))
  10. const target = cart.find(product => product._id === productId)
  11. target.count = count
  12. localStorage.setItem("cart", JSON.stringify(cart))
  13. }
  14. }
  15. return cart
  16. }
  • src/components/core/CartItem.js ```jsx import { Button, Image, Input } from ‘antd’ import { useState } from ‘react’ import {API} from ‘../../config’ import { updateItem } from ‘../../helpers/cart’

function CartItem({product, setCart}) { const [count, setCount] = useState(product.count)

const handleChange = event => { const count = parseInt(event.target.value) setCount(count) setCart(updateItem(product._id, count)) } return ( 实战开发 - 图2 {product.name} {product.price} {product.category.name} ) }

export default CartItem

  1. <a name="w0i0f"></a>
  2. ## 删除购物车中的商品
  3. - src/helpers/cart.js
  4. ```javascript
  5. export function deleteItem(productId) {
  6. let cart = []
  7. if (typeof window !== "undefined") {
  8. if (localStorage.getItem("cart")) {
  9. cart = JSON.parse(localStorage.getItem("cart"))
  10. const index = cart.findIndex(product => product._id === productId)
  11. cart.splice(index, 1)
  12. localStorage.setItem("cart", JSON.stringify(cart))
  13. }
  14. }
  15. return cart
  16. }
  • src/components/core/CartItem.js
    1. <Button danger type="primary" onClick={() => setCart(deleteItem(product._id))}>删除</Button>

构建购物车右侧布局

  • src/components/core/Cart.js ```jsx import { useEffect, useState } from ‘react’ import { getCart } from ‘../../helpers/cart’ import Layout from ‘./Layout’ import { Col, Input, Row, Typography, Form} from ‘antd’ import CartItem from ‘./CartItem’

const { Title } = Typography

function Cart() { const [cart, setCart] = useState([]) const [address, setAddress] = useState(“”) const [totalPrice, setTotalPrice] = useState(0)

useEffect(() => { setCart(getCart()) }, [])

useEffect(() => { let totalPrice = cart.reduce((currentValue, nextValue) => { return (currentValue += nextValue.count * nextValue.price) }, 0).toFixed(2) setTotalPrice(totalPrice) }, [cart])

return ( {cartTable()} setAddress(event.target.value)} placeholder=”请输入收货地址”> ) }

export default Cart

  1. <a name="hV6GZ"></a>
  2. ## 增加支付按钮或登陆按钮
  3. - src/components/core/PayButtom.js
  4. ```jsx
  5. import { Button } from 'antd'
  6. import { Link } from 'react-router-dom'
  7. import { isAuth } from '../../helpers/auth'
  8. function PayButton ({address, cart, totalPrice}) {
  9. const showButton = () => {
  10. return isAuth() ? (<Button>提交</Button>) : (<Button><Link to='/signin'>登陆</Link></Button>)
  11. }
  12. return <div>{showButton()}</div>
  13. }
  14. export default PayButton
  • src/components/core/Cart.js
    1. <Layout title='购物车' subTitle="付款吧,我就是你的了">
    2. <Row gutter={16}>
    3. <Col span="18">{cartTable()}</Col>
    4. <Col span="6">
    5. <Form.Item label="收获地址">
    6. <Input
    7. value={address}
    8. onChange={event => setAddress(event.target.value)}
    9. placeholder="请输入收货地址"></Input>
    10. </Form.Item>
    11. <Title level={5}>总价:{totalPrice}</Title>
    12. <PayButton address={address} cart={cart} totalPrice={totalPrice} />
    13. </Col>
    14. </Row>
    15. </Layout>

实现支付创建订单

  • src/components/core/PayButton.js ```jsx import { Button } from ‘antd’ import axios from ‘axios’ import { Link } from ‘react-router-dom’ import { isAuth } from ‘../../helpers/auth’ import {API} from ‘../../config’ function PayButton ({address, cart, totalPrice}) {

    const handlePay = () => { axios.post(${API}/alipay,{

    1. totalAmount: totalPrice,
    2. subject: '测试订单标题',
    3. body: '测试订单描述',
    4. products: cart.map(product => ({
    5. product: product._id,
    6. count: product.count
    7. })),
    8. address: address,
    9. userId: isAuth().user._id

    }).then(response => {

    1. window.location.href = response.data.result

    }) }

    const showButton = () => { return isAuth() ? () : () } return

    {showButton()}
    }

export default PayButton

  1. <a name="hHt6X"></a>
  2. ## 获取并展示订单列表数据
  3. - src/components/admin/Order.js
  4. ```jsx
  5. import Layout from "../core/Layout"
  6. import { useState, useEffect } from "react"
  7. import axios from "axios"
  8. import { API } from "../../config"
  9. import { isAuth } from "../../helpers/auth"
  10. import { Divider, Typography, Select } from "antd"
  11. import dateformat from "dateformat"
  12. const { Title } = Typography
  13. function Order() {
  14. const [orders, setOrders] = useState([])
  15. const { user, token } = isAuth()
  16. async function loadOrders() {
  17. let { data } = await axios.get(`${API}/order/list/${user._id}`, {
  18. headers: {
  19. Authorization: `Bearer ${token}`
  20. }
  21. })
  22. setOrders(data)
  23. }
  24. useEffect(() => {
  25. loadOrders()
  26. }, [])
  27. const getOrderNumber = () => {
  28. return orders.length > 0
  29. ? `您的订单数量是${orders.length}`
  30. : "您还没有订单"
  31. }
  32. const showStatus = status => {
  33. switch (status) {
  34. case "Unpaid":
  35. return "未付款"
  36. case "Paid":
  37. return "已付款"
  38. case "Shipped":
  39. return "运输中"
  40. case "Completed":
  41. return "已完成"
  42. case "Cancelled":
  43. return "已取消"
  44. }
  45. }
  46. const handleStatus = orderId => status => {
  47. axios
  48. .put(
  49. `${API}/order/status/${user._id}`,
  50. { orderId, status },
  51. {
  52. headers: {
  53. Authorization: `Bearer ${token}`
  54. }
  55. }
  56. )
  57. .then(() => loadOrders())
  58. }
  59. return (
  60. <Layout title="订单列表" subTitle={getOrderNumber()}>
  61. {orders.map(order => (
  62. <>
  63. <Title level={5}>订单ID: {order._id}</Title>
  64. <table style={{ width: "100%" }}>
  65. <thead className="ant-table-thead">
  66. <tr>
  67. <th className="ant-table-cell">订单状态</th>
  68. <th className="ant-table-cell">订单号</th>
  69. <th className="ant-table-cell">总价</th>
  70. <th className="ant-table-cell">创建时间</th>
  71. <th className="ant-table-cell">邮寄地址</th>
  72. <th className="ant-table-cell">客户姓名</th>
  73. </tr>
  74. </thead>
  75. <tbody className="ant-table-tbody">
  76. <tr className="ant-table-row">
  77. <td className="ant-table-cell">
  78. {showStatus(order.status)}
  79. <Select
  80. defaultValue={order.status}
  81. onChange={handleStatus(order._id)}
  82. >
  83. <Select.Option value="Unpaid">未付款</Select.Option>
  84. <Select.Option value="Paid">已付款</Select.Option>
  85. <Select.Option value="Shipped">运输中</Select.Option>
  86. <Select.Option value="Completed">已完成</Select.Option>
  87. <Select.Option value="Cancelled">已取消</Select.Option>
  88. </Select>
  89. </td>
  90. <td className="ant-table-cell">{order.trade_no}</td>
  91. <td className="ant-table-cell">{order.amount}</td>
  92. <td className="ant-table-cell">
  93. {dateformat(order.createdAt, "yyyy-mm-dd")}
  94. </td>
  95. <td className="ant-table-cell">{order.address}</td>
  96. <td className="ant-table-cell">{order.user.name}</td>
  97. </tr>
  98. </tbody>
  99. </table>
  100. <table style={{ width: "100%" }}>
  101. <thead className="ant-table-thead">
  102. <tr>
  103. <th className="ant-table-cell">商品名称</th>
  104. <th className="ant-table-cell">商品价格</th>
  105. <th className="ant-table-cell">商品数量</th>
  106. <th className="ant-table-cell">商品ID</th>
  107. </tr>
  108. </thead>
  109. <tbody className="ant-table-tbody">
  110. {order.products.map(product => (
  111. <tr className="ant-table-row">
  112. <td className="ant-table-cell">{product.product.name}</td>
  113. <td className="ant-table-cell">{product.product.price}</td>
  114. <td className="ant-table-cell">{product.count}</td>
  115. <td className="ant-table-cell">{product.product._id}</td>
  116. </tr>
  117. ))}
  118. </tbody>
  119. </table>
  120. <Divider />
  121. </>
  122. ))}
  123. <pre>{JSON.stringify(orders, null, 2)}</pre>
  124. </Layout>
  125. )
  126. }
  127. export default Order