使用AntdPro创建基于React的管理后台(学习笔记4)(技术笔记2)中 <火山引擎后台管理>商品模块的实现;有代码结构简析;文末附有源码

主要职能

这是一个react+antdpro后台管理项目,我的主要职责:

  • 规划发布项目的架构、开发环境、工具,更新云工程(原商品模块),封装oss
  • 了解react的生命周期,使用过api,复用组件
  • 合并分支,更新迭代,优化debug,带领团队完成进度,部署项目

方案选型

 因为随机分配的小组没有大佬,大部分人都只有三件套或B4、jQ的经验,我们一致认为对于结营项目,最好的方法是从B站上找一个教程,分工完成,视频地址:React项目全程实录#电商项目#react+UmiJS+Antd Pro#React全套技术#哔哩哔哩_bilibili

完成过程

  1. 云工程(商品列表)
  2. 上架与推荐
  3. 添加云工程(商品页面)
  4. 处理云工程分类(商品分类)
  5. 封装OSS
  6. 使用并集成富文本器
  7. 项目总结与优化

我写的代码 的结构简介图解

参考文档umijs/umi-request: A request tool based on fetch. (github.com)

 商品模块共有4个代码文件:

src\pages\Projects\index.jsx

//主页面

import....区域

const Index = () => {

// 将表单初始化的值设置成状态,

  const [isModalVisible, setIsModalVisible] = useState(false)

  const [editId, setEditId] = useState(undefined)

  // 表格的ref, 便于自定义操作表格,ref属性其实就是为了获取DOM节点

  const actionRef = useRef()...

  //一个异步函数获取数据

  const getData = async (params) =>...

//上架和下架商品;推荐和不推荐商品;控制模态框显示和隐藏的三个函数

const handleIsOn;const handleIsRecommend;const isShowModal

//商品属性

const columns = [ ]

return (

    <PageContainer>

      <ProTable

       //这个区域进行传参和添加商品按钮

        !isModalVisible ? '' :

        <CreateOrEdit

          isModalVisible={isModalVisible}

          isShowModal={isShowModal}

          actionRef={actionRef}

          editId={editId}

          //引入新建功能的组件

        />

      }

    </PageContainer>

};

export default Index;

src\services\goods.js

//商品功能的实现

写法的来源参考ProTable - 高级表格 - ProComponents

 import request from '@/utils/request';

//定义了5个功能,这里就列出一个获取商品列表

export async function getGoods(params) {

  return request('/admin/goods', {params});

}

 src\pages\Projects\components\CreateOrEdit.jsx

//添加商品的模态框

import 一大片

const CreateOrEdit = (props) => {

  // 将表单初始化的值设置成状态, 在编辑的时候, 使用这个状态

  const { isModalVisible } = props // 模态框是否显示
  ......这里定义了一大片功能

  // 添加 或者 编辑 的描述文字
  const type = editId === undefined ? '添加' : '编辑'

    if (response.status === undefined) {
      // 关闭模态框
     ....
    }
  }

  return (
    <Modal
      title={`${type}商品`}
      visible={isModalVisible}
      destroyOnClose={true}>   

<ProFormText
              //一大片功能,用antd的form实现
            />
              <div>
                <OSSUpload>
                  <Button icon={<UploadOutlined />}>点击上传商品主图</Button>
                </OSSUpload>{url}
              </div>
    </Modal>
  );
};

export default CreateOrEdit;

src\components\OSS\index.jsx

//这个是可以复用的oss上传组件

import 区域....

export default class OSSUpload

