第九课 复习之从零开始搭建页面

页面搭建步骤

  • 1.使用脚手架生成商户侧项目,并安装依赖(rdms有自己的脚手架生成)
  • 2.使用vscode插件或者代码片段生成静态页面并注册路由
  • 3.修改组件使用配置以及添加代码
  • 4.使用dva进行异步交互的页面开发,并和组件连接

1.使用脚手架开发商户侧项目

  • cnpm安装:npm install -g cnpm --registry=https://registry.npm.taobao.org
// 全局安装fl-hscli,生成商户侧脚手架的命令(目前写入的是mall商户侧)
npm i fl-hscli -g

// 进入想要创建项目的文件夹创建项目(projectName是你的项目名称)
create-react-app-fl projectName

// package.json中fl-pro、fulu-method是公司内部组件库,安装模块包之前,先安装公内部组件,不然会报错

// 下载模块包方式一
// (通常安装命令是直接npm install),由于需要先安装内部组件库,所以添加scripts脚本命令去先安装内部组件库然后再安装其余组件  
// "install-all": "npm run install-init && cnpm install",
npm run install-all

// 下载模块包方式二
npm --registry http://10.0.1.244:8081/repository/npm-group/ install fl-pro   --save 

// 安装成功后,执行安装模块包的命令,因为package.json模块非常多,可以使用淘宝镜像进行安装,安装速度快
npm i
cnpm i(需要先安装淘宝镜像)

// 安装成功后,启动项目
npm start

商户侧显示效果

image

