基本路由

<Switch>
    <Route path='/product' component={ProductHome} exact />
    {/*路径完全匹配*/}
    <Route path='/product/addupdate' component={ProductAddUpdate} />
    <Route path='/product/detail' component={ProductDetail} />
    <Redirect to='/product' />
</Switch>

两种分页技术

  1. 前台分页

    后台直接返回全部数据

  2. 后台分页

    后台返回给前台当前页数与总页数

    前台需要传递当前页码数与每页数量

跳转路由时传递参数

跳转路由

this.props.history.push('/product/detail', { product })

接受参数

const { pCategoryId, categoryId } = this.props.location.state.product

React中输出HTML标签

<span dangerouslySetInnerHTML={{ __html: detail }}></span>

级联选择器

<Cascader
    options={this.state.options}
    loadData={this.loadData}
    onChange={this.onChange}
    changeOnSelect
    />
  • options

    数据源

  • loadData

    加载数据的回调函数

  • changeOnSelect

    当此项为 true 时,点选每级菜单选项值都会发生变化,具体见上面的演示

  • onChange

    选择完成后的回调

loadData = (selectedOptions) => {
    // 的到选择的option对象
    const targetOption = selectedOptions[selectedOptions.length - 1]
    // loading效果
    targetOption.loading = true

    // load options lazily
    setTimeout(() => {
        targetOption.loading = false
        // 某一项的下一级列表
        targetOption.children = [
            {
                label: `${targetOption.label} Dynamic 1`,
                value: 'dynamic1',
                isLeaf: true
            },
            {
                label: `${targetOption.label} Dynamic 2`,
                value: 'dynamic2',
                isLeaf: true
            }
        ]
        this.setState({
            options: [...this.state.options]
        })
    }, 1000)
}

获取分类

export default class ProductAddUpdate extends Component {
    formRef = React.createRef()
state = {
    options: []
}
initOptions = (categorys) => {
    // 根据categorys数组生成options数组
    const options = categorys.map((c) => ({
        value: c._id,
        label: c.name,
        isLeaf: false
    }))
    // 更新状态
    this.setState({ options })
}
// 获取一级/二级分类列表
getCategorys = async (parentId) => {
    parentId = parentId || '0'
    const result = await reqCategorys(parentId)
    if (result.status !== 0) return message.error('分类获取失败')
    const categorys = result.data
    // 如果是一级分类
    if (parentId === '0') {
        this.initOptions(categorys)
    } else {
        // 二级列表
        return categorys
    }
}
loadData = async (selectedOptions) => {
    // 的到选择的option对象
    const targetOption = selectedOptions[selectedOptions.length - 1]
    // loading效果
    targetOption.loading = true

    // 根据选中的分类请求获取下一级分类
    const subCategorys = await this.getCategorys(targetOption.value)
    if (subCategorys && subCategorys.length > 0) {
        // 生成二级列表options
        const cOptions = subCategorys.map((c) => ({
            value: c._id,
            label: c.name,
            isLeaf: true
        }))
        // 某一项的下一级列表
        targetOption.children = cOptions
    } else {
        targetOption.isLeaf = true
    }
    targetOption.loading = false
    this.setState({
        options: [...this.state.options]
    })
}
componentDidMount() {
    this.getCategorys()
}
}

默认分类

initOptions = async (categorys) => {
    // 根据categorys数组生成options数组
    const options = categorys.map((c) => ({
        value: c._id,
        label: c.name,
        isLeaf: false
    }))
    // 如果是一个二级分类商品的更新
    const { isUpdate, product } = this
    const { pCategoryId, categoryId } = product
    if (isUpdate && pCategoryId !== '0') {
        // 获取对应的二级分类列表
        const subCategorys = await this.getCategorys(pCategoryId)
        // 生成二级下拉列表的options
        const childOptions = subCategorys.map((c) => ({
            value: c._id,
            label: c.name,
            isLeaf: true
        }))
        // 找到当前商品对应的option对象
        const targetOption = options.find(
            (option) => option.value === pCategoryId
        )
        // 关联到对应的一级options
        targetOption.children = childOptions
    }

    // 更新状态
    this.setState({ options })
}

上传图片

上传组件:https://ant.design/components/upload-cn/

上传图片时所触发的回调函数中filefileList最后一个元素不是同一个。因此修改的并不是file,而是fileList最后一个元素

// file:当前操作的图片文件
// fileList:所有已上传的文件对象
handleChange = ({ file, fileList }) => {
    // 一旦上传成功,将当前上传的file的信息修正
    if (file.status === 'done') {
        const result = file.response
        if (result.status === 0) {
            message.success('成功了!')
            const { name, url } = result.data
            file = fileList[fileList.length - 1]
            file.name = name
            file.url = url
        } else {
            message.error('失败了!')
        }
    }
    this.setState({ fileList })
}

父组件收集子组件的数据

为子组件添加ref引用。类似于antd4中的表单。例如

class MyComponent extends React.Component {
    constructor(props) {
        super(props);
        this.myRef = React.createRef();
    }
    render() {
        return <div ref={this.myRef} />;
    }
}

子组件定义方法返回需要的列表,父组件调用此方法即可。

编辑时显示图片

编辑时显示图片涉及到fileList图片列表的初始值。因此需要接受数据来判断是否存在。如果存在则生成有图片的列表

constructor(props) {
    super(props)
    const { imgs } = this.props
    let fileList = []
    if (imgs && imgs.length > 0) {
        fileList = imgs.map((img, index) => ({
            uid: -index,
            name: img,
            status: 'done',
            url: BASE_IMG_URL + img
        }))
    }
    this.state = {
        previewVisible: false, // 是否显示大图预览
        previewImage: '', // 图片地址
        previewTitle: '',
        fileList
    }
}

父组件只需要将图片列表传递给子组件即可。

富文本编辑器

安装插件

yarn add react-draft-wysiwyg draftjs-to-html draft-js
<Editor
    editorState={editorState}
    editorStyle={{
        border: '1px solid black',
            minHeight: 200,
                maxHeight: 500,
                    paddingLeft: 10
    }}
    toolbar={{
        image: {
            uploadCallback: this.uploadImageCallBack,
                alt: { present: true, mandatory: true }
        }
    }}
    onEditorStateChange={this.onEditorStateChange}
    />

图片上传

// 图片上传
uploadImageCallBack = (file) => {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()
        xhr.open('POST', '/manage/img/upload')
        const data = new FormData()
        data.append('image', file)
        xhr.send(data)
        xhr.addEventListener('load', () => {
            const response = JSON.parse(xhr.responseText)
            const url = response.data.url
            resolve({ data: { link: url } })
        })
        xhr.addEventListener('error', () => {
            const error = JSON.parse(xhr.responseText)
            reject(error)
        })
    })
}