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

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

【 html+css 绚丽Loading 】000046 三才归元阵

前言:哈喽,大家好,今天给大家分享html+css 绚丽Loading!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、信息💡1.简介:💡2.外观描述:💡3.使用方式:💡4.战斗方式:💡5.提升:💡6.传说: 📚三、源代码,上代码,可以直接复制使用🎥效果🗂️目录✍️

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

hdu1394(线段树点更新的应用)

题意:求一个序列经过一定的操作得到的序列的最小逆序数 这题会用到逆序数的一个性质,在0到n-1这些数字组成的乱序排列,将第一个数字A移到最后一位,得到的逆序数为res-a+(n-a-1) 知道上面的知识点后,可以用暴力来解 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#in