项目实战二:共享单车后台2

本文介绍了一种表格组件的封装方法,包括动态数据渲染、分页、排序等功能,并详细讲解了如何利用Ant Design和React进行实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天听雷鹏飞大佬讲了一个 material UI 记录一下 跟antd差不多
https://www.easy-mock.com/login 提供动态数据渲染 Easy mock
mockjs搭建的平台)
强大的插件Mock.js,可以非常方便的模拟后端的数据,也可以轻松的实现增删改查这些操作。

ps:操作时间码转换成统一格式
const columns=[
{title:'...',
dataIndex:'...',
render:Utils.formateDate
}]

formateDate(time){
	if(!time)return '';
	let date= new Date(time);
	return date.getFullYear() + '-'+(data.getMouth()+1)+'-'+date.get.....;
}

数据
{
"code":0,//成功时返回
"msg":"",//错误时返回信息 等同于"message"
"result|10":[{  //|10指的是定义10条数据
	id:'0',
	userName:'Jack',
	...
},
...
],
page:1,
page_size:10,
}

{
"code":0,//成功时返回
"msg":"",//错误时返回信息 等同于"message"
"result":{  //|10指的是定义10条数据
	"list|5":[{
	"id|+1":1, //id值每次递增+1
	"userName":“@cname",//自动获取中文名
	"sex|1-2":1,//性别只有1、2
...
			}],	
page:1,//第一页
page_size:10,//1页放10条
total:100//100页
	}
}

表格
pages/tabke
basic.js

src/config/menuConfig.js 定义路由地址 名字 等

menuConfig.js
export default [
	{
	 	title:'首页',
	 	key:'/admin/home'
	},
	{	title:'UI',
	key:'admin/ui',
	children:[
		{
			title:'按钮',
			key:'url',
		},//一个大括号里是一个人的信息
		...
	]},
	...
]

basic.js
...
state={//state里数据用来渲染Htmldom结构的 执行setState会重新渲染dom
	dataSource2:[]//dataSource2如果不存入state render不执行
}
params={//只改变页码不需要改变dom结构
	page:1 //接口调用需要 而不是页面需要
}
componentDidMount(){
	const dataSource =[//数据源
		{
			id:'0',
			userName:'Jack'
			sex:'1',
			...
		}
	]
	data.map((item,index)=>{
		item.key = index;
	}) //datasource在表格中应有独一无二的key
	this.setState({
		dataSource//相当于dataSource:dataSource
		
	})
	this.request();
}

request=() =>{ //动态获取mock数据
	let baseUrl = 'url';
	axios.get(baseUrl +'/table/list').then((res)=>{//.then...就是一个promise的结构
		if(res.status =='200'&&res.data.code==0){
			this.setState({
				dataSource2:res.data.result,
				selectedRowKeys:[],
				selectedRows:null
			})
		}
	})
}
onRowClick =(record,index) =>{
	let selectKey = [index];//多选会用到这个数组
	this.setState({
		selectedRowKeys:selectKey,//选中索引
		selectedItem:record//选择某一行
	})
}
//多选执行删除动作
handleDelete =()=>{
	let row = this.state.selectedRows;
	let ids= [];
	rows.map((item)=>{
		ids.push(item.id)
	})
	Modal.confirm({
		title:'...',
		content:`...${ids.join(',')`,
		//用,连接起来ids里面的值
		onOk:()=>{
			message.success('...');
			this.request();//刷新一下
			}
		})
	}
