React+Redux+Ant Design+TypeScript 电子商务实战-客户端应用 05 购物车和订单

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

将产品添加到购物车

定义方法

// src\helpers\cart.ts
import { Product } from '../store/models/product'export interface CartItem extends Product {count: number
}// 将产品添加到购物车
export const addItem = (item: Product, next: () => void) => {let cart: CartItem[] = []if (typeof window !== 'undefined') {if (localStorage.getItem('cart')) {// `!` 非空断言cart = JSON.parse(localStorage.getItem('cart')!)}}const cartItem = cart.find(product => item._id === product._id)if (cartItem) {cartItem.count++} else {cart.push({...item,count: 1})}localStorage.setItem('cart', JSON.stringify(cart))// 执行回调next()
}

执行操作

// src\components\core\ProductItem.tsx
import { Button, Card, Col, Image, Row, Typography } from 'antd'
import { push } from 'connected-react-router'
import moment from 'moment'
import { useDispatch } from 'react-redux'
import { Link } from 'react-router-dom'
import { FC } from 'react'
import { API } from '../../config'
import { addItem } from '../../helpers/cart'
import { Product } from '../../store/models/product'const { Title, Paragraph } = Typographyinterface Props {product: ProductshowViewBtn?: booleanshowCartBtn?: boolean
}const ProductItem: FC<Props> = ({ product, showViewBtn = true, showCartBtn = true }) => {const dispatch = useDispatch()// 加入购物车const addToCart = () => {addItem(product, () => {// 添加完成后跳转到购物车页面// connected-react-router 通过向 store 派发动作的方式实现路由跳转// 可以在 store 中存储路由历史dispatch(push('/cart'))})}const showButtons = () => {const buttonArray = []if (showViewBtn) {buttonArray.push(<Button type="link"><Link to={`/product/${product._id}`}>查看详情</Link></Button>)}if (showCartBtn) {buttonArray.push(<Button type="link" onClick={addToCart}>加入购物车</Button>)}return buttonArray}return (<Cardcover={<Image src={`${API}/product/photo/${product._id}`} alt={product.name} preview={false} />}actions={showButtons()}><Title level={5}>{product.name}</Title><Paragraph ellipsis={{ rows: 2 }}>{product.description}</Paragraph><Row><Col span="12">销量:{product.sold}</Col><Col span="12" style={{ textAlign: 'right' }}>价格:¥{product.price}</Col></Row><Row><Col span="12">上架时间:{moment(product.createdAt).format('YYYY-MM-DD')}</Col><Col span="12" style={{ textAlign: 'right' }}>所属分类:{product.category.name}</Col></Row></Card>)
}export default ProductItem

构建购物车组件布局

添加获取购物车方法

// src\helpers\cart.ts
import { Product } from '../store/models/product'export interface CartItem extends Product {count: number
}// 将产品添加到购物车
export const addItem = (item: Product, next: () => void) => {let cart: CartItem[] = []if (typeof window !== 'undefined') {if (localStorage.getItem('cart')) {// `!` 非空断言cart = JSON.parse(localStorage.getItem('cart')!)}}const cartItem = cart.find(product => item._id === product._id)if (cartItem) {cartItem.count++} else {cart.push({...item,count: 1})}localStorage.setItem('cart', JSON.stringify(cart))// 执行回调next()
}// 获取本地购物车数据
export const getCart = () => {if (typeof window !== 'undefined') {if (localStorage.getItem('cart')) {return JSON.parse(localStorage.getItem('cart')!)}}return []
}

构建页面布局

// src\components\core\Cart.tsx
import { Row, Col } from 'antd'
import { useEffect, useState } from 'react'
import { CartItem as CartItemModel, getCart } from '../../helpers/cart'
import CartItem from './CartItem'
import Layout from './Layout'const Cart = () => {const [cart, setCart] = useState<CartItemModel[]>([])useEffect(() => {setCart(getCart())}, [])const showCart = () => (// 为了拆分组件,使用 Antd 的 Table 组件的结构,但不使用这个组件<table style={{ width: '100%' }}><thead className="ant-table-thead"><tr><th className="ant-table-cell">产品封面</th><th className="ant-table-cell">产品名称</th><th className="ant-table-cell">产品价格</th><th className="ant-table-cell">产品分类</th><th className="ant-table-cell">产品数量</th><th className="ant-table-cell">操作</th></tr></thead><tbody className="ant-table-tbody">{cart.map(item => (<CartItem product={item} />))}</tbody></table>)return (<Layout title="购物车" subTitle=""><Row gutter={16}><Col span="16">{showCart()}</Col><Col span="8"></Col></Row></Layout>)
}export default Cart
// src\components\core\CartItem.tsx
import { Button, Image, Input } from 'antd'
import { FC } from 'react'
import { API } from '../../config'
import { CartItem as CartItemModel } from '../../helpers/cart'interface Props {product: CartItemModel
}const CartItem: FC<Props> = ({ product }) => {return (<tr className="ant-table-row"><td className="ant-table-cell"><Image src={`${API}/product/photo/${product._id}`} width={120} alt={product.name} preview={false} /></td><td className="ant-table-cell">{product.name}</td><td className="ant-table-cell">{product.price}</td><td className="ant-table-cell">{product.category.name}</td><td className="ant-table-cell"><Input type="number" value={product.count} /></td><td className="ant-table-cell"><Button danger type="primary">删除</Button></td></tr>)
}export default CartItem

更改购物车产品数量

添加方法

// src\helpers\cart.ts
import { Product } from '../store/models/product'export interface CartItem extends Product {count: number
}...// 更改购物车中产品数量
export const updateItem = (productId: string, count: number) => {const cart: CartItem[] = getCart()const cartItem = cart.find(product => productId === product._id)if (cartItem) {cartItem.count = countlocalStorage.setItem('cart', JSON.stringify(cart))}return cart
}

修改数量并更新购物车信息

// src\components\core\CartItem.tsx
import { Button, Image, Input } from 'antd'
import { API } from '../../config'
import { CartItem as CartItemModel, updateItem } from '../../helpers/cart'
import { ChangeEvent, FC } from 'react'interface Props {product: CartItemModelsetCart: (arg: CartItemModel[]) => void
}const CartItem: FC<Props> = ({ product, setCart }) => {const handleChange = (event: ChangeEvent<HTMLInputElement>) => {const count = Math.max(parseInt(event.target.value), 1)setCart(updateItem(product._id, count))}return (<tr className="ant-table-row"><td className="ant-table-cell"><Image src={`${API}/product/photo/${product._id}`} width={120} alt={product.name} preview={false} /></td><td className="ant-table-cell">{product.name}</td><td className="ant-table-cell">{product.price}</td><td className="ant-table-cell">{product.category.name}</td><td className="ant-table-cell"><Input type="number" value={product.count} onChange={handleChange} /></td><td className="ant-table-cell"><Button danger type="primary">删除</Button></td></tr>)
}export default CartItem
// src\components\core\Cart.tsx
import { Row, Col } from 'antd'
import { useEffect, useState } from 'react'
import { CartItem as CartItemModel, getCart } from '../../helpers/cart'
import CartItem from './CartItem'
import Layout from './Layout'const Cart = () => {const [cart, setCart] = useState<CartItemModel[]>([])useEffect(() => {setCart(getCart())}, [])const showCart = () => (// 为了拆分组件,使用 Antd 的 Table 组件的结构,但不使用这个组件<table style={{ width: '100%' }}><thead className="ant-table-thead"><tr><th className="ant-table-cell">产品封面</th><th className="ant-table-cell">产品名称</th><th className="ant-table-cell">产品价格</th><th className="ant-table-cell">产品分类</th><th className="ant-table-cell">产品数量</th><th className="ant-table-cell">操作</th></tr></thead><tbody className="ant-table-tbody">{cart.map(item => (<CartItem key={item._id} setCart={setCart} product={item} />))}</tbody></table>)return (<Layout title="购物车" subTitle=""><Row gutter={16}><Col span="16">{showCart()}</Col><Col span="8"></Col></Row></Layout>)
}export default Cart

删除购物车中的产品

添加方法

// src\helpers\cart.ts
import { Product } from '../store/models/product'export interface CartItem extends Product {count: number
}...// 删除购物车中的产品
export const deleteItem = (productId: string) => {const cart: CartItem[] = getCart()const cartIndex = cart.findIndex(product => productId === product._id)if (cartIndex !== -1) {cart.splice(cartIndex, 1)localStorage.setItem('cart', JSON.stringify(cart))}return cart
}

修改页面

// src\components\core\CartItem.tsx
import { Button, Image, Input } from 'antd'
import { API } from '../../config'
import { CartItem as CartItemModel, deleteItem, updateItem } from '../../helpers/cart'
import { ChangeEvent, FC } from 'react'interface Props {product: CartItemModelsetCart: (arg: CartItemModel[]) => void
}const CartItem: FC<Props> = ({ product, setCart }) => {const handleChange = (event: ChangeEvent<HTMLInputElement>) => {const count = Math.max(parseInt(event.target.value), 1)setCart(updateItem(product._id, count))}return (<tr className="ant-table-row"><td className="ant-table-cell"><Image src={`${API}/product/photo/${product._id}`} width={120} alt={product.name} preview={false} /></td><td className="ant-table-cell">{product.name}</td><td className="ant-table-cell">{product.price}</td><td className="ant-table-cell">{product.category.name}</td><td className="ant-table-cell"><Input type="number" value={product.count} onChange={handleChange} /></td><td className="ant-table-cell"><Buttondangertype="primary"onClick={() => {setCart(deleteItem(product._id))}}>删除</Button></td></tr>)
}export default CartItem

计算商品总价

// src\components\core\TotalPrice.tsx
import { Typography } from 'antd'
import { FC, useEffect } from 'react'
import { CartItem } from '../../helpers/cart'interface Props {cart: CartItem[]setTotalPrice: (price: number) => void
}const { Title } = Typographyconst TotalPrice: FC<Props> = ({ cart, setTotalPrice }) => {const getTotalPrice = () => {return cart.reduce((total, cartItem) => {return total + cartItem.price * cartItem.count}, 0).toFixed(2)}useEffect(() => {setTotalPrice(parseFloat(getTotalPrice()))}, [cart])return <Title level={5}>商品总价:¥{getTotalPrice()}</Title>
}export default TotalPrice
// src\components\core\Cart.tsx
import { Row, Col, Input, Divider } from 'antd'
import { ChangeEvent, useEffect, useState } from 'react'
import { CartItem as CartItemModel, getCart } from '../../helpers/cart'
import CartItem from './CartItem'
import Layout from './Layout'
import TotalPrice from './TotalPrice'const Cart = () => {const [cart, setCart] = useState<CartItemModel[]>([])const [address, setAddress] = useState<string>('')const [totalPrice, setTotalPrice] = useState<number>(0)useEffect(() => {setCart(getCart())}, [])const showCart = () => (// 为了拆分组件,使用 Antd 的 Table 组件的结构,但不使用这个组件<table style={{ width: '100%' }}><thead className="ant-table-thead"><tr><th className="ant-table-cell">产品封面</th><th className="ant-table-cell">产品名称</th><th className="ant-table-cell">产品价格</th><th className="ant-table-cell">产品分类</th><th className="ant-table-cell">产品数量</th><th className="ant-table-cell">操作</th></tr></thead><tbody className="ant-table-tbody">{cart.map(item => (<CartItem key={item._id} setCart={setCart} product={item} />))}</tbody></table>)return (<Layout title="购物车" subTitle=""><Row gutter={16}><Col span="16">{showCart()}</Col><Col span="8"><Row><Inputplaceholder="请输入收货地址"value={address}onChange={(event: ChangeEvent<HTMLInputElement>) => setAddress(event.target.value)}/></Row><Divider /><Row><TotalPrice cart={cart} setTotalPrice={setTotalPrice} /></Row></Col></Row></Layout>)
}export default Cart

添加提交订单或登录按钮

// src\components\core\Pay.tsx
import { Button } from 'antd'
import { Link } from 'react-router-dom'
import { isAuth } from '../../helpers/auth'const Pay = () => {const showButton = () => {return isAuth() ? (<Button>提交订单</Button>) : (<Button><Link to="/signin">登录</Link></Button>)}return <>{showButton()}</>
}export default Pay
// src\components\core\Cart.tsx<Row><TotalPrice cart={cart} setTotalPrice={setTotalPrice} />
</Row>
<Row><Pay />
</Row>

订单支付成功后的提示页面组件

// src\components\core\Success.tsx
import { Button } from 'antd'
import { Link } from 'react-router-dom'
import Layout from './Layout'const Success = () => {return (<Layout title="支付完成" subTitle=""><Button><Link to="/">继续购物</Link></Button></Layout>)
}export default Success
// src\Routes.tsx
import { HashRouter, Route, Switch } from 'react-router-dom'
import AddCategory from './components/admin/AddCategory'
import AddProduct from './components/admin/AddProduct'
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 Cart from './components/core/Cart'
import Home from './components/core/Home'
import Product from './components/core/Product'
import Shop from './components/core/Shop'
import Signin from './components/core/Signin'
import Signup from './components/core/Signup'
import Success from './components/core/Success'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} /><AdminRoute path="/create/category" component={AddCategory} /><AdminRoute path="/create/product" component={AddProduct} /><Route path="/product/:productId" component={Product} /><Route path="/cart" component={Cart} /><Route path="/paysuccess" component={Success} /></Switch></HashRouter>)
}export default Routes

实现提交订单流程

  1. 获取支付宝收银台地址,并跳转
  2. 支付成功后跳转回客户端支付成功页面
  3. 支付宝服务器会向 ecommerce 服务端的 api 发送请求通知支付结果
    • 由于是支付宝服务器发送请求,所以不能指定本地地址(localhost),应该提供公网可以访问的地址,如本地服务的 IP,或部署服务端 API 的服务器的地址
// src\components\core\Pay.tsx
import { Button } from 'antd'
import axios from 'axios'
import { FC } from 'react'
import { Link } from 'react-router-dom'
import { API } from '../../config'
import { isAuth } from '../../helpers/auth'
import { CartItem } from '../../helpers/cart'
import { Jwt } from '../../store/models/auth'interface Props {totalPrice: numberaddress: stringcart: CartItem[]
}const Pay: FC<Props> = ({ totalPrice, address, cart }) => {const getPayUrl = () => {axios.post(`${API}/alipay`, {totalAmount: totalPrice,subject: '订单标题',body: '订单描述',// 测试时也要用 127.0.0.1returnUrl: 'http://127.0.0.1:3000/#/paysuccess',notifyUrl: '<服务端地址>/api/alipayNotifyUrl',address: address,products: cart.map(item => ({product: item._id,count: item.count})),userId: (isAuth() as Jwt).user._id}).then(response => {window.location.href = response.data.url}).catch(error => {console.error(error)})}const showButton = () => {return isAuth() ? (<Button onClick={getPayUrl}>提交订单</Button>) : (<Button><Link to="/signin">登录</Link></Button>)}return <>{showButton()}</>
}export default Pay

传递参数:

// src\components\core\Cart.tsx<Pay totalPrice={totalPrice} address={address} cart={cart} />

在导航栏添加购物车链接

获取购物车产品数量的方法

// src\helpers\cart.ts
import { Product } from '../store/models/product'...// 获取购物车产品数量
export const itemCount = () => {const cart: CartItem[] = getCart()return cart.length
}

添加链接

// src\components\core\Navigation.tsx
import { Badge, 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 isCart = useActive(pathname, '/cart')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><Menu.Item className={isCart}><Link to="/cart">购物车<Badge count={10} offset={[5, -10]} /></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

另一种方式存储共享状态

使用 createContext 创建共享状态的组件:

// src\anotherStore.tsx
import React, { Dispatch, FC, SetStateAction, useState } from 'react'
import { itemCount } from './helpers/cart'export const TotalContext = React.createContext<[number, Dispatch<SetStateAction<number>>]>([0, () => null])interface Props {children: React.ReactNode[] | React.ReactNode
}const AnotherStore: FC<Props> = ({ children }) => {const [count, setCount] = useState(itemCount())return <TotalContext.Provider value={[count, setCount]}>{children}</TotalContext.Provider>
}export default AnotherStore

包裹全部组件:

// 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'
import AnotherStore from './anotherStore'ReactDOM.render(<React.StrictMode><Provider store={store}><ConnectedRouter history={history}><AnotherStore><Routes /></AnotherStore></ConnectedRouter></Provider></React.StrictMode>,document.getElementById('root')
)

修改导航

// src\components\core\Navigation.tsx
import { Badge, 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'
import { useContext, useEffect } from 'react'
import { TotalContext } from '../../anotherStore'
import { itemCount } from '../../helpers/cart'// 判断选中类名的钩子函数
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 isCart = useActive(pathname, '/cart')const isDashboard = useActive(pathname, getDashboardUrl())// 获取 anotherStore 中的状态const [count, setCount] = useContext(TotalContext)// 每轮渲染后都获取购物车数量useEffect(() => {setCount(itemCount())})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><Menu.Item className={isCart}><Link to="/cart">购物车<Badge count={count} offset={[5, -10]} /></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

支付成功清空购物车

// src\helpers\cart.ts...// 清空购物车
export const clearCart = () => {if (typeof window !== 'undefined') {localStorage.removeItem('cart')}
}
// src\components\core\Success.tsx
import { Button } from 'antd'
import { useContext, useEffect } from 'react'
import { Link } from 'react-router-dom'
import { TotalContext } from '../../anotherStore'
import { clearCart } from '../../helpers/cart'
import Layout from './Layout'const Success = () => {const [count, setCount] = useContext(TotalContext)useEffect(() => {clearCart()setCount(0)})return (<Layout title="支付完成" subTitle=""><Button><Link to="/">继续购物</Link></Button></Layout>)
}export default Success

管理员订单列表页面

创建页面组件

// src\components\admin\Orders.tsx
import Layout from '../core/Layout'const Orders = () => {return (<Layout title="订单" subTitle="当前订单的数量是 10"><Title level={4}>订单号:{order.out_trade_no}</Title><table style={{ width: '100%' }}><thead className="ant-table-thead"><tr><th className="ant-table-cell">订单状态</th><th className="ant-table-cell">订单号</th><th className="ant-table-cell">总价</th><th className="ant-table-cell">创建时间</th><th className="ant-table-cell">邮寄地址</th><th className="ant-table-cell">客户姓名</th></tr></thead><tbody className="ant-table-tbody"><tr className="abt-table-row"><td className="ant-table-cell"></td><td className="ant-table-cell"></td><td className="ant-table-cell"></td><td className="ant-table-cell"></td><td className="ant-table-cell"></td><td className="ant-table-cell"></td></tr></tbody></table><table style={{ width: '100%' }}><thead className="ant-table-thead"><tr><th className="ant-table-cell">产品名称</th><th className="ant-table-cell">产品价格</th><th className="ant-table-cell">产品数量</th><th className="ant-table-cell">产品 ID</th></tr></thead><tbody className="ant-table-tbody"><tr className="abt-table-row"><td className="ant-table-cell"></td><td className="ant-table-cell"></td><td className="ant-table-cell"></td><td className="ant-table-cell"></td></tr></tbody></table></Layout>)
}export default Orders

配置路由

// src\Routes.tsx
import { HashRouter, Route, Switch } from 'react-router-dom'
import AddCategory from './components/admin/AddCategory'
import AddProduct from './components/admin/AddProduct'
import AdminDashboard from './components/admin/AdminDashboard'
import AdminRoute from './components/admin/AdminRoute'
import Dashboard from './components/admin/Dashboard'
import Orders from './components/admin/Orders'
import PrivateRoute from './components/admin/PrivateRoute'
import Cart from './components/core/Cart'
import Home from './components/core/Home'
import Product from './components/core/Product'
import Shop from './components/core/Shop'
import Signin from './components/core/Signin'
import Signup from './components/core/Signup'
import Success from './components/core/Success'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="/product/:productId" component={Product} /><Route path="/cart" component={Cart} /><Route path="/paysuccess" component={Success} /><PrivateRoute path="/user/dashboard" component={Dashboard} /><AdminRoute path="/admin/dashboard" component={AdminDashboard} /><AdminRoute path="/create/category" component={AddCategory} /><AdminRoute path="/create/product" component={AddProduct} /><AdminRoute path="/admin/orders" component={Orders} /></Switch></HashRouter>)
}export default Routes

配置连接

// src\components\admin\AdminDashboard.tsx
<Link to="/admin/orders">订单列表</Link>

获取订单列表数据

定义 interface

// src\store\models\order.ts
import { User } from './auth'
import { Product } from './product'export interface OrderProduct {_id: stringcount: numberproduct: Productsnapshot: Product
}export interface Order {status: string_id: stringout_trade_no: stringtrade_no: stringamount: numberaddress: stringproducts: OrderProduct[]user: UsercreatedAt: string
}

定义订单状态枚举

// src\helpers\status.ts// 订单状态
export const status: { [param: string]: string } = {Unpaid: '未付款',Paid: '已付款',Shipped: '运输中',Complete: '已完成',Cancelle: '已取消'
}

获取数据并展示

// src\components\admin\Orders.tsx
import { Divider, message, Select, Typography } from 'antd'
import axios from 'axios'
import moment from 'moment'
import React, { useEffect, useState } from 'react'
import { API } from '../../config'
import { isAuth } from '../../helpers/auth'
import { status } from '../../helpers/status'
import { Jwt } from '../../store/models/auth'
import { Order } from '../../store/models/order'
import Layout from '../core/Layout'const { Title } = Typographyconst Orders = () => {const {token,user: { _id: userId }} = isAuth() as Jwtconst [orders, setOrders] = useState([])useEffect(() => {async function getOrders() {try {const response = await axios.get(`${API}/orders/${userId}`, {headers: {Authorization: `Bearer ${token}`}})setOrders(response.data)} catch (error) {console.dir(error)message.error(error.response.data.errors[0])}}getOrders()}, [])// 页面副标题const getOrderCount = () => {if (orders.length > 0) {return `当前订单的数量是 ${orders.length}`} else {return `还没有订单`}}return (<Layout title="订单" subTitle={getOrderCount()}>{orders.map((order: Order) => (<React.Fragment key={order._id}><Title level={4}>订单号:{order.out_trade_no}</Title><table style={{ width: '100%' }}><thead className="ant-table-thead"><tr><th className="ant-table-cell">订单状态</th><th className="ant-table-cell">订单号</th><th className="ant-table-cell">总价</th><th className="ant-table-cell">创建时间</th><th className="ant-table-cell">邮寄地址</th><th className="ant-table-cell">客户姓名</th></tr></thead><tbody className="ant-table-tbody"><tr className="abt-table-row"><td className="ant-table-cell">{status[order.status]}</td><td className="ant-table-cell">{order.out_trade_no}</td><td className="ant-table-cell">{order.amount}</td><td className="ant-table-cell">{moment(order.createdAt).format('YYYY-MM-DD HH:mm:ss')}</td><td className="ant-table-cell">{order.address}</td><td className="ant-table-cell">{order.user.name}</td></tr></tbody></table><table style={{ width: '100%' }}><thead className="ant-table-thead"><tr><th className="ant-table-cell">产品名称</th><th className="ant-table-cell">产品价格</th><th className="ant-table-cell">产品数量</th><th className="ant-table-cell">产品 ID</th></tr></thead><tbody className="ant-table-tbody">{order.products.map(item => (<tr key={item._id} className="abt-table-row"><td className="ant-table-cell">{item.product.name}</td><td className="ant-table-cell">{item.product.price}</td><td className="ant-table-cell">{item.count}</td><td className="ant-table-cell">{item.product._id}</td></tr>))}</tbody></table><Divider /></React.Fragment>))}</Layout>)
}export default Orders

更改订单状态

// src\components\admin\Orders.tsx
import { Divider, message, Select, Typography } from 'antd'
import axios from 'axios'
import moment from 'moment'
import React, { useEffect, useState } from 'react'
import { API } from '../../config'
import { isAuth } from '../../helpers/auth'
import { status } from '../../helpers/status'
import { Jwt } from '../../store/models/auth'
import { Order } from '../../store/models/order'
import Layout from '../core/Layout'const { Title } = Typographyconst Orders = () => {const {token,user: { _id: userId }} = isAuth() as Jwtconst [orders, setOrders] = useState([])async function getOrders() {try {const response = await axios.get(`${API}/orders/${userId}`, {headers: {Authorization: `Bearer ${token}`}})setOrders(response.data)} catch (error) {console.dir(error)message.error(error.response.data.errors[0])}}useEffect(() => {getOrders()}, [])// 页面副标题const getOrderCount = () => {if (orders.length > 0) {return `当前订单的数量是 ${orders.length}`} else {return `还没有订单`}}// 变更订单状态const handleChange = (orderId: string) => (status: string) => {axios.put(`${API}/order/updateStatus/${userId}`,{orderId,status},{headers: {Authorization: `Bearer ${token}`}}).then(() => {getOrders()})}return (<Layout title="订单" subTitle={getOrderCount()}>{orders.map((order: Order) => (<React.Fragment key={order._id}><Title level={4}>订单号:{order.out_trade_no}</Title><table style={{ width: '100%' }}><thead className="ant-table-thead"><tr><th className="ant-table-cell">订单状态</th><th className="ant-table-cell">订单号</th><th className="ant-table-cell">总价</th><th className="ant-table-cell">创建时间</th><th className="ant-table-cell">邮寄地址</th><th className="ant-table-cell">客户姓名</th></tr></thead><tbody className="ant-table-tbody"><tr className="abt-table-row"><td className="ant-table-cell"><Select defaultValue={order.status} onChange={handleChange(order._id)}>{Object.entries(status).map(([value, label]) => (<Select.Option key={value} value={value}>{label}</Select.Option>))}</Select></td><td className="ant-table-cell">{order.out_trade_no}</td><td className="ant-table-cell">{order.amount}</td><td className="ant-table-cell">{moment(order.createdAt).format('YYYY-MM-DD HH:mm:ss')}</td><td className="ant-table-cell">{order.address}</td><td className="ant-table-cell">{order.user.name}</td></tr></tbody></table><table style={{ width: '100%' }}><thead className="ant-table-thead"><tr><th className="ant-table-cell">产品名称</th><th className="ant-table-cell">产品价格</th><th className="ant-table-cell">产品数量</th><th className="ant-table-cell">产品 ID</th></tr></thead><tbody className="ant-table-tbody">{order.products.map(item => (<tr key={item._id} className="abt-table-row"><td className="ant-table-cell">{item.product.name}</td><td className="ant-table-cell">{item.product.price}</td><td className="ant-table-cell">{item.count}</td><td className="ant-table-cell">{item.product._id}</td></tr>))}</tbody></table><Divider /></React.Fragment>))}</Layout>)
}export default Orders

这篇关于React+Redux+Ant Design+TypeScript 电子商务实战-客户端应用 05 购物车和订单的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Redis客户端连接机制的实现方案

《Redis客户端连接机制的实现方案》本文主要介绍了Redis客户端连接机制的实现方案,包括事件驱动模型、非阻塞I/O处理、连接池应用及配置优化,具有一定的参考价值,感兴趣的可以了解一下... 目录1. Redis连接模型概述2. 连接建立过程详解2.1 连php接初始化流程2.2 关键配置参数3. 最大连

Python标准库之数据压缩和存档的应用详解

《Python标准库之数据压缩和存档的应用详解》在数据处理与存储领域,压缩和存档是提升效率的关键技术,Python标准库提供了一套完整的工具链,下面小编就来和大家简单介绍一下吧... 目录一、核心模块架构与设计哲学二、关键模块深度解析1.tarfile:专业级归档工具2.zipfile:跨平台归档首选3.

SQL Server跟踪自动统计信息更新实战指南

《SQLServer跟踪自动统计信息更新实战指南》本文详解SQLServer自动统计信息更新的跟踪方法,推荐使用扩展事件实时捕获更新操作及详细信息,同时结合系统视图快速检查统计信息状态,重点强调修... 目录SQL Server 如何跟踪自动统计信息更新:深入解析与实战指南 核心跟踪方法1️⃣ 利用系统目录

使用IDEA部署Docker应用指南分享

《使用IDEA部署Docker应用指南分享》本文介绍了使用IDEA部署Docker应用的四步流程:创建Dockerfile、配置IDEADocker连接、设置运行调试环境、构建运行镜像,并强调需准备本... 目录一、创建 dockerfile 配置文件二、配置 IDEA 的 Docker 连接三、配置 Do

深入浅出SpringBoot WebSocket构建实时应用全面指南

《深入浅出SpringBootWebSocket构建实时应用全面指南》WebSocket是一种在单个TCP连接上进行全双工通信的协议,这篇文章主要为大家详细介绍了SpringBoot如何集成WebS... 目录前言为什么需要 WebSocketWebSocket 是什么Spring Boot 如何简化 We

java中pdf模版填充表单踩坑实战记录(itextPdf、openPdf、pdfbox)

《java中pdf模版填充表单踩坑实战记录(itextPdf、openPdf、pdfbox)》:本文主要介绍java中pdf模版填充表单踩坑的相关资料,OpenPDF、iText、PDFBox是三... 目录准备Pdf模版方法1:itextpdf7填充表单(1)加入依赖(2)代码(3)遇到的问题方法2:pd

Java Stream流之GroupBy的用法及应用场景

《JavaStream流之GroupBy的用法及应用场景》本教程将详细介绍如何在Java中使用Stream流的groupby方法,包括基本用法和一些常见的实际应用场景,感兴趣的朋友一起看看吧... 目录Java Stream流之GroupBy的用法1. 前言2. 基础概念什么是 GroupBy?Stream

python中列表应用和扩展性实用详解

《python中列表应用和扩展性实用详解》文章介绍了Python列表的核心特性:有序数据集合,用[]定义,元素类型可不同,支持迭代、循环、切片,可执行增删改查、排序、推导式及嵌套操作,是常用的数据处理... 目录1、列表定义2、格式3、列表是可迭代对象4、列表的常见操作总结1、列表定义是处理一组有序项目的

C#中的Converter的具体应用

《C#中的Converter的具体应用》C#中的Converter提供了一种灵活的类型转换机制,本文详细介绍了Converter的基本概念、使用场景,具有一定的参考价值,感兴趣的可以了解一下... 目录Converter的基本概念1. Converter委托2. 使用场景布尔型转换示例示例1:简单的字符串到

Spring Boot Actuator应用监控与管理的详细步骤

《SpringBootActuator应用监控与管理的详细步骤》SpringBootActuator是SpringBoot的监控工具,提供健康检查、性能指标、日志管理等核心功能,支持自定义和扩展端... 目录一、 Spring Boot Actuator 概述二、 集成 Spring Boot Actuat