第九课 复习之从零开始搭建页面
页面搭建步骤
- 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
商户侧显示效果
注意
-
安装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: '序号', width: 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: '消息类型', width: 150, key: 'tempMessageType', dataIndex: 'tempMessageType',
render(text) {
const arr = ['', '支付类型', '表单类型'];
return arr[text];
}
},
{
title: '状态', width: 150, key: 'isEnable', dataIndex: 'isEnable',
render(text) {
return text ? '启用' : '禁用';
}
},
{
title: '操作', width: 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的使用
- 官方网站使用3.x版本
- 查找符合原型设计的组件
- 复制代码
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} />
- 查看对应的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组件库的使用
- 组件库说明文档以及代码
- 查找符合原型设计的组件
- 引用组件并查看组件的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 } });