注意

  • 安装nodejs,才可使用npm命令

  • cnpm安装:npm install -g cnpm --registry=https://registry.npm.taobao.org

  • 开发者运行的商户侧地址都需要在福禄管家配置回调地址,否则页面打不开,开发环境访问使用自己开启的域名(例如:http://192.168.0.105:3008)/?MerchantId=商户id(bcc1adce-927a-4c0b-88c0-c446a0435b98 )进行访问

  • 回调地址配置:

福禄管家域名:http://it.gj.admin.fulu.com
账号密码:13618627189 

  • 项目运行起来会报错

  • 点进去注释这个报错代码

2.使用vscode插件或者代码片段生成静态页面并注册路由

页面开发流程

  • 1.通过vscode插件生成页面组件、models、services
  • 2.路由注册对应页面
  • 3.index.js引用对应的页面models
  • 4.models,services进行细微调整
  • 5.api接口地址调整对应后端接口地址

1.vscode插件使用方式

  • 1.找到vscode插件create-react-component-song并安装
  • 2.src目录下右键使用Create-Class-Component创建对应页面,输入页面名称,直接生成对应的页面以及models和services

2.路由注册

// 定义组件
const CouponsList = dynamic({
  app,
  component: () => import('./components/CouponList'),
});
// 配置路由(路由需要在商户控制台配置才能使用,否则会报无权限访问)
<Route exact path="/marketingManage/couponsList" render={(props) => WraperRouter(props, CouponsList)} />

3.index.js注册页面的models

import { default as couponList } from './models/couponList';
app.model(couponList);

3.修改组件使用配置以及添加代码

例如

  • 1.查询组件配置searchConfig
searchConfig: [{
   label: '优惠券名称',
   type: 'Input',
   name: 'Name',
   placeholder: '请输入优惠券名称',
},
{
   label: '优惠券类型',
   type: 'Select',
   name: 'Type',
   initialValue: '',
   items: [
      { name: '全部', value: '' },
      { name: '满减券', value: '1' },
      { name: '折扣券', value: '2' },
      { name: '兑换券', value: '3' },
   ]
}]
  • 2.表格列数据columns
const tableColums = [
   { title: '序号',  80, key: 'index', dataIndex: 'index', fixed: 'left', render(text, record, index) { return index + 1 } },
   {
      title: '模版标题', key: 'tempNo', dataIndex: 'tempNo',
      render(text) {
         const arr = ['', '待支付提醒', '订单取消通知', '充值成功通知', '充值失败通知', '温馨提示'];
         return arr[text];
      }
   },
   {
      title: '模版ID', key: 'tempId', dataIndex: 'tempId',
   },
   {
      title: '消息类型',  150, key: 'tempMessageType', dataIndex: 'tempMessageType',
      render(text) {
         const arr = ['', '支付类型', '表单类型'];
         return arr[text];
      }
   },
   {
      title: '状态',  150, key: 'isEnable', dataIndex: 'isEnable',
      render(text) {
         return text ? '启用' : '禁用';
      }
   },
   {
      title: '操作',  180, key: 'control', fixed: 'right',
      render: (text, record) => {
         return (
            <div>
               {btnArr.find(v => v.enCode === 'lr-enable') &&
                  <a onClick={() => { this.enableInfo(record) }}>{record.isEnable ? '禁用' : '启用'}</a>}
               {btnArr.find(v => v.enCode === 'lr-enable') && <Divider type='vertical' />
               }
               {btnArr.find(v => v.enCode === 'lr-edit') &&
                  <a onClick={() => {
                     this.props.history.push(`/couponList/couponListAdd?id=${record.id
                        }`)

                  }}>编辑</a>
               }
               {btnArr.find(v => v.enCode === 'lr-edit') && <Divider type='vertical' />
               }
               {btnArr.find(v => v.enCode === 'lr-delete') && <a onClick={() => {
                  this, this.deleteInfo(record)
               }
               }>删除</a>
               }
            </div>
         )
      }
   }
]
  • 3.查询、删除、启用禁用事件的异步交互方法名修改
// 查询修改为
this.props.dispatch({ type: 'couponList/getMerCouponInfoPage', payload: { ...postData } });
// 删除
that.props.dispatch({
   type: 'couponList/stopMerCouponActivity', payload: { id: record.id },
   callback: ({ code, data, message: info }) => {
      if (code === '0') {
         message.success(info);
         that.getData();
      } else if (code === '-1') {
         message.error(info);
      }
   }
});
// 启用禁用
enableInfo = (record) => {
   this.props.dispatch({
      type: 'couponsList/couponsListEnable', payload: { id: record.id, isEnable: !record.isEnable },
      callback: ({ code, data, message: info }) => {
         if (code === '0') {
            message.success(info);
            this.getData();
         } else if (code === '-1') {
            message.error(info);
         }
      }
   });
}
  • 4.新增、编辑按钮的交互、form表单提交的组件展示以及异步交互
addCouponsList = () => {
   this.props.history.push('/marketingManage/couponsList/couponsListAdd');
}
{btnArr.find(v => v.enCode === 'lr-add') && <div className='table-control-bar'>
   <Button onClick={this.addCouponsList} type='primary' >新增优惠券</Button>
</div>}

4.修改异步交互

models

// 原本的获取列表的函数
*couponListList({ payload, callback }, { call, put }) {
    const testRes = yield call(couponList.couponListList, payload);
    yield put({
        type: 'success',
        payload: {
            couponListListResult: testRes
        }
    });
    callback && callback(testRes);
    return testRes;
},
// 修改为以下,主要修改的地方有三处
*getMerCouponInfoPage({ payload, callback }, { call, put }) {
    const testRes = yield call(couponsList.getMerCouponInfoPage, payload);
    yield put({
        type: 'success',
        payload: {
            getMerCouponInfoPageResult: testRes
        }
    });
    callback && callback(testRes);
    return testRes;
},

... 其他的类似

修改services

// 原本的获取列表的函数
export function couponListList(params) {
    return axios.get(configs.host.test + Api.couponList, { 'params': params });
}

// 修改为以下,主要修改的地方有两处,提交参数格式与后端沟通进行交互,通常get delete的参数使用{params},post,put参数使用params
export function getMerCouponInfoPage(params) {
    return axios.get(configs.host.test + Api.getMerCouponInfoPage, { 'params': params });
}

... 其他的类似

修改api.js添加接口地址

getMerCouponInfoPage: '/api/MerCouponActivity/GetMerCouponInfoPage',
stopMerCouponActivity: '/api/MerCouponActivity/StopMerCouponActivity', // 编辑商户优惠券信息
couponsListEnable: '/api/MerCouponActivity/CouponsListEnable', // 停止活动

以上操作已经初步实现了页面的查询、删除、禁用功能

实现编辑、新增功能

新建CoupomsListAdd.js并注册路由

  • 1.获取id,定义详情数据以及页面需要定义的state等
  • 2.时间转换,文本框输入以及切换控制详情页展示
  • 3.选择弹窗进行页面加载
  • 4.新增编辑的数据处理
constructor(props){
    const id = mathmanage.getParam('id');
    this.state = {
        detailData: {
            baseProInfo: [],
        },
        oneClassList: [],
        id,
        type,
        couponDataSource: [],
        showRelationCouponModal: false,
    };
}
componentDidMount() {
    const { id } = this.state;
    if (id) {
        //请求商品详情
        this.props.dispatch({ type: 'couponsList/getMerCouponInfoDetails', payload: { id } });
    }
}
  • 2.修改render数据
<div className="common-page-content couponslist-add">
    <Spin spinning={!!this.props.loading.models.couponsList}>
        <Form >
            <PageHeader
                className="site-page-header"
                title="基本信息"
            />
            <FormItem {...formItemLayout2} label="券来源">
                {getFieldDecorator('relationProduct', {
                    initialValue: 'relationProduct',
                    rules: [
                        { required: true, message: '请选择券来源', },
                    ]
                })(
                    <Fragment>
                        {detailData.baseProInfo.length ?
                            <Table
                                scroll={{ x: getScrollWidth(this.columns) }}
                                rowKey="batchNumber"
                                columns={this.columns}
                                pagination={false}
                                dataSource={detailData.baseProInfo} />
                            : <a onClick={this.relationCoupon}>选择优惠券</a>}
                    </Fragment>

                )}
            </FormItem>
            {/*此处省略多行代码*/}
        </Form>
    </Spin>
    {showRelationCouponModal && <CouponsModal hideModal={this.hideModal} baseProInfo={cloneDeep(detailData.baseProInfo)} />}
</div>
  • 修改事件
// 提交
btnOk = () => {
    const { detailData, id, type } = this.state;
    this.props.form.validateFields((err, values) => {
        if (!err) {
            // 优惠券展示
            values.viewType = values.viewType ? 1 : '';
            // 其他限制
            values.limitRule = values.limitRule ? 1 : '';
            values.startCouponTime = moment(values.couponTime[0]).format('YYYY/MM/DD HH:mm:ss');
            values.endCouponTime = moment(values.couponTime[1]).format('YYYY/MM/DD HH:mm:ss');
            if (!detailData.baseProInfo.length) {
                return message.error('请关联优惠券');
            }
            if (values.totalNum > detailData.baseProInfo[0].totalNum) {
                return message.error('发放总量不能大于关联的优惠券的发放总量');
            }
            // 如果是编辑并且不是复制
            if (id && !type) {
                return this.props.dispatch({ type: 'couponList/updateMerCouponActivity', payload: { ...detailData, ...values, id } });
            }
            this.props.dispatch({ type: 'couponList/addMerCouponActivity', payload: { ...detailData, ...values } });
        }
    });
}
// 选择弹窗之后的关闭事件
hideModal = (baseProInfo) => {
    const { detailData } = this.state;
    if (baseProInfo) {
        detailData.baseProInfo = baseProInfo;
        detailData.batchNumber = detailData.baseProInfo.length ? detailData.baseProInfo[0].batchNumber : '';
        detailData.type = detailData.baseProInfo.length ? detailData.baseProInfo[0].type : '';
        detailData.content = detailData.baseProInfo.length ? detailData.baseProInfo[0].content : '';
        detailData.productId = '';
    }
    this.setState({
        detailData,
        showRelationCouponModal: false
    })
}
// 文本改变事件操作detailData来进行控制
changeInput = (value, field) => {
    const { detailData } = this.state;
    detailData[field] = value;
    if (field === 'noLimitNumber' && value) {
        detailData.limitNum = null;
    }
    this.setState({
        detailData
    });
}
  • 添加页面对应的models、services、api.js
  • 处理接口交互的返回值
const { getMerCouponInfoDetailsResult, updateMerCouponActivityResult, addMerCouponActivityResult } = nextProps.couponList;
if (updateMerCouponActivityResult !== this.props.couponList.updateMerCouponActivityResult) {
    if (updateMerCouponActivityResult.code === '0') {
        message.success(updateMerCouponActivityResult.message);
        return this.props.history.push('/marketingManage/couponsList');
    }
    return message.error(updateMerCouponActivityResult.message);
}
if (addMerCouponActivityResult !== this.props.couponList.addMerCouponActivityResult) {
    if (addMerCouponActivityResult.code === '0') {
        message.success(addMerCouponActivityResult.message);
        return this.props.history.push('/marketingManage/couponsList');
    }
    return message.error(addMerCouponActivityResult.message);
}
if (getMerCouponInfoDetailsResult !== this.props.couponList.getMerCouponInfoDetailsResult) {
    if (getMerCouponInfoDetailsResult.code === '0') {
        const { data } = getMerCouponInfoDetailsResult;
        let batchNumber = data.batchNumber;
        let batchName = data.batchName;
        this.props.dispatch({
            type: 'couponList/getCouponInfos', payload: {
                pageIndex: 1, pageSize: 10, batchNumber, batchName
            }
        }).then((res) => {
            const { list } = res.data;
            if (res.code === '0') {
                data.baseProInfo = list;
                // 优惠券展示
                data.viewType = data.viewType ? true : false;
                // 其他限制
                data.limitRule = data.limitRule ? true : false;
                data.noLimitNumber = data.limitNum ? false : true;
                return this.setState({
                    detailData: data
                })
            }
            return message.error(res.data.message);
        });
    } else {
        return message.error(getMerCouponInfoDetailsResult.message);
    }
}
关于表格复选的问题
  • 1.需要传递selectRows和selectRowKeys进行复选,selectRowKeys的id必须和表格的id为统一数值才能选中,selectRows主要用于多选保留之前选中的行(antd不会通过selectRowKeys自动记录你之前选中的行数据,只会记录当前选中的行数据,所以传递过来一起进行记录,这样数据就不会丢失)
  • 2.表格需要添加key的唯一标识,否则复选功能或者列表展示功能会有问题
  • 3.分页之后不会记录前一页勾选的数据,使用封装的checkRows函数
import checkRows from '../../utils/checkRows';
// 传递this,selectedRowKeys,selectedRows,主键id
const rowSelection = checkRows(this, selectedRowKeys, selectedRows, 'merBannerId');

前端开发着重了解

  • ant design的使用
  • 福禄组件库的使用
  • dva、异步交互的使用
  • 页面逻辑开发

1.ant design的使用

  1. 官方网站使用3.x版本
  2. 查找符合原型设计的组件
  3. 复制代码
const columns = [
{
  title: 'Name',
  dataIndex: 'name',
  key: 'name',
  render: text => <a>{text}</a>,
};
const data = [
{
  key: '1',
  name: 'John Brown',
  age: 32,
  address: 'New York No. 1 Lake Park',
  tags: ['nice', 'developer'],
}];
<Table columns={columns} dataSource={data} />
  1. 查看对应的API
  • api分为参数、说明、类型、默认值、版本

  • 具体使用方式
// 1.添加scroll
<Table columns={columns} dataSource={data} scroll={{ x: 1000 }} />

// 添加表格分页
const pagination = {
   total,
   showQuickJumper: true,
   pageSize: postData.pageSize,
   current: postData.pageIndex,
   pageSizeOptions: ['30', '50', '100'],
   onShowSizeChange: (current, pageSize) => {
      this.setState({ postData: { ...postData, pageIndex: current, pageSize: pageSize } }, () => {
         this.getData();
      })
   },
   onChange: (current, pageSize) => {
      console.log(current, 'current');
      this.setState({ postData: { ...postData, pageIndex: current, pageSize: pageSize } }, () => {
         this.getData();
      })
   },
};
<Table columns={columns} dataSource={data} scroll={{ x: 1000 }} pagination={pagination} />


// 添加rowSelection,表格单选多选
const rowSelection = {
    selectedRowKeys,
    onChange: (selectedRowKeys, selectedRows) => {
        this.setState({ selectedRowKeys, selectedRows });
    },
};

<Table columns={columns} dataSource={data} scroll={{ x: 1000 }} pagination={pagination} rowSelection={rowSelection} />

2.fulu组件库的使用

  1. 组件库说明文档以及代码
  2. 查找符合原型设计的组件
  3. 引用组件并查看组件的api

SearchForm

  • 使用示例
<SearchForm
    searchConfig={searchFormConfig}
    initNoSearch={this.searchFormConfig.initNoSearch}
    exportToExcelSetting={{
        url: 'http://10.0.1.29:3002/api/admin/merchantstastic/ExportMerchantStasticList',
        merchantId: '',
        params: { name: 1 },
        fileName: '123123',
        method: 'post'
    }}
    exportToExcel={this.exportToExcel}
    search={this.search}
    onFieldsChange={this.onFieldsChange}
    showCount={this.searchFormConfig.showCount}
    toggleSearchForm={this.toggleSearchForm}
    onRef={r => this.child = r}
/>
  • 属性说明
参数 说明 类型
searchConfig 传入的配置文件 Array
initNoSearch 初始化是否不加载,true:不加载,false:加载,不传默认加载列表 Boolean
exportToExcel 回调的导出excel的函数,传入则展示导出excel按钮,不传默认不展示(老功能) Function
exportToExcelSetting 传入导出excel的配置项(参数见ExportButton),不传默认不展示(新功能) Object
search 回调的查询函数 Function
onFieldsChange 字段改变传入的回调事件 Function
showCount 初始化展示文本框的个数,传入则展示收起、展开功能,不传默认展示全部 Int
toggleSearchForm 收起、展开功能的回调函数 Int
onRef 父子组件通信 this.child.reset(); this.child.search(); Int
  • 参数使用
// 传入数组
[{
    label: '库存编号', // label
    type: 'Input', // 给Input Select Time
    name: 'id', // name
    numberObj: {
        type: 'decimal' // 数字e是否可以输入
    },
    maxLength: 10,
    placeholder: '请输入监控名称', // placeholder
    initialValue: ''
},  {
    label: '创建时间',
    type: 'RangePicker',
    name: 'Time',
    initialValue: '',
    responseFiled: {
        beginTime: 'startTime',
        endTime: 'endTime',
    },
    timeFormat: 'YYYY/MM/DD HH:mm:ss',
     // showTime: {
    //     hideDisabledOptions: true,
    //     defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')],
    // },
    allowClear: true,
    disabledTime: {
        beforeDate: '2019-07-01 00:00:00',
        afterDate: '2019-08-30 23:00:00'
    },
}]

3.dva异步交互

// models
*getCouponInfos({ payload, callback }, { call, put }) {
    const testRes = yield call(couponList.getCouponInfos, payload);
    // 结合props使用
    yield put({
        type: 'success',
        payload: {
            getCouponInfosResult: testRes
        }
    });
    // 结合callback使用
    callback && callback(testRes);
    // 结合then使用
    return testRes;
},

// services
export function getCouponInfos(params) {
    return axios.get(configs.host.test + Api.getCouponInfos, { 'params': params });
}

// api.js
getCouponInfos: '/api/MerCouponActivity/GetCouponInfos', // 获取券码平台券码信息

// index.js引用models
import { default as couponList } from './models/couponList';
app.model(couponList);

// component.js
this.props.dispatch({ type: 'couponList/getCouponInfos', payload: { ...postData } });
原文地址:https://www.cnblogs.com/Hsong/p/14128206.html