React+Redux+Ant Design+TypeScript 电子商务实战-客户端应用 03 分类、产品

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

创建添加分类组件

创建组件文件

// src\components\admin\AddCategory.tsx
import { Button, Form, Input } from 'antd'
import { Link } from 'react-router-dom'
import Layout from '../core/Layout'const AddCategory = () => {const onFinish = (value: { name: string }) => {console.log(value)}return (<Layout title="添加分类" subTitle=""><Form onFinish={onFinish}><Form.Item name="name" label="分类名称"><Input /></Form.Item><Form.Item><Button type="primary" htmlType="submit">添加分类</Button></Form.Item></Form><Button><Link to="/admin/dashboard">返回 Dashboard</Link></Button></Layout>)
}export default AddCategory

配置路由

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

配置链接入口

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

实现添加分类功能

// src\components\admin\AddCategory.tsx
import { Button, Form, Input, message } from 'antd'
import axios from 'axios'
import { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import { API } from '../../config'
import { isAuth } from '../../helpers/auth'
import { Jwt } from '../../store/models/auth'
import Layout from '../core/Layout'const AddCategory = () => {const [name, setName] = useState<string>('')const { user, token } = isAuth() as JwtuseEffect(() => {async function addCategory() {try {const response = await axios.post<{ name: string }>(`${API}/category/create/${user._id}`,{name: name},{headers: {Authorization: `Bearer ${token}`}})message.success(`[${response.data.name}] 分类添加成功`)} catch (error) {message.error(`${error.response.data.errors[0]}`)}}if (name) {addCategory()}}, [name])const onFinish = (value: { name: string }) => {setName(value.name)}return (<Layout title="添加分类" subTitle=""><Form onFinish={onFinish}><Form.Item name="name" label="分类名称"><Input /></Form.Item><Form.Item><Button type="primary" htmlType="submit">添加分类</Button></Form.Item></Form><Button><Link to="/admin/dashboard">返回 Dashboard</Link></Button></Layout>)
}export default AddCategory

创建添加产品组件

创建组件文件

// src\components\admin\AddProduct.tsx
import { Button, Form, Input, Select, Upload } from 'antd'
import Layout from '../core/Layout'const AddProduct = () => {return (<Layout title="添加产品" subTitle=""><Form><Form.Item label="上传封面"><Upload><Button>上传产品封面</Button></Upload></Form.Item><Form.Item name="name" label="产品名称"><Input /></Form.Item><Form.Item name="description" label="产品描述"><Input /></Form.Item><Form.Item name="price" label="产品价格"><Input /></Form.Item><Form.Item name="category" label="所属分类"><Select><Select.Option value="">请选择分类</Select.Option><Select.Option value="1">测试分类</Select.Option></Select></Form.Item><Form.Item name="quantity" label="产品库存"><Input /></Form.Item><Form.Item name="shipping" label="是否需要运输"><Select><Select.Option value={1}></Select.Option><Select.Option value={0}></Select.Option></Select></Form.Item><Form.Item><Button type="primary" htmlType="submit">添加产品</Button></Form.Item></Form></Layout>)
}export default AddProduct

配置路由

// 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 Home from './components/core/Home'
import Shop from './components/core/Shop'
import Signin from './components/core/Signin'
import Signup from './components/core/Signup'const Routes = () => {return (<HashRouter><Switch><Route path="/" component={Home} exact /><Route path="/shop" component={Shop} /><Route path="/signin" component={Signin} /><Route path="/signup" component={Signup} /><PrivateRoute path="/user/dashboard" component={Dashboard} /><AdminRoute path="/admin/dashboard" component={AdminDashboard} /><AdminRoute path="/create/category" component={AddCategory} /><AdminRoute path="/create/product" component={AddProduct} /></Switch></HashRouter>)
}export default Routes

配置链接入口

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

获取分类列表

创建分类相关的 action

// src\store\models\category.ts
export interface Category {_id: stringname: string
}
// src\store\actions\category.action.ts
import { Category } from '../models/category'export const GET_CATEGORY = 'GET_CATEGORY'
export const GET_CATEGORY_SUCCESS = 'GET_CATEGORY_SUCCESS'export interface GetCategoryAction {type: typeof GET_CATEGORY
}export interface GetCategorySuccessAction {type: typeof GET_CATEGORY_SUCCESSpayload: Category[]
}export const getCategory = (): GetCategoryAction => ({type: GET_CATEGORY
})export const getCategorySuccess = (payload: Category[]): GetCategorySuccessAction => ({type: GET_CATEGORY_SUCCESS,payload
})// action 的联合类型
export type CategoryUnionType = GetCategoryAction | GetCategorySuccessAction

定义 reducer

// src\store\reducers\category.reducer.ts
import { CategoryUnionType, GET_CATEGORY, GET_CATEGORY_SUCCESS } from '../actions/category.action'
import { Category } from '../models/category'export interface CategoryState {category: {loaded: booleansuccess: booleanresult: Category[]}
}const initialState: CategoryState = {category: {loaded: false,success: false,result: []}
}export default function categoryReducer(state = initialState, action: CategoryUnionType) {switch (action.type) {case GET_CATEGORY:return {...state,category: {loaded: false,success: false,result: []}}case GET_CATEGORY_SUCCESS:return {...state,category: {loaded: true,success: true,result: action.payload}}default:return state}
}
// src\store\reducers\index.ts
import { connectRouter, RouterState } from 'connected-react-router'
import { History } from 'history'
import { combineReducers } from 'redux'
import authReducer, { AuthState } from './auth.reducer'
import categoryReducer, { CategoryState } from './category.reducer'
// import testReducer from './test.reducer'// 定义一个包含 router 的 store 类型接口 供外部使用
export interface AppState {router: RouterStateauth: AuthStatecategory: CategoryState
}const createRootReducer = (history: History) =>combineReducers({// test: testReducer,router: connectRouter(history),auth: authReducer,category: categoryReducer})export default createRootReducer

定义 sage

// src\store\sagas\category.sage.ts
import axios, { AxiosResponse } from 'axios'
import { put, takeEvery } from 'redux-saga/effects'
import { API } from '../../config'
import { getCategorySuccess, GET_CATEGORY } from '../actions/category.action'
import { Category } from '../models/category'function* handleGetCategory() {const response: AxiosResponse = yield axios.get<Category[]>(`${API}/categories`)yield put(getCategorySuccess(response.data))
}export default function* categorySage() {// 获取分类列表yield takeEvery(GET_CATEGORY, handleGetCategory)
}
// src\store\sagas\index.ts
import { all } from 'redux-saga/effects'
import authSaga from './auth.saga'
import categorySage from './category.sage'export default function* rootSaga() {yield all([authSaga(), categorySage()])
}

修改组件

// src\components\admin\AddProduct.tsx
import { Button, Form, Input, Select, Upload } from 'antd'
import Layout from '../core/Layout'
import { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { getCategory } from '../../store/actions/category.action'
import { AppState } from '../../store/reducers'
import { CategoryState } from '../../store/reducers/category.reducer'const AddProduct = () => {const dispatch = useDispatch()const category = useSelector<AppState, CategoryState>(state => state.category)useEffect(() => {dispatch(getCategory())}, [])return (<Layout title="添加产品" subTitle=""><ForminitialValues={{category: '',shipping: 0}}><Form.Item><Upload label="上传封面"><Button>上传产品封面</Button></Upload></Form.Item><Form.Item name="name" label="产品名称"><Input /></Form.Item><Form.Item name="description" label="产品描述"><Input /></Form.Item><Form.Item name="price" label="产品价格"><Input /></Form.Item><Form.Item name="category" label="所属分类"><Select><Select.Option value="">请选择分类</Select.Option>{category.category.result.map(item => (<Select.Option key={item._id} value={item._id}>{item.name}</Select.Option>))}</Select></Form.Item><Form.Item name="quantity" label="产品库存"><Input /></Form.Item><Form.Item name="shipping" label="是否需要运输"><Select><Select.Option value={1}></Select.Option><Select.Option value={0}></Select.Option></Select></Form.Item><Form.Item><Button type="primary" htmlType="submit">添加产品</Button></Form.Item></Form></Layout>)
}export default AddProduct

实现添加产品功能

// src\components\admin\AddProduct.tsx
import { Button, Form, Input, message, Select, Upload } from 'antd'
import { PlusOutlined } from '@ant-design/icons'
import Layout from '../core/Layout'
import { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { getCategory } from '../../store/actions/category.action'
import { AppState } from '../../store/reducers'
import { CategoryState } from '../../store/reducers/category.reducer'
import { RcFile, UploadProps } from 'antd/lib/upload'
import axios from 'axios'
import { API } from '../../config'
import { isAuth } from '../../helpers/auth'
import { Jwt } from '../../store/models/auth'// 将图片转化为 Data Url
function getBase64(blob: RcFile, callback: (e: string) => void) {const reader = new FileReader()reader.addEventListener('load', () => callback(reader.result as string))reader.readAsDataURL(blob)
}const AddProduct = () => {const dispatch = useDispatch()const [file, setFile] = useState<RcFile>()const category = useSelector<AppState, CategoryState>(state => state.category)useEffect(() => {dispatch(getCategory())}, [])// 产品封面 Data Urlconst [imgUrl, setImgUrl] = useState<string>()// 上传产品封面按钮const uploadButton = (<div><PlusOutlined /><div style={{ marginTop: 8 }}>Upload</div></div>)// Upload 组件属性const props: UploadProps = {accept: 'image/*',showUploadList: false,listType: 'picture-card',beforeUpload(file: RcFile) {// 获取文件保存下来等待和表单一起提交setFile(file)// 阻止默认上传行为(发送请求)return false},onChange({ file }) {// 获取图片的 Data Url 显示在页面getBase64(file as RcFile, (dataUrl: string) => {setImgUrl(dataUrl)})}}const addProductForm = () => {const { user, token } = isAuth() as Jwtconst onFinish = (product: any) => {// 要将二进制图片和普通文本字段结合在一起发送需要用到 FormDataconst formData = new FormData()for (let attr in product) {formData.set(attr, product[attr])}if (typeof file !== 'undefined') {formData.set('photo', file)}axios.post(`${API}/product/create/${user._id}`, formData, {headers: {Authorization: `Bearer ${token}`}}).then(() => {message.success('产品添加成功')}).catch(() => {message.success('产品添加失败')})}return (<FormonFinish={onFinish}initialValues={{category: '',shipping: 0}}><Form.Item label="上传封面"><Upload {...props}>{imgUrl ? (<img src={imgUrl} alt="产品封面" style={{ maxWidth: '100%', maxHeight: '100%' }} />) : (uploadButton)}</Upload></Form.Item><Form.Item name="name" label="产品名称"><Input /></Form.Item><Form.Item name="description" label="产品描述"><Input /></Form.Item><Form.Item name="price" label="产品价格"><Input /></Form.Item><Form.Item name="category" label="所属分类"><Select><Select.Option value="">请选择分类</Select.Option>{category.category.result.map(item => (<Select.Option key={item._id} value={item._id}>{item.name}</Select.Option>))}</Select></Form.Item><Form.Item name="quantity" label="产品库存"><Input /></Form.Item><Form.Item name="shipping" label="是否需要运输"><Select><Select.Option value={1}></Select.Option><Select.Option value={0}></Select.Option></Select></Form.Item><Form.Item><Button type="primary" htmlType="submit">添加产品</Button></Form.Item></Form>)}return (<Layout title="添加产品" subTitle="">{addProductForm()}</Layout>)
}export default AddProduct

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



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

相关文章

vite搭建vue3项目的搭建步骤

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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