{

//同时在创建模态框那个组件里引入这个上传组件,并添加上传按钮等form

//组件挂载完成后, 进行初始化, 获取oss配置

async componentDidMount() {

    await this.init();

  }//这是一个异步函数使用的例子,await

init = async () =>

//回调函数的例子

render() {

    const { value, accept, showUploadList } = this.props;

    const props = {

     ....

    };

    return (

      <Upload {...props}>

        <Button icon={<UploadOutlined />}>点击上传</Button>

      </Upload>

    );

}

完成步骤

云工程(商品列表)

首先在路由界面添加页面,component(创建模态框的那个),然后在内置的国际化文件添加翻译

上架与推荐

根据后端api写good.js

api文档 - (https://www.showdoc.com.cn/1824645173761431/8414274620688955)(私有整体说明文档访问密码为:666666)

添加云工程(商品页面)

具体如何实现可以看上面的index.jsx图解或文末源码

处理云工程分类(商品分类)

在 src\pages\Projects\components\CreateOrEdit.jsx中

使用了antdpro高级表格的Item

       <ProForm.Item
         name="category_id"
       label="分类"
        rules={[{required: true, message: '请选择分类'}]}
        >
      <Cascader
         fieldNames={ {label: 'name', value: 'id'} }
       options={options}
      placeholder="请选择分类"
      />
      </ProForm.Item>

封装OSS(云对象储存)

使用原因:储存式云盘与服务器分离可以大大减少花销;上传下载不经过服务器,只请求授权

将阿里云oss使用代码模板复制来,并进行修改

参考博客:antd上传图片到阿里云的oss服务器 - 简书

使用并集成富文本器

项目总结与优化

主要源码

 src/pages/index.jsx

import React, {useRef, useState} from 'react';
import {PageContainer} from '@ant-design/pro-layout'
import ProTable from '@ant-design/pro-table'
import {Button, Image, Switch, Alert, message, Tabs} from 'antd'
import {PlusOutlined} from '@ant-design/icons'
import {getGoods, isOn, isRecommend} from '@/services/goods'
import CreateOrEdit from '../Userlist/components/CreateOrEdit'


const Index = () => {
// 将表单初始化的值设置成状态,
  const [isModalVisible, setIsModalVisible] = useState(false)
  const [editId, setEditId] = useState(undefined)

  // 表格的ref, 便于自定义操作表格
  const actionRef = useRef()

  /**
   * 获取商品列表数据
   *
   * @param params
   * @returns {Promise<{total: *, data: *, success: boolean}>}
   */
  const getData = async (params) => {
    const response = await getGoods(params)
    return {
      data: response.data,
      success: true,
      total: response.meta.pagination.total,
    };
  }

  /**
   * 上架和下架商品
   *
   * @param goodsId
   * @returns {Promise<void>}
   */
  const handleIsOn = async (goodsId) => {
    const response = await isOn(goodsId)
    if (response.status === undefined) message.success('操作成功')
  }

  /**
   * 推荐和不推荐商品
   *
   * @param goodsId
   * @returns {Promise<void>}
   */
  const handleIsRecommend = async (goodsId) => {
    const response = await isRecommend(goodsId)
    if (response.status === undefined) message.success('操作成功')
  }

  /**
   * 控制模态框显示和隐藏
   */
  const isShowModal = (show, id = undefined) => {
    setEditId(id)
    setIsModalVisible(show)
  }
  const columns = [
    {
      title: '商品图',
      dataIndex: 'cover_url',
      hideInSearch: true,
      render: (_, record) =>
        <Image
          width={64}
          src={record.cover_url}
          placeholder={
            <Image
              preview={false}
              src={record.cover_url}
              width={200}
            />
          }
        />
    },
    {
      title: '标题',
      dataIndex: 'title'
    },
    {
      title: '价格',
      dataIndex: 'price',
      hideInSearch: true
    },
    {
      title: '库存',
      dataIndex: 'stock',
      hideInSearch: true
    },
    {
      title: '销量',
      dataIndex: 'sales',
      hideInSearch: true
    },
    {
      title: '是否上架',
      dataIndex: 'is_on',
      render: (_, record) => <Switch
        checkedChildren="已上架"
        unCheckedChildren="未上架"
        defaultChecked={record.is_on === 1}
        onChange={ () => handleIsOn(record.id)}
      />,
      valueType: 'radioButton',
      valueEnum: {
        1: {text: '已上架'},
        0: {text: '未上架'},
      }
    },
    {
      title: '是否推荐',
      dataIndex: 'is_recommend',
      render: (_, record) => <Switch
        checkedChildren="已推荐"
        unCheckedChildren="未推荐"
        defaultChecked={record.is_recommend === 1}
        onChange={ () => handleIsRecommend(record.id)}
      />,
      valueType: 'radioButton',
      valueEnum: {
        1: {text: '已推荐'},
        0: {text: '未推荐'},
      }
    },

    {
      title: '创建时间',
      dataIndex: 'created_at',
      hideInSearch: true
    },
    {
      title: '操作',
      render: (_, record) => <a onClick={ () => isShowModal(true, record.id) }>编辑</a>
    },
  ]

  return (
    <PageContainer>
      <ProTable
        columns={columns}
        actionRef={actionRef}
        request={(params = {}) => getData(params) }
        rowKey="id"
        search={ {
          labelWidth: 'auto',
        } }
        pagination={ {
          pageSize: 10,
        } }
        dateFormatter="string"
        headerTitle="用户列表"
        toolBarRender={() => [
          <Button key="button" icon={<PlusOutlined />} type="primary" onClick={() => isShowModal(true)}>
            新建
          </Button>,
        ]}
        />

      {
        // 模态框隐藏的时候, 不挂载组件; 模态显示时候再挂载组件, 这样是为了触发子组件的生命周期
        !isModalVisible ? '' :
        <CreateOrEdit
          isModalVisible={isModalVisible}
          isShowModal={isShowModal}
          actionRef={actionRef}
          editId={editId}
        />
      }
    </PageContainer>
  );
};

export default Index;

src/utils/request

 src/service/good.js

import request from '@/utils/request';

/**
  关于 request 第二参数 options, 常用的两个传参方式:

  1. params 传参, 也就是query传参, 多用于 get 请求, 查询数据使用, 类型是对象或者 URLSearchParams
  2. data 传参, 也就是body传参, 多用于提交表单数据, 类型是 any, 推荐使用对象

  /**
 * 获取商品列表
 *
 * @returns {Promise<void>}
 */
export async function getGoods(params) {
  return request('/admin/goods', {params});
}



/**
 * 上架和下架商品
 *
 * @param goodsId 商品 id
 * @returns {Promise<any>}
 */
export async function isOn(goodsId) {
  return request.patch(`/admin/goods/${goodsId}/on`)
}

/**
 * 推荐和不推荐商品
 *
 * @param goodsId 商品 id
 * @returns {Promise<any>}
 */
export async function isRecommend(goodsId) {
  return request.patch(`/admin/goods/${goodsId}/recommend`)
}


/**
 * 添加商品
 *
 * @param params
 * @returns {Promise<void>}
 */
export async function addGoods(data) {
  return request.post('/admin/goods', {data})
}

/**
 * 商品详情
 *
 * @param editId
 * @returns {Promise<void>}
 */
export async function showGoods(editId) {
  return request.get(`/admin/goods/${editId}?include=category`)
}

/**
 * 更新商品
 *
 * @returns {Promise<void>}
 * @param editId
 * @param data
 */
export async function updateGoods(editId, data) {
  return request.put(`/admin/goods/${editId}`, {data})
}

创建模态框:

src\pages\Projects\components\CreateOrEdit.jsx

import React, {useEffect, useState} from 'react';
import ProForm, {
  ProFormText,
  ProFormTextArea,
  ProFormDigit,
} from "@ant-design/pro-form";
import {message, Modal, Skeleton, Cascader, Button, Image} from "antd";
import {getCategory} from '@/services/category'
import {addGoods, showGoods, updateGoods} from '@/services/goods'
import {UploadOutlined} from "@ant-design/icons";
import Editor from '@/components/Editor'
import OSSUpload from "@/components/OSS";

const CreateOrEdit = (props) => {

  // 将表单初始化的值设置成状态, 在编辑的时候, 使用这个状态
  const [initialValues, setInitialValues] = useState(undefined)
  const [options, setOptions] = useState([])

  // 定义Form实例, 用来操作表单
  const [formObj] = ProForm.useForm()
  // 设置表单的值
  // formObj.setFieldsValue({fieldName: value})

  const { isModalVisible } = props // 模态框是否显示
  const { isShowModal } = props // 操作模态框显示隐藏的方法
  const { actionRef } = props // 父组件传过来的表格的引用, 可以用来操作表格, 比如刷新表格
  const { editId } = props // 要编辑的ID, 添加的时候是undefined, 只有编辑才有

  // 添加 或者 编辑 的描述文字
  const type = editId === undefined ? '添加' : '编辑'

  useEffect(async () => {
    // 查询分类数据
    const resCategory = await getCategory()
    if (resCategory.status === undefined) setOptions(resCategory)

    // 发送请求, 获取商品详情
    if (editId !== undefined) {
      const response = await showGoods(editId)
      console.log(response);
      // 获取数据之后, 修改状态, 状态改变, 组件重新渲染, 骨架屏消失, 编辑表单出现
      const {pid, id} = response.category
      const defaultCategory = pid === 0 ? [id] : [pid, id]
      setInitialValues({...response, category_id: defaultCategory})
    }
  }, [])

  /**
   * 文件上传成功后, 设置cover字段的value
   * @param fileKey
   */
  const setCoverKey = fileKey => formObj.setFieldsValue({'cover': fileKey})

  /**
   * 编辑输入内容后, 设置details字段的value
   * @param fileKey
   */
  const setDetails = content => formObj.setFieldsValue({'details': content})

  /**
   * 提交表单, 执行编辑或者添加
   *
   * @param values
   * @returns {Promise<void>}
   */
  const handleSubmit = async values => {
    let response = {}
    if (editId === undefined) { // 执行添加
      // 发送请求, 添加商品
      response = await addGoods({...values, category_id: values.category_id[1]})
    } else { // 执行编辑
      // 发送请求, 更新商品
      response = await updateGoods(editId, {...values, category_id: values.category_id[1]})
    }

    if (response.status === undefined) {
      message.success(`${type}成功`)
      // 刷新表格数据
      actionRef.current.reload()
      // 关闭模态框
      isShowModal(false)
    }
  }

  return (
    <Modal
      title={`${type}商品`}
      visible={isModalVisible}
      onCancel={() => isShowModal(false) }
      footer={null}
      destroyOnClose={true}
    >

      {
        // 只有是编辑的情况下, 并且要显示的数据还没有返回, 才显示骨架屏
        initialValues === undefined && editId !== undefined ? <Skeleton active={true} paragraph={{ rows: 4 } } /> :
          <ProForm
            form={formObj}
            initialValues={ initialValues }
            onFinish={ values => handleSubmit(values) }
          >

            <ProForm.Item
              name="category_id"
              label="分类"
              rules={[{required: true, message: '请选择分类'}]}
            >
              <Cascader
                fieldNames={ {label: 'name', value: 'id'} }
                options={options}
                placeholder="请选择分类"
              />
            </ProForm.Item>

            <ProFormText
              name="title"
              label="商品名"
              placeholder="请输入商品名"
              rules={[{required: true, message: '请输入商品名'},]}
            />

            <ProFormTextArea
              name="description"
              label="描述"
              placeholder="请输入描述"
              rules={[{required: true, message: '请输入描述'},]}
            />

            <ProFormDigit
              name="price"
              label="价格"
              placeholder="请输入价格"
              min={0}
              max={99999999}
              rules={[{required: true, message: '请输入价格'},]}
            />

            <ProFormDigit
              name="stock"
              label="库存"
              placeholder="请输入库存"
              min={0}
              max={99999999}
              rules={[{required: true, message: '请输入库存'},]}
            />

            <ProFormText name="cover" hidden={true}/>

            <ProForm.Item
              name="cover"
              label="商品主图"
              rules={[{required: true, message: '请上传商品主图'}]}
            >
              <div>
                <OSSUpload
                  accept="image/*"
                  setCoverKey={setCoverKey}
                  showUploadList={true}
                >
                  <Button icon={<UploadOutlined />}>点击上传商品主图</Button>
                </OSSUpload>
                {
                  initialValues === undefined || !initialValues.cover_url ? '' :
                    <Image width={200} src={initialValues.cover_url} />
                }
              </div>
            </ProForm.Item>

            <ProForm.Item
              name="details"
              label="商品详情"
              rules={[{required: true, message: '请输入详情'}]}
            >
              <Editor
                setDetails={setDetails}
                content={initialValues === undefined ? '' : initialValues.details}
              />
            </ProForm.Item>
          </ProForm>
      }
    </Modal>
  );
};

export default CreateOrEdit;

src\components\OSS\index.jsx

import React from "react";
import {Upload, message, Button} from 'antd';
// import { ossConfig } from "@/services/common ";
import {UploadOutlined} from "@ant-design/icons";

export default class OSSUpload extends React.Component {
  state = {
    OSSData: {},
  };

  /**
   * 组件挂载完成后, 进行初始化, 获取oss配置
   * @returns {Promise<void>}
   */
  async componentDidMount() {
    await this.init();
  }

  /**
   * 初始化, 获取oss上传签名
   *
   * @returns {Promise<void>}
   */
  init = async () => {
    try {
      const OSSData = await mockGetOSSData();

      this.setState({
        OSSData,
      });
    } catch (error) {
      message.error(error);
    }
  };

  /**
   * 文件上传过程中触发的回调函数, 直到上传完成
   *
   * @param fileList
   */
  onChange = ( { file } ) => {
    if (file.status === 'done') {
      // const {setCoverKey, insertImage} = this.props
      //
      // // 上传成功之后, 把文件的key, 设置为表单某个字段的值
      // if (setCoverKey) setCoverKey(file.key)
      //
      // // 上传完成之后, 如果需要url, 那么返回url给父组件
      // if (insertImage) insertImage(file.url)
      this.props.setCoverKey(file.key);
      message.success('上传成功')
    }
  };
  onRemove = (file) => {
    const { value, onChange } = this.props;

    const files = value.filter((v) => v.url !== file.url);

    if (onChange) {
      onChange(files);
    }
  };
  /**
   * 额外的上传参数
   *
   * @returns {Promise<void>}
   */
  getExtraData = file => {
    const { OSSData } = this.state;

    return {
      key: file.key,
      OSSAccessKeyId: OSSData.accessid,
      policy: OSSData.policy,
      Signature: OSSData.signature,
    };
  };

  /**
   * 选择文件之后, 上传文件之前, 执行回调
   *
   * @param file
   * @returns {Promise<*>}
   */
  beforeUpload = async file => {
    const { OSSData } = this.state;
    const expire = OSSData.expire * 1000;

    // 如果签名过期了, 重新获取
    if (expire < Date.now()) {
      await this.init();
    }

    const dir = 'react/' // 定义上传的目录

    const suffix = file.name.slice(file.name.lastIndexOf('.'));
    const filename = Date.now() + suffix;
    file.key = OSSData.dir + dir + filename; // 在 getExtraData 函数中会用到, 在云存储中存储的文件的 key
    file.url = OSSData.host + OSSData.dir + dir + filename; // 上传完成后, 用于显示内容

    return file;
  };

  render() {
    const { value, accept, showUploadList } = this.props;
    const props = {
      accept: accept || '',
      name: 'file',
      fileList: value,
      action: this.state.OSSData.host,
      onChange: this.onChange,
      data: this.getExtraData,
      beforeUpload: this.beforeUpload,
      listType: "picture",
      maxCount: 1,
      showUploadList
    };
    return (
      <Upload {...props}>
        <Button icon={<UploadOutlined />}>点击上传</Button>
        {/*{this.props.children}*/}
      </Upload>
    );
  }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FS9000

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值