render () {
	const columns =[//定义表头
		{
			title:'id',//列名
			dataIndex:'id'//数据项中所对应的key
		},
		{
			title:'用户名',//列名
			dataIndex:'userName'
		},
		{
			title:'性别',//列名
			dataIndex:'sex',
			render(sex){
				return sex==1 ? '男':'女'
			}
		},
		{
			title:'状态',
			dataIndex:'state',
			render(abc){//或者state
				let config ={//太多了的话也可以专门定义一个字典文件.js
					'1':'咸鱼',
					....
				}
				return config[abc];//或者state
			}
			还有一种写法:
			render(abc){//或者state
				return {
					'1':'咸鱼',
					'2':'风华浪子',
					...
				}[abc];//或者state
			}
		}
		...
	]
	const { selectedRowKeys } =this.state;//等于
	const selectedRowKeys =this.state.selectedRowKeys ;
	const rowSelection = {
		type:'radio',//复选为checkbox
		selectedRowKeys//存选中某一项的key
		//也可以加onchange:意为选中下一个框时发生了改变启动事件
	}
	const rowSelection = {
		type:'checkbox',//复选为checkbox
		selectedRowKeys,//存选中某一项的key
		onChange:(selectedRowKeys,selectedRows)=>{//rows指行
			let ids =[];
			selectedRows.map((item)=>{
				ids.push(item.id)
			})
			this.setState({
				selectedRowKeys,
				selectedIds:ids//存下对应的id
			})
		}
		
	}
	return(
		<div>
			<Card title="基础表格">
				<Table 
					bordered //边框
					columns={columns}//如果放在render外需要用this.state.columns引用
					dataSource={this.state.dataSource}
					pagination={false}//不分页
				/>
			</Card>
				<Card title="动态数据渲染表格" style={{margin:'10px 0' }}>
				<Table 
					bordered //边框
					columns={columns}//如果放在render外需要用this.state.columns引用
					dataSource={this.state.dataSource2}
					pagination={false}//不分页
				/>
			</Card>
			<Card title="Mock-单选" style={{margin:'10px 0' }}>
				<div>
					<Button onClick={}this.handleDelete}>删除</Button>
				</div>
				<Table 
					 onRow={(record,index) => {
    					return {
     				 		onClick: () => {
     				 			this.onRowClick(record,index);
     				 		}     // 点击行即可选中
						};
					bordered //边框
					rowSelection={rowSelection}//设置为单选
					columns={columns}//如果放在render外需要用this.state.columns引用
					dataSource={this.state.dataSource2}
					pagination={false}//不分页
				/>
			</Card>
			<Card title="Mock-分页" style={{margin:'10px 0' }}>
				<Table 
					bordered //边框
					columns={columns}//如果放在render外需要用this.state.columns引用
					dataSource={this.state.dataSource2}
					pagination={this.state.pagination}//把接口里数据存下来
				/>
			</Card>
		</div>
	)

}
src/axios/index.js
...
//请求插件的封装
static ajax(options) { //resolve成功时返回 reject失败时返回
	let loading;
	if(options.data && options.data.isShowLoading !== false){//不是false就show
		loading = document.getElementById('ajaxLoading'); //loading代码在全局html文件里 
		//直接用这种方式获取
		loading.style.display = 'block';
	}
	let baseApi= 'url';
	return new Promise((resolve,reject)=>{
		axios({
			url:options.url,
			method:'get',
			baseURL:baseApi, //地址通用前缀
			timeout:5000,//超过五秒即超时会报错
			params:(options.data && options.data.params)||'' //获取参数
		}).then((response)=>{
			if(options.data && options.data.isShowLoading !== false){//不是false就show
				loading = document.getElementById('ajaxLoading'); //loading代码在全局html文件里 
		//直接用这种方式获取
				loading.style.display = 'none';//关闭掉
			}
			if(response.status == '200'){//HTTP状态请求码
				let res= response.data;
				if(res.code == '0') {//业务代码成功定义为0
					resolve(res);//把res.data抛出去 我们可以接收到
				}else{
					Modal.info({ //错误信息
						title:"提示",
						content:res.msg
					})
				}
			}
			else {reject(response.data);}//把它打印出来并报错
		})
	});
	
}

封装了之后我们可以修改上述的request 即
request =() =>{
	let _this =this;//怕作用域出问题 提前提取this
	axios.ajax({
		url:'/table/list',
		data:{
			params:{ page:this.params.page},//第一页的
			isShowLoading:false//不需要show loading
		}
	}).then((res)=>{
		if(res.code == 0){
			res.result.list.map((item,index)=>{
				item.key= index;
			})
			this.setState({
				dataSource2:res.result.list,
				selectedRowKeys:[],
				selectedRows:null,
				pagination:Utils.pagination(res,(current)=>{
					_this.params.page = current;//把current存入this.params.page里
					this.request();
				})
			})
		}
	})
}
然后要做一个在请求完成之前的loading效果
public/index.html
....
<div id="root"></div>//插入以下代码
<div class="ajax-loading" id="ajaxLoading" style="display:none;">
	<div class="overlay"></div>
	<div class="loading">
		<img src="url" alt=" ">
		<span>加载中</span>
	</div>
</div>

还有性别等里面显示的是数字 我们希望显示文字 修改一下columns
那如果想在打开的网页中修改表格数据呢?

----
分页功能
utils.js
export default{
	...,//关于分页的封装
	//当点击下一页时触发回调函数 
	pagination(data,callback){
		let page = {//current当前页数 onchange为页码改变的回调,参数是改变后的页码及每页条数
			onChange:(current)=>{
				callback(current);
			},
			current:data.result.page,
			pageSize:data.result.page_size,
			total:dada.result.total,
			showTotal:()=>{//用于显示数据总量和当前数据顺序
				return `共${data.result.total}条`
			},
			showQuickJumper:true//showQuickJumper是否可以快速跳转至某页
		}
		return page;
		//或者 整个return {...}即可
	},
	getOptionList(data){
		if(!data){
			return [];
		}
		let option = [];
		data.map((item)=>{
			options.push(<Option value={item.id} key={item.id}>{item.name}</Option>)
		})
		return options;//不要忘记返回
	}
}

高级表格:分别是表头固定和左侧固定
pages/table/highTable.js

handleChange =(pagination, filters, sorter)=>{
	this.setState({
		sortOrder:sorter.order
	})
}
handleDelete =(item) =>{
	let id =item.id;
	Modal.confirm({
		title:'确认',
		content:'确认删除?',
		onOk:()=>{
			message.success('删除成功');
			this.request();
		}
	})
}
render () {
	const columns =[//定义表头
		{
			title:'id',//列名
			key:'id',
			width:80,//这一列宽度为80
			dataIndex:'id'//数据项中所对应的key
		},
		{
			title:'用户名',//列名
			dataIndex:'userName'
		},
		{
			title:'性别',//列名
			dataIndex:'sex',
			render(sex){
				return sex==1 ? '男':'女'
			}
		},
		{
			title:'状态',
			dataIndex:'state',
			render(abc){//或者state
				let config ={//太多了的话也可以专门定义一个字典文件.js
					'1':'咸鱼',
					....
				}
				return config[abc];//或者state
			}
		}
		...
	]
	const columns2 =[//定义表头
		{
			title:'id',//列名
			key:'id',
			width:80,//这一列宽度为80
			fixed:'left', //该列左侧固定不动
			dataIndex:'id'//数据项中所对应的key
		},
		{
			title:'用户名',//列名
			dataIndex:'userName'
		},
		{
			title:'性别',//列名
			dataIndex:'sex',
			render(sex){
				return sex==1 ? '男':'女'
			}
		},
		{
			title:'状态',
			dataIndex:'state',
			render(abc){//或者state
				let config ={//太多了的话也可以专门定义一个字典文件.js
					'1':'咸鱼',
					....
				}
				return config[abc];//或者state
			}
		}
		...
	]
	const columns3 =[//定义表头
		{
			title:'id',//列名
			key:'id',
			width:80,//这一列宽度为80
			dataIndex:'id'//数据项中所对应的key
		},
		{
			title:'用户名',//列名
			dataIndex:'userName'
		},
		{
			title:'年龄',//列名
			key:'age',
			dataIndex:'age',
			width:80,
			sorter:(a,b)=>{
				return a.age -b.age; //小于0则a<b 即为升序
			},
			sortOrder:this.state.sortOrder
		},
		{
			title:'性别',//列名
			dataIndex:'sex',
			render(sex){
				return sex==1 ? '男':'女'
			}
		},
		{
			title:'状态',
			dataIndex:'state',
			render(abc){//或者state
				let config ={//太多了的话也可以专门定义一个字典文件.js
					'1':'咸鱼',
					....
				}
				return config[abc];//或者state
			}
		}
		...
	]
	const columns4 =[//定义表头
		{
			title:'id',//列名
			列宽度为80
			dataIndex:'id'//数据项中所对应的key
		},
		{
			title:'用户名',//列名
			dataIndex:'userName'
		},
		{
			title:'年龄',//列名
			dataIndex:'age',
			width:80,
		},
		{
			title:'性别',//列名
			dataIndex:'sex',
			render(sex){
				return sex==1 ? '男':'女'
			}
		},
		{
			title:'状态',
			dataIndex:'state',
			render(abc){
				let config ={//badge 徽标
					'1':<Badge status="success" text="1"/>,//status :error default processing warning
					....
				}
				return config[abc];//或者state
			}
		},
		{
			title:'操作',
			render:(text,item)=>{//item指一行  改为箭头函数使this指向react里面
				return <Button size="small" onClick ={(item)=>{this.handleDelete(item)} }>删除</Button> 
			}
		}
		...
	]
	
	return(){
		<div>
				<Card title="头部固定">
				<Table 
					bordered //边框
					columns={columns}//如果放在render外需要用this.state.columns引用
					dataSource={this.state.dataSource}
					pagination={false}//不分页
					scroll={{y:240}}//在y轴滚动 :后为高度px 
					//注意表头和表格要对齐 
				/>
			</Card>
				<Card title="左侧固定" style={{margin:'10px 0' }}>
				<Table 
					bordered //边框
					columns={columns2}//如果放在render外需要用this.state.columns引用
					dataSource={this.state.dataSource2}
					pagination={false}//不分页
					scroll={{x:2650}}//在x轴滚动 :后为高度px  注意把每一列的宽度相加再多一点的值放到这
					//注意表头和表格要对齐 
				/>
			</Card>
			<Card title="表格排序" style={{margin:'10px 0' }}>
				<Table 
					bordered //边框
					columns={columns3}//如果放在render外需要用this.state.columns引用
					dataSource={this.state.dataSource2}
					pagination={false}//不分页
					onChange={this.handleChange} //排序 分页都可用
					scroll={{x:2650}}//在x轴滚动 :后为高度px  注意把每一列的宽度相加再多一点的值放到这
					//注意表头和表格要对齐 
				/>
			</Card>
			<Card title="操作按钮" style={{margin:'10px 0' }}>
				<Table 
					bordered //边框
					columns={columns4}//如果放在render外需要用this.state.columns引用
					dataSource={this.state.dataSource2}
					pagination={false}//不分页
					scroll={{x:2650}}//在x轴滚动 :后为高度px  注意把每一列的宽度相加再多一点的值放到这
					//注意表头和表格要对齐 
				/>
			</Card>
		</div>
	}
	

城市开通

state ={ 
	list:[],
	isShowOpenCity:false
}
page = {
	page:1
}


componentDidMount(){
	this.requestList();
}
requestList ={ //获取接口中的list 
	let _this= this;
	axios.ajax({
		url:'...',
		data:{
			params:{
				page:this.params.page
			}
		}
	}).then((res)=>{
		this.setState({
			list:res.result.item_list.map((item,index)=>{//为了加上独一的key值 不然报错
			item.key = index;
			return item;//返回一个全新的对象 不return还是之前那个老版的
		}),
			pagination:Utils.pagination(res,()=>{
			_this.params.page =current;
			_this.requestList();
			})
		})
	})
}
handleOpenCity =() =>{
	this.setState({
		isShowOpenCity:true
	})
}

handleSubmit =()=>{
	let cityInfo =this.cityForm.props.form.getFieldsValue();
	axios.ajax({
		url:'url',
		data:{
			params:cityInfo
		}
	}).then((res)=>{
		if(res.code =='0'){
			message.success('开通成功');
			this.setState({
				isShowOpenCity:false
			})
			this.requestList();//更新列表
		}
	})
}
render(){
	const columns = [
		表格其中的一列 一格中数据不能渲染复杂的对象、数组等,只能渲染普通的如数字文字等,所以要修改数据源
		{
			title:'...',
			dataIndex:'...', //dataIndex传过来的值是一个数组
			render(arr){
				return arr.map((item)=>{
					return item.user_name;//得到全新的数组 数组中只有username
				}).join(',';//以逗号分开并连接成一个全新的字符串
			}
		},
		...
	]
	return(
		<div>
			<Card>
				<FilterForm />
			</Card>
			<Card>
				<Button type="primary" onClick={this.handleOpenCity} />			
			</Card>
			<div className="content-wrap">
				<Table 
					columns={columns}
					dataSource={this.state.list}
					pagination={this.state.pagination}
				/>
			</div>
			<Modal
				title="开通城市"
				visible={this.state.isShowOpenCity}
				onCancle={()=>{
					this.setState({
						isShowOpenCity:false
					})
				}}
				onOk={this.handleSubmit}
			>
				<OpenCityForm wrappedComponentRef={(inst)=>{this.cityForm = inst;}}/>
				//wrappedComponentRef相当于ref 把表单对象存到本地 便于获取里面的值
				//再次引用时直接this.cityForm.props.from.getFieldValue();
				
	)
}
//注意是在一个文件里
class OpenCityForm extends React.Component{
	render(){
		const formItemLayout ={
			labelCol:{span:5},
			wrapperCol:{span:10}
		}
		const { getFieldDecorator } =this.props.form;
		//辅助性的帮助双向数据绑定
		return(
			<Form layout="horizontal" {...formItemLayout}>
				<FormItem label="选择城市">
					{
						getFieldDecorator('city_id',{
							initialValue:'1'
					})
						(<Select>
							<Option value="1">全部</Option>
							<Option value="2">北京市</Option>
							<Option value="3">天津市</Option>
						</Select>)
					}
				</FormItem>
				...
			</Form>
		)
	}
}
OpenCityForm = Form.create({})(openCityForm);

订单管理
pages/order/index.js

const FormItem=Form.Item;
const Option = Select.Option;
state={}
params ={ page:1}
componentDidMount(){
	this.requestList()
}
requestList =()=>{
	axios.ajax({
		url:'/order/list',
		data:{
			params:{
				page:this.params
			}
		}
	}).then((res)=>{//写法1
		let list = res.result.item_list.map((item,index) => {
			item.key = index;
			return item;
		});if(res.code ==0)
			this.setState({
			list:res.result.item_list.map((item,index)=>{//为了加上独一的key值 不然报错
			item.key = index;
			return item;//返回一个全新的对象 不return还是之前那个老版的
		}),
			pagination:Utils.pagination(res,()=>{
			_this.params.page =current;
			_this.requestList();
			})
		})

	})
}

handleFinishOrder =() =>
{
	let item = this.state.selectedItem;
	if(!item){
		Modal.info({
			title:'信息',
			content:'请选择一条订单结束'
		})
		return;
	}
	axios.ajax({
		url:'/order/ebike_info',
		data:{
			params:{
				orderId:item.id
			}
		}
	}).then((res)=>{
		if(res.code ==0){.
			this.setState({
				orderInfo:res.result,
				orderConfirmVisible:true
		}),
			pagination:Utils.pagination(res,()=>{
			_this.params.page =current;
			_this.requestList();
			})
		})

	})}

}

openOrderDetail =()=>{
	let item = this.state.selectedItem;
	if(!item){//没选择一个行时
		Modal.info({
			title:'信息',
			content:'请选择一条订单结束'
		})
		return;
	}
	//新建页加载
	window.open(`/#/common/order/detail/${item.id}`'_blank')
	//window.location.href = `/#/common/order/detail/${item.id}`同一页加载
}

handleFinishOrder =() =>{
}

render(){
	const columns=[//表头
		{
			title:'订单编号',
			dataIndex:'order_sn'//要严格 遵守接口的返回名
		},
		{
			title:'车辆编号',
			dataIndex:'bike_sn'//要严格 遵守接口的返回名
		},
		{
			title:'用户名',
			dataIndex:'user_name'//要严格 遵守接口的返回名
		},
		{
			title:'手机号',
			dataIndex:'mobile'//要严格 遵守接口的返回名
		},
		{
			title:'里程',
			dataIndex:'distance'//要严格 遵守接口的返回名
		},
		。。。
	]
	
	return(
		<div>
			<Card>
				<FilterForm />
			</Card>
			<Card>
				<Button onClick={this.openOrderDetail>订单详情</Button>
				<Button>结束订单</Button>
			</Card>
			<div className="content-wrap">
				<Table
					bordered
					columns={columns}
					dataSource={this.state.list}
					pagination={this.state.pagination}
					//注意区分:datasource里面是数据 columns里面是标题以及它的索引和key值
				/>
			</div>
		</div>
	)
}

<FilterForm />:
render(){
	const {getFieldDecorator}=this.props.form;
	return(
		<Form layout="inline">
			<FormItem label="城市">
				{
					getFieldDecorator('city_id')(
						<Select
							style={{width:100}}
							placeholder="全部"
						>
							<Option value=""> 全部</Option>
							<Option value="1"> 北京市</Option>
							。。。
						</Select>
							)
	)
}
</FormItem>
<Form layout="inline">
			<FormItem label="订单时间">
			{
					getFieldDecorator('start_time')(
						<DatePicker showTimen format="YYYY-MM-DD HH:mm:ss" />
				)
	
			}	
			{
					getFieldDecorator('end_time')(
						<DatePicker style={{marginLeft:10}} showTimen format="YYYY-MM-DD HH:mm:ss" />
				)
	
			}	
</FormItem>

通用页面的结构设计

router.js
import ....
export default class ....{
	render(){
		return(
			<HashRouter>
				<App>
					<Route path="/login" component={Login} />
					<Route path="/admin" render={()=>
					<Admin>//都属于admin下面的子页面
						<Switch>
						<Route path="/admin/ui/buttons" component={Buttons} />//子路由的嵌套
						<Route  component={NoMatch} />//没匹配的都会跳转到404
						</Switch>
					</Admin>
					} />//进入admin即可进入主页面
					
				</App>
			</HashRouter>
			
		);
	}
}

//通用的js文件承载大体不动的部分 比如页头页脚 。。只改变里面的内容部分
admin.js
import React from 'react'
import { Row } from 'antd';
import './style/common.less'
import Header from './components/Header'
import Footer from './components/Footer'
import NavLeft from './components/Navleft'
export default class Admin extends React.Component {
	render(){
		return (
			<Row className="container">
				<Col span="3" className="nav-left">
					<NavLeft />
				</Col>
				<Col span="21" className="main">
					<Header />
					<Row className="content">
					{this.props.children}//动态实现 嵌套子组件
					</Row>//body区域
					<Footer />
				</Col>
			</Row>

		)
	}
}

我们再建一个common.js作为类似一个admin.js 作为详情页面

common.js
<Row className="simple-page">//二级页面头部不一样 另取一个classname
<Header menuType="second" />//menuType //传了一个second值
</Row>
...

在header/index.js
render(){
	const menuType =this.props.menuType;
	render(
		...
		{//menutype有值返回空 无值返回页头
			menuType?'':
			<div className="header">
				<Row className="header-top"> //行
					{
						menuType?<Col span="6">
							<img src="/assets/logo-ant.svg" alt="" />
							<span>IMooc 通用管理系统 </span>
					</Col>:' '
					}
					<Col span={menuTyoe?18:24}>
						<span>欢迎,{this.state.userName}</span>
						<a href="#">退出</a>
					</Col>
				</Row>
				<Row className="breadcrumb">
					<Col span="4" className="breadcrumb-title">
					首页
					</Col>
					<Col span="20" className="weather">
						<span className="date">{this.state.sysTime}</span>
						<span className="weather-img">
							<img src={this.state.dayPictureUrl} alt="" />
						</span>
						<span className="weather-detail">
							{this.state.weather}
						</span>
					</Col>
				</Row>
			</div>

		}
	)
	}

定义路由
<Route path="/common" render{() =>
	 <Common> 
		<Route path="/common/order/detail/:orderId" component={OrderDetail}/>//:后加动态路由 变量为orderId
	</Common>
} />

const info =this.state.orderInfo ||{};
//若为空则是一个{}

.clearfix//清除浮动
首先来看一下伪元素 ::after
https://www.cnblogs.com/starof/p/4459991.html
里面有清除浮动实例
https://www.cnblogs.com/937522zy/p/6650708.html
我们提取一个典型出来

.cf:before,
.cf:after {
    content: " ";//清空内容
    clear:both;//清除两边浮动
    display:block;
    visibility:hidden;//使元素不可见 不然会占位
}
//可在元素头/尾部自动清除浮动

地图功能实现
打开map.baidu.com
然后点击最下面的百度地图开放平台
开发文档 - javascript API
使用前应先申请ak密钥 申请成为开发者 登陆
创建应用
在public/index.html
在title下面把<script …><… />引入进来即可

BMap挂载在window下 window....来引用

然后在detail.js里初始化地图 在render上面
通过this.renderMap(res.result);来调用它

renderMap =(result)=>{
	this.map = new window.BMap.Map('要初始化的百度地图的div的id')this.map.centerAndZoom('北京',11);
	//BMap.Map.centerAndZoom()方法要求设置中心点坐标和地图级别。
	this.addMapControl();
	this.drawBikeRoute(result.position_list);
	this.drawServiceArea(result.area);
}
//添加地图控件
addMapControl =() =>{
	let map = this.map;
	map.addControl(new  window.BMap.ScaleControl({ anchor:  window.BMAP_ANCHOR_TOP_RIGHT}));//比例尺 右上角
	map.addControl(new  window.BMap.NavigationControl({ anchor:  window.BMAP_ANCHOR_TOP_RIGHT}));//比例尺 右上角
}
//绘制用户行驶路线 positionList坐标点
drawBikeRoute =(positionList)=>{
	let map =this.map;
	let startPoint ='';//起始点
	let endPoint = '';
	if(positionList.length>0){
		let first =positionList[0];
		let last =positionList[positionList.length-1];
		startPoint = new window.BMap.Point(first.lon,first.lat);//lon经度 lat纬度
		let startIcon = new window.BMap.Icon('/assets/start_point.png',new window.BMap.Size(36,42),{//icon需要的空间大小
			imageSize:new window.BMap.Size(36,42), //图像大小
			anchor:new window.BMap.Size(36,42)//anchor图标的定位点相对于图标左上角的偏移值
		})//图标宽高
		let startMarker =new window.BMap.Marker(startPoint,{icon:startIcon});//将坐标点和图标对应起来 点在文档里面叫做marker
		this.map.addOverlay(startMarker);//添加点

		endPoint = new window.BMap.Point(last.lon,last.lat);//lon经度 lat纬度
		let endIcon = new window.BMap.Icon('/assets/start_point.png',new window.BMap.Size(36,42),{//icon需要的空间大小
			imageSize:new window.BMap.Size(36,42), //图像大小
			anchor:new window.BMap.Size(36,42)//anchor图标的定位点相对于图标左上角的偏移值
		})
		let endMarker =new window.BMap.Marker(endPoint,{icon:endIcon});//将坐标点和图标对应起来 点在文档里面叫做marker
		this.map.addOverlay(endMarker);//添加点

		//连接路线图
		let trackPoint =[];//跟踪点
		for(let i=0;i<positionList.length;i++){
			let point =positionList[i];
			trackPoint.push(new window.BMap.Point(point.lon,point.lat));
		}
		
		let polyline =new window.BMap.Polyline(trackPoint,{//折现属性 详情见http://lbsyun.baidu.com/cms/jsapi/reference/jsapi_reference.html#a3b11
			strokeColor:'#FFFFFF',
			strokeWeight:3,
			strokeOpacity:1
		})
		this.map.addOverlay(polyline);
		
		this.map.centerAndZoom(endPoint,11);
	}
	
}
drawServiceArea =(positionList) =>{
		//绘制服务区
		let trackPoint =[];//跟踪点
		for(let i=0;i<positionList.length;i++){
			let point =positionList[i];
			trackPoint.push(new window.BMap.Point(point.lon,point.lat));
		}
		
		let polygon = new Window.BMap.Polygon(trackPoint,{
			strokeColor:'#FFFFFF',
			strokeWeight:3,
			strokeOpacity:1,
			fillColor:'#ff8605',
			fillOpacity:0.4
		})
		this.map.addOverlay(polygon);
}

render(){
	const info =this.state.orderInfo || {};
	return(
		<div>
			<Card>
				<div id="orderDetailMap" className="order-map">要初始化的百度地图的div</div> 
	)
}

项目工程化

  • 项目架构设计
  • 目录结构定义
  • 制定项目开发规范 (实例如https://www.cnblogs.com/luxiaoxiao/p/6432788.html) eslint
  • 模块化 组件化
  • 前后端接口规范
  • 性能优化 自动化部署(压缩 合并 打包

对表单等基本组件进行封装
src/components/BaseForm 文件夹

  • index.js
应用:
在order/index.js下测试
封装了之后就可以把之后的Filterform删掉了
formList = [
	{
		type:'SELECT',
		label:'城市',
		field:'city',
		placeholder:'全部',
		initialValue:'1',
		width:100,
		list:[{id:'0',name:'全部'},{id:'1',name:'北京'},{id:'2',name:'天津'},{id:'3',name:'上海'}]
	},
	{
		type:'时间查询',
		
	},
	{
		type:'SELECT',
		field:'order_status',
		label:'订单状态',
		placeholder:'全部',
		initialValue:'1',
		width:100,
		list:[{id:'0',name:'全部'},{id:'1',name:'进行中'},{id:'2',name:'结束行程'},]
	},
	
]

handleFilter = (params)=>{
	this.params = params;
	this.requestList();
}
return(
	<div>
		<Card>
			<BaseForm formList = { this.formList } filterSubmit={this.handleFilter} /> //formlist为了把该表单数据传给封装好的basicform 
	</div>
)
BaseForm /index.js

const FormItem =Form.Item;
const Option = Select.Option;

class FilterForm extends React.Component 
{
	handleFilterSubmit =() =>{
		let fieldsValue =this.props.form.getFieldValue();
		this.props.filterSubmit(fieldsValue); //调用那边本部的方法 这个不是固定的
	}
	
	reset =()=>{
		this.props.form.resetFields();
	}
	
	initFormList =()=>{
		const { getFieldDecorator } =this.props.form;
		//antd里有getFieldDecorator参数的设置
		const formList = this.props.formList;//然后我们需要把数据解析出来
		const formItemList = [];
		if(formList &&formList.length>0){
			formList.forEach((item,i) =>{
				let label = item.label;
				let field = item.field;
				let initValue = item.initialValue || '';
				let placeholder = item.placeholder;
				let width =item.width;
				if(item.type == '时间查询'){
					const begin_time=<FormItem label={label} key={field} >
						{
							getFieldDecorator('begin_time',{
									initialValue:initialValue,
									
								})(
									<DatePicker showTime={true} placeholder={placeholder} format="YYYY-MM-DD HH:mm:ss"/>
									)
						}</FormItem>;
					formItemList.push(begin_time);
					
					const end_time=<FormItem label={label} key={field} >
						{
							getFieldDecorator('begin_time',)(
									<DatePicker showTime={true} placeholder={placeholder} format="YYYY-MM-DD HH:mm:ss"/>
									)
						}</FormItem>;
					formItemList.push(end_time);
				}
				if(item.type =='SELECT'){
					const SELECT = <FormItem label="~" colon={false}  key={field} >
					//colon 不显示label后的:
						{
							getFieldDecorator('end_time')(
								<Select 
									style={{ width:width  }}
									placeholder={placeholder}
								>
									{Utils.getOptionList(item.list)}
									//遍历option 在utils里进行封装
								</Select>
									
							)
						}
					</FormItem>;
					formItemList.push(SELECT)
				} else if (item.type == 'INPUT'}{
					const INPUT = <FormItem label={label} key={field} >
						{
							getFieldDecorator([field],{
									initialValue:initialValue,
									
								})(
									<Input type="text" placeholder={placeholder} />
									)
						}</FormItem>;
						formItemList.push(INPUT)
				}
				else if (item.type == 'CHECKBOX'}{
					const SELECT = <FormItem label={label} key={field} >
						{
							getFieldDecorator([field],{
									valuePropName:'checked',//这个属性必须要加上
									initialValue:initialValue,//checkbox初始值必须为true/false
									
								})(
									<Checkbox> 
										{label}
									<Checkbox />
							)
						}</FormItem>;
						formItemList.push(CHECKBOX)
				}
				
			})
		}
		return formItemList;
	}
	render(){
		return(
			<Form layout="inline">
				{this.initFormList()}
			</Form>
			<FormItem>
				<Button onClick={this.handleFilterSubmit}>查询</Button>
				<Button onClick={this.reset}>重置</Button>
			</FormItem>
			)
	}
}
export default Form.create({})(FilterForm);

公共机制封装
打开src/axios/index.js
定义一个专门表单查询的

	static requestList(_this,url,params,isMock){//还可以检测是否是mock数据 根据情况变化
		var data= {
			params:params
			//isMock
		}
		this.ajax({
			url:url,
			data:data,//可以省略为url,data
		}).then((data)=>{
			if(data&&data.result){
				let list = data.result.item_list.map((item,index) => {
					item.key = index;
					return item;
				});
				_this.setState({
					list,
					pagination:Utils.pagination(data,(current) =>{
						_this.params.page = current;
						_this.requestList(); //注意 此this非彼this 我们需要修改order index里的requestList 修改如后所示
					}
				})
			}
		})
	}

order index.js 修改如下 (为了传递this
requestList= ()=>{
	let _this=this;
	axios.requestList(this,'/order/list',this.params);
}

表格封装

<Table
	bordered //边框
	columns={columns} //表头
	dataSource={this.state.list} //数据源 与表头形成一一映射 渲染出表格
	pagination={this.state.pagination}//分页
	rowSelection={rowSelection}//控制单选复选
	onRow={(record,index) =>{
		return{
			onClick:()=>{
				this.onRowClick(record,index);
			}
		};
	}}//跟单选复选配合 点中某一行实现某一行被选中
	
components/ETable/index.js
import Utils from './../../utils/utils'

export default class ETable extends React.Component{
	onRowClick = (record,index)=>{
		let rowSeletion =this.props.rowSelection;
		if(rowSelection =='checkbox'){//多选框需要的是id
			let selectedRowKeys=this.props.selectedRowKeys;
			let selectedIds = this.props.selectedIds;
			let selectedItem =this.props.selectedItem;
			if(selectedIds){
//已有那么要判断是否有重复的
			const i=selectedIds.indexOF(record.id);
			if(i==-1){//新的放入 旧的取消勾选
				selectedIds.push(record.id);
				selectedRowKeys.push(index);
				selectedItem.push(record);
			}else{
				selectedIds.splice(i,1);//注意 slice不会改变原先数组 只会返回新数组 splice改变原先数组
				selectedRowKeys.splice(i,1);
				selectedItem.splice(i,1);
			}
			}else{
				selectedIds=[record.id];
				selectedRowKeys=[index];
				selectedItem =[record];
				this.props.updateSelectedItem(selectedRowKeys,selectedItem,selectedIds)
			}
		}else{
			let selectedRowKeys = [index];
			let selectedItem =record;//单选不需要[record]数组
			this.props.updateSelectedItem(selectedRowKeys,selectedItem)
		}
	}
	tableInit =()=>{
		let selectedRowKeys = this.props.selectedRowKeys;
		let row_selection =this.props.rowSelection;
		const rowSelection ={
			type:'radio',
			selectedRowKeys,//选中哪一行
			onChange:this.onSelectChange//跟上个属性配合使用
			
		}
		if(row_selection === false ||row_selection===null){
			row_selection = false;
		}else if(row_selection=='checkbox'){
			row_selection.type = 'checkbox';
		}else 
		{	row_selection ='radio' }
		this.props 
		return <Table
			bordered //边框
			{...this.props} //把这个源组件的属性传到这里来 即 order index.js的columns datasource pagination
			rowSelection={row_Selection?rowSelection:null}//控制单选复选
			onRow={(record,index) =>{//record是一整行数据 index是索引值
				return{
					onClick:()=>{
						if(!row_selection)return;				this.onRowClick(record,index);
					}
				};
			}}
			/>
	}
	render(){
		return(
			<div>
				{this.tableInit()}
			</div>
		);
	}
}

应用到order/index.js
import ETable from './../../Components/ETable'return上面的const selectedRowKeys rowSelection onRowClick都删掉
<div className="content-wrap">
	<ETable
		selectedItem ={this.state.selectedItem} //接口统一要id时可以删除selecteditem
		selectedIds={this.state.selectedIds}
		updateSelectedItem ={Utils.updateSelectedItem.bind(this)}//把当前页面的作用域传进去  
		columns={columns}
		dataSource={this.state.list} //数据源 与表头形成一一映射 渲染出表格
		pagination={this.state.pagination}//分页
		selectedRowKeys={this.state.selectedRowKeys}
		rowSelection={false}
	/>
</div>

Utils.js
updateSelectedItem(selectedRowKeys,selectedItem,selectedIds){//选中的行的key 选中的那一行
//判断单选复选
	if(selectedIds){
		this.setState({
		selectedRowKeys,
		selectedItem,
		selectedIds
	})
	}else{
		this.setState({
				selectedRowKeys,
				selectedItem
			})
	}
	

}

封装了之后可以用一个方法做各种功能

onClick={()=>this.handle('create')}
onClick={()=>this.handle('edit')}
onClick={()=>this.handle('detail')}
onClick={()=>this.handle('delete')}
函数里区别参数即可

注意
返回数组时<Radio value={1}>变量形式 而非"1" 字符串形式
返回字符串时才能用"1"

要想通过
<Modal 
	footer={this.state.type=='detail'?null:''}>//不可行
解决方案:
<Modal 
	{...footer}>

let footer={};
if(this.state.type=='detail'){
	footer={
		footer:null
	}
}

车辆地图

renderMap =(res) =>{
	let list =res.result.route_list;
	this.map =new window.BMap.Map('container');//在div是这个名的里面渲染地图
	let gps1=list[0].split(',');//起点
	let gps2=list[list.length-1].split(',');//起点
	let startPoint =new window.BMap.Point(gsp1[0],gsp1[1]);//一个数组里面 左边经度 右边纬度
	let endPoint =new window.BMap.Point(gsp2[0],gsp2[1]);
	this.map.centerAndZoom(endPoint,11);//以什么点居中
	let startPointIcon = new window.BMap.Icoon('url.png',new window.BMap.Size(36,42),{//指定属性
	imageSize:new window.BMap.Size(36,42)
	})
	let endPointIcon = new window.BMap.Icoon('url.png',new window.BMap.Size(36,42),{
	imageSize:new window.BMap.Size(36,42),
	anchor:new window.BMap.Size(18,42)
	})
	let bikeMarkerStart =new window.BMap.Marker(startPoint,{icon:startPointIcon})
}
	this.map.addOverlay(bikeMarkerStart);//把点添加到上面
	同理结束点

//绘制车辆行驶路线
let routeList =[];
list.forEach((item)=>{
	let p=item.split(',');
	routeList.push(new window.BMap.Point(p[0],p[1]))	
})
let polyLine =new window.BMap.Polyline(routeList,{//用折线把routelist里面的点连接起来
	strokeColor:'#ef4136',
	strokeWeight:2,
	strokeOpacity:1
})
this.map.addOverlay(polyLine);
}
//绘制服务区
let servicePointList =[];
let serviceList =res.result.service_list;
serviceList.forEach((item)=>{
	servicePointList.push(new window.BMap.Point(item.lon,item.lat))
})
let polyServiceLine =new window.BMap.Polyline(servicePointList,{//用折线把routelist里面的点连接起来
	strokeColor:'#ef4136',
	strokeWeight:3,
	strokeOpacity:1
})
this.map.addOverlay(polyServiceLine);

//添加图中自行车图标
let bikeList =res.result.bike_list;
let bikeIcon =new window.BMap.Icon('....jpg',new window.BMap.Size(36,42),{...同理})
bikeList.forEach((item)=>{
	let p=item.split(',');
	let point = new window.BMap.Point(p[0],p[1]);
	let bikeMarker =new window.BMap.Marker(point,{icon:bikeIcon})
	this.map.addOverlay(bikeMarker);
})

图表
ECharts
可以选择把主题下载下来
(主题的源码由AMD-异步模块定义(AMD是RequireJS的)和commonJS
还可以定制主题
–save 可以使该组件保存到package.jsoon里 install即安装了

node版本过高怎么办
降级即可 npm官网搜索n 安装n这个插件


$ n 版本号 即可安装这个版本

lts:指的是长期稳定支持的版本

echarts是一核心类 做图表肯定要安装的
echarts-for-react 导入之后是组件化的(ES6单页面开发) 不需要像百度地图一样new 一个对象 (多页面的形式) 

不需要导入所有的图标 即import echarts from 'echarts'
按需加载:
import echarts from 'echars/lib/echarts'

主题导入://注意先把下载好的主题文件放入定义好的src/echarts/包里
import echartTheme from './../echartTheme'

导入柱形图://如何看在哪个包里呢  搜npm里echars 或github
import  'echars/lib/chart/bar' 
import 'echars/lib/component/tooltip' //图表上的信息框
import 'echars/lib/component/title'
import 'echars/lib/component/legend'
import 'echars/lib/component/markPoint'

import ReactEcharts from 'echarts-for-react'

componentWillMount(){
//注册主题
   echarts.registerTheme('自定义主题名',echartTheme);//导入的这个主题
}

getOption(){
   let option ={
   	title:{
   			text:'用户骑行订单'
   		},
   		tooltip:{
   			trigger:'axis'
   		},
   	xAxis:{//x轴数据
   			data:['周一','周二',....] //可以直接从服务端提取数据即可
   		},
   	yAxis:{
   		type:'value' //把数据自动计算 列出来即可
   	},
   	series:[//定义数据量 数据源   展示
   		{
   				name:'订单量',
   				type:'bar',
   				data:[1000,2000,1500,...],
   			}
   	]
   }
   return option;
}

getOption2(){
   let option ={
   	title:{
   			text:'用户骑行订单'
   		},
   		legend:{
   			data:['OFO','摩拜‘,’小蓝'] //渲染出来一个关于同一x轴不同分类 点击一方可进行不展示操作
   		},
   		tooltip:{
   			trigger:'axis'
   		},
   	xAxis:{//x轴数据
   			data:['周一','周二',....] //可以直接从服务端提取数据即可
   		},
   	yAxis:{
   		type:'value' //把数据自动计算 列出来即可
   	},
   	series:[//定义数据量 展示
   		{
   				name:'OFO',
   				type:'bar',
   				data:[1000,2000,3500,...],
   			},
   			{
   				name:'摩拜',
   				type:'bar',
   				data:[2000,3000,4500,...],
   			},
   			{
   				name:'小蓝',
   				type:'bar',
   				data:[5000,5200,5500,...],
   			}
   	]
   }
   return option;
}


   <Card title="柱形图1"> 
   	<ReactEcharts optioon={this.getOption() }  theme="自定义主题名” /> //跟上面保持一致 先注册再使用
   </Card>
   <Card title="柱形图2"> 
   	<ReactEcharts optioon={this.getOption2() }  theme="自定义主题名” /> //跟上面保持一致 先注册再使用
   </Card>

饼图
getOption= () => {
   let option ={
   		title:{
   				text:'用户骑行订单',
   				x:'center'
   			},
   		tooltip:{
   			trigger:'item',//指数据项
   			formatter:'{a}<br />{b}:{c}({d}%)'//格式化:系列名 回车 数据名 value 比例%
   		},
   		legend:{
   			right:10,
   			top:20,
   			botton:20,
   			orient:'vertical',//垂直方向居中
   			data:['OFO','摩拜‘,’小蓝'] //渲染出来一个关于同一x轴不同分类 点击一方可进行不展示操作
   		},
   	series:[//定义数据量 展示
   		{
   				name:'订单量',//系列名
   				type:'pie',
   				radius:['50%,30%],//有这一项就是环形图半径 分别控制内外环 没有就是大饼图
   				center:['50%','60%'],//整个图往左往下移动
   				data:[{
   						value:1000,
   						name:'周一'//数据名
   					},{
   							...
   						},{
   								...
   					}],
   			}
   	]
   }
   return option;
}

还有一种根据数据量定义半径大小的饼图(南丁格尔图
首先要对数据进行排序
getOption= () => {
   let option ={
   		title:{
   				text:'用户骑行订单',
   				x:'center'
   			},
   		tooltip:{
   			trigger:'item',//指数据项
   			formatter:'{a}<br />{b}:{c}({d}%)'//格式化:系列名 回车 数据名 value 比例%
   		},
   		legend:{
   			right:10,
   			top:20,
   			botton:20,
   			orient:'vertical',//垂直方向居中
   			data:['OFO','摩拜‘,’小蓝'] //渲染出来一个关于同一x轴不同分类 点击一方可进行不展示操作
   		},
   	series:[//定义数据量 展示
   		{
   				name:'订单量',//系列名
   				type:'pie',
   				data:[{
   						value:1000,
   						name:'周一'//数据名
   					},{
   							...
   						},{
   								...
   					}].sort((a,b)=>{
   							return a.value -b.value;
   					}),
   					roseType:'radius'//显示风格为南丁格尔图
   					//还可以加动画
   					animationType:'scale',//鼠标移上去松开会变大变小
   					animationEasing:'elasticOut',
   					animationDelay:function (idx){
   							return Math.random() *200;
   				}
   			}
   	]
   }
   return option;
}

折线
import  'echars/lib/chart/line' 
getOption= () => {
   let option ={
   		title:{
   				text:'用户骑行订单',
   			},
   		tooltip:{
   			trigger:'axis'//指数据项
   		},
   		
   		xAxis:{
   			    type:'category',//默认值
   			    boundaryGap:false,//控制面积x轴上开始的起点是0 还是第一个数据点
   				data:['周一',....]
   		},
   		yAxis:{
   			type:'value'
   		},
   	series:[//定义数据量 展示
   		{
   				type:'line',
   				data:[
   					1500,
   					2500,
   					1000,
   					...
   				],
   				areaStyle:{}//填充面积
   				}
   	]
   }
   return option;
}

import  'echars/lib/chart/line' 
getOption= () => {
   let option ={
   		title:{
   				text:'用户骑行订单',
   			},
   		tooltip:{
   			trigger:'axis'//指数据项
   		},
   		legend:{
   			data:['ofo订单量','mobike订单量']
   		},
   		xAxis:{
   			   
   				data:['周一',....]
   		},
   		yAxis:{
   			type:'value'
   		},
   	series:[//定义数据量 展示
   		{
   				name:'ofo订单量',//系列名
   				type:'line',
   				data:[
   					1500,
   					2500,
   					1000,
   					...
   				]
   				}
   			},
   			{
   				name:'mobike订单量',//系列名
   				type:'line',
   				data:[
   					1000,
   					2000,
   					1500,
   					...
   				]
   				}
   			}
   	]
   }
   return option;
}

富文本功能 满足文字各种样子的需求
npm官网里有react-draft-wysiwyg插件
还需要一个转化成HTML内容的插件  draftjs-to-html 
都需要终端进行安装
import {Editor } from 'react-draft-wysiwyg';
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
import draftToHtml from 'draftjs-to-html';

state={
showRichText:false //默认不显示
editorState:''
}

onEditorChange=(contentState)=>{
   this.setState({
   	contentState//内容状态
   });
}

onEditorStateChange =( editorState) =>{
   this.setState({
   	editorState,//编辑器状态
   });
}

handleClearContent =()=>{
   this.setState({
   	editorState:' ' ,
   });
}

handleGetText =() =>{
   this.setState({
   	showRichText:true
   });
}

render(){
   const { editorState } =this.state;
   return(
   <div>
   	<Card>
   		<Button type="primary" onClick={this.handleClearContent}>清空内容</Button >
   		<Button  type="primary" onClick={this.handleGetText}>获取html文本</Button>
   	</Card>
   	<Card>
   		<Editor
   	  editorState={editorState} //接收到状态值
   	  onContentStateChange={this.onEditorChange}
   	  onEditorStateChange={this.onEditorStateChange} //状态值变化之后
   	/>
   	</Card>
   </div>
   <Modal
    title="。。。" 
    visible={this.state.showRichText}
    onCancel={ ()=>{
   	 this.setState({
   		showRichText:false
   	})  }
   	footer={null}
    }
    >
   {draftjs(this.state.contentState)}//转化成对应的html文本
   </Modal>
   )
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

七灵微

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

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

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

打赏作者

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

抵扣说明:

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

余额充值