创建添加分类组件
创建组件文件
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
配置路由
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
配置链接入口
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 } = Typography
const AdminDashboard = () => {
const {
user: { name, email }
} = isAuth() as Jwt
const 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
实现添加分类功能
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 Jwt
useEffect(() => {
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
创建添加产品组件
创建组件文件
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
配置路由
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
配置链接入口
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 } = Typography
const AdminDashboard = () => {
const {
user: { name, email }
} = isAuth() as Jwt
const 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
export interface Category {
_id: string
name: string
}
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_SUCCESS
payload: Category[]
}
export const getCategory = (): GetCategoryAction => ({
type: GET_CATEGORY
})
export const getCategorySuccess = (payload: Category[]): GetCategorySuccessAction => ({
type: GET_CATEGORY_SUCCESS,
payload
})
export type CategoryUnionType = GetCategoryAction | GetCategorySuccessAction
定义 reducer
import { CategoryUnionType, GET_CATEGORY, GET_CATEGORY_SUCCESS } from '../actions/category.action'
import { Category } from '../models/category'
export interface CategoryState {
category: {
loaded: boolean
success: boolean
result: 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
}
}
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'
export interface AppState {
router: RouterState
auth: AuthState
category: CategoryState
}
const createRootReducer = (history: History) =>
combineReducers({
router: connectRouter(history),
auth: authReducer,
category: categoryReducer
})
export default createRootReducer
定义 sage
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)
}
import { all } from 'redux-saga/effects'
import authSaga from './auth.saga'
import categorySage from './category.sage'
export default function* rootSaga() {
yield all([authSaga(), categorySage()])
}
修改组件
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="">
<Form
initialValues={{
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
实现添加产品功能
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'
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())
}, [])
const [imgUrl, setImgUrl] = useState<string>()
const uploadButton = (
<div>
<PlusOutlined />
<div style={{ marginTop: 8 }}>Upload</div>
</div>
)
const props: UploadProps = {
accept: 'image/*',
showUploadList: false,
listType: 'picture-card',
beforeUpload(file: RcFile) {
setFile(file)
return false
},
onChange({ file }) {
getBase64(file as RcFile, (dataUrl: string) => {
setImgUrl(dataUrl)
})
}
}
const addProductForm = () => {
const { user, token } = isAuth() as Jwt
const onFinish = (product: any) => {
const 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 (
<Form
onFinish={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