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

相关文章

nginx -t、nginx -s stop 和 nginx -s reload 命令的详细解析(结合应用场景)

《nginx-t、nginx-sstop和nginx-sreload命令的详细解析(结合应用场景)》本文解析Nginx的-t、-sstop、-sreload命令,分别用于配置语法检... 以下是关于 nginx -t、nginx -s stop 和 nginx -s reload 命令的详细解析,结合实际应

MySQL 多列 IN 查询之语法、性能与实战技巧(最新整理)

《MySQL多列IN查询之语法、性能与实战技巧(最新整理)》本文详解MySQL多列IN查询,对比传统OR写法,强调其简洁高效,适合批量匹配复合键,通过联合索引、分批次优化提升性能,兼容多种数据库... 目录一、基础语法:多列 IN 的两种写法1. 直接值列表2. 子查询二、对比传统 OR 的写法三、性能分析

Python办公自动化实战之打造智能邮件发送工具

《Python办公自动化实战之打造智能邮件发送工具》在数字化办公场景中,邮件自动化是提升工作效率的关键技能,本文将演示如何使用Python的smtplib和email库构建一个支持图文混排,多附件,多... 目录前言一、基础配置:搭建邮件发送框架1.1 邮箱服务准备1.2 核心库导入1.3 基础发送函数二、

PowerShell中15个提升运维效率关键命令实战指南

《PowerShell中15个提升运维效率关键命令实战指南》作为网络安全专业人员的必备技能,PowerShell在系统管理、日志分析、威胁检测和自动化响应方面展现出强大能力,下面我们就来看看15个提升... 目录一、PowerShell在网络安全中的战略价值二、网络安全关键场景命令实战1. 系统安全基线核查

PostgreSQL的扩展dict_int应用案例解析

《PostgreSQL的扩展dict_int应用案例解析》dict_int扩展为PostgreSQL提供了专业的整数文本处理能力,特别适合需要精确处理数字内容的搜索场景,本文给大家介绍PostgreS... 目录PostgreSQL的扩展dict_int一、扩展概述二、核心功能三、安装与启用四、字典配置方法

从原理到实战深入理解Java 断言assert

《从原理到实战深入理解Java断言assert》本文深入解析Java断言机制,涵盖语法、工作原理、启用方式及与异常的区别,推荐用于开发阶段的条件检查与状态验证,并强调生产环境应使用参数验证工具类替代... 目录深入理解 Java 断言(assert):从原理到实战引言:为什么需要断言?一、断言基础1.1 语

Python中re模块结合正则表达式的实际应用案例

《Python中re模块结合正则表达式的实际应用案例》Python中的re模块是用于处理正则表达式的强大工具,正则表达式是一种用来匹配字符串的模式,它可以在文本中搜索和匹配特定的字符串模式,这篇文章主... 目录前言re模块常用函数一、查看文本中是否包含 A 或 B 字符串二、替换多个关键词为统一格式三、提

Java MQTT实战应用

《JavaMQTT实战应用》本文详解MQTT协议,涵盖其发布/订阅机制、低功耗高效特性、三种服务质量等级(QoS0/1/2),以及客户端、代理、主题的核心概念,最后提供Linux部署教程、Sprin... 目录一、MQTT协议二、MQTT优点三、三种服务质量等级四、客户端、代理、主题1. 客户端(Clien

在Spring Boot中集成RabbitMQ的实战记录

《在SpringBoot中集成RabbitMQ的实战记录》本文介绍SpringBoot集成RabbitMQ的步骤,涵盖配置连接、消息发送与接收,并对比两种定义Exchange与队列的方式:手动声明(... 目录前言准备工作1. 安装 RabbitMQ2. 消息发送者(Producer)配置1. 创建 Spr

深度解析Spring Boot拦截器Interceptor与过滤器Filter的区别与实战指南

《深度解析SpringBoot拦截器Interceptor与过滤器Filter的区别与实战指南》本文深度解析SpringBoot中拦截器与过滤器的区别,涵盖执行顺序、依赖关系、异常处理等核心差异,并... 目录Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现