调用后返回上一页&&跳转至当前页
this.props.history.goBack(); 返回
this.props.history.push("/index/setting/basicsetting"); 跳转
this.props.history.go(-1) 跳转
当使用时出现以下错误Cannot read property 'push' of undefined,
因为父组件调用子组件定义的跳转事件时,要传递history,这里history未定义
(比如app.js这个组件,一般是首页,不是通过路由跳转过来的,而是直接从浏览器中输入地址打开的,如果不使用withRouter此组件的this.props为空,没法执行props中的history、location、match等方法)
解决方法:
import React from "react";
import {withRouter} from "react-router-dom"; //第一,引入withRouter
class MyComponent extends React.Component {
...
myFunction() {
this.props.history.push("/some/Path");
}
...
}
export default withRouter(MyComponent); //第二,不要在上面暴露模块,在这里使用withRouter暴露模块
详细可见:react-router v4 使用 history 控制路由跳转 https://github.com/brickspert/blog/issues/3
调用后重新渲染当前页&&跳转至当前页
this.props.history.go(0) 跳转
state={
visible:false
}
handleClick=()=>{
this.setState({
visible:true
});
}
this.setState(this.state) //此语句执行过后页面重新渲染state。这里需要注意的事重新渲染会将visible渲染成state里的false,而不是handleClick里的true
//渲染会把state里的内容全部重新渲染,即执行this.setState(state)之后,visible会被重新渲染为false,若需要visible为true,可以在渲染之后重新定义状态,如下重新定义
this.setState({this.state})
this.setState({
visible:true
});
定义componentWillMount=async()=>{
this.getData(); //重新渲染当前页面
} // 使用:调用this.getData();函数即可
async getData(){
代码段
}
代码解析
let {state}=this; // 即 let state = this.state;
let res = await api.SettingGetInfo(this.props.match.params) //获取列表里当前元素的id值,此方法需要注意有一个必要条件在route页面里的route添加 /:id
//即<Route path="/index/setting/basicsetting/:id" component={BasicSettting} />
rowKey={record=>record.id} //表格table中的每个记录应该有唯一的“key”支持,或者将“rowKey”设置为唯一的主键,否则会报错。这里是给表格设置rowKey
点击按钮复制相关内容(这里以复制链接为例)
npm i --save copy-to-clipboard //首先安装相关包
import copy from 'copy-to-clipboard'; //引入模块
render:(a, code, index)=>{
return <span>
<a href="# " onClick={()=>{this.copyLink(code)}}>复制链接</a>
</span>
}
copyLink=(code)=>{
copy(`https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=${code.info}`)
message.success("链接复制成功",2.5)
}
三目运算符的嵌套使用
正常使用:code.type===1?(adate<bdate?atime:btime):<span className="text-success">永不</span>
react里:{title:"时间",dataIndex:"Time",render(a,code,index){
let adate=...;
let bdate=...;
var overtime=<span className="text-danger">时间不足</span>;
var forevertime=<span className="text-success">永久</span>
var times=`${date.getFullYear()}-${date.getMonth() + 1<10?"0"+(date.getMonth()+1):date.getMonth()+1}-${date.getDate()<10?"0"+date.getDate():date.getDate()} ${date.getHours()<10?"0"+date.getHours():date.getHours()}:${date.getMinutes()<10?"0"+date.getMinutes():date.getMinutes()}:${date.getSeconds()<10?"0"+date.getSeconds():date.getSeconds()}` //将时间戳转换为日期格式
return <span>{code.type===1?(adate<bdate?overtime:times):forevertime}</span> //嵌套使用
}}
======================================================================================================================================================
接口部分
读懂接口文档
/api/wechat/code/:officialId/:qrcodeId 带冒号,动态数据:`${base}/api/wechat/code/${localStorage.getItem("listid")}/${params.id}` //这列的:qrcodeId需要使用${params.id}动态写入,params即数组
/api/wechat/statistics/:officialId/code 不带冒号,静态的:`${base}/api/wechat/statistics/${localStorage.getItem("listid")}/code` //这里的qrcode直接写上去就可以
参数问题
request<T = any>(config: AxiosRequestConfig): AxiosPromise<T>; //request 一个参数 config
get<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>; //get 两个参数 url config
delete(url: string, config?: AxiosRequestConfig): AxiosPromise; //delete 两个参数 url config
head(url: string, config?: AxiosRequestConfig): AxiosPromise; //head 两个参数 url config
post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise<T>; //post 三个参数 url data config
put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise<T>; // put 三个参数 url data config
patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise<T>; // patch 三个参数 url data config
参数解释 :url —— 请求地址
data——发送给服务器的请求数据
config——配置,一般都是固定格式
举个栗子:
export default{
async codeDesign(params){
return await axios.put(`${base}/api/code/${localStorage.getItem("listid")}/${params.id}`, params,{ //url data
headers:{ //config 一般都是固定格式
"token":cookie.load("usertoken")
}
}).then((res)=>{
return res.data;
});
},
async codeExtend(params){
return axios.post(`${base}/api/code/${localStorage.getItem('listid')}/${params.id}/extend`,{},{ //这里data就没有传参,而是用{}表示
headers:{
"token":cookie.load("usertoken")
}
}).then((res)=>{
return res.data;
});
},
async codeGetMsg(params){
return await axios.get(`${base}/api/code/${localStorage.getItem("listid")}/${params.id}`,{
params, **//如果是get delete两个参数的这种,是没有data参数的,那么params不能放在data里,就可以放在config里**
headers:{
"token":cookie.load("usertoken")
}
}).then((res)=>{
return res.data;
});
},
}
注意:需要的参数,一个都不能少,data如果不需要传,设置为{}都行,也不能不传
axios实战举例
state={
//上传接口的参数
title:"",
author:"",
digest:"",
content:"",
piclist:[], //从服务器获取的图片数组
picTotal:2,
filter:{
isloading:false,
type:"image",
flag:2,
page:1,
pageSize:18,
},
}
//上传pictextData数据到服务器
addPictext=async()=>{
let {title,author,digest,content}=this.state.pictextData;
if(!title){
this.setState({
pictextTitleRemind:true
})
return;
}else{
this.setState({
pictextTitleRemind:false
})
}
if(!(/^(((https?)://|w+(.w+)+)(:w+)?).*/.test(contentSourceUrl))){ //匹配正则
this.setState({
urlRemind:true
})
return;
}else{
this.setState({
urlRemind:false
})
}
let data=await api.pictextAdd({
title,
author,
digest,
content,
})
if(data.code!==0){
console.log(api.stateCode[data.code]);
return;
}
if(data.code===0){
message.success("保存成功!",3);
this.props.history.push("/index/setting/pictextIndex");
}
}
componentWillMount=async()=>{ //render加载之前就获取到图片信息
this.getData();
}
//获取图片的接口
async getData(){
this.setState({
filter: Object.assign(this.state.filter, {
isloading: true
}),
});
let res=await api.picGetList(this.state.filter)
this.setState({
filter: Object.assign(this.state.filter, {
isloading: false
}),
if(res.code===0){
this.state.picTotal=res.data.total;
this.state.piclist=res.data.data;
}else{
console.log(api.stateCode[res.code]);
}
this.setState(this.state); //重新渲染页面
}
//分页
onChangePage = (page = 1, pageSize = 18) => {
this.setState(
Object.assign(this.state.filter, {
page,pageSize
})
);
this.getData(); //重新加载getData函数,为了重新渲染页面
}
//删除图片接口
deletePic=async(item)=>{
this.setState({
imgDelete:false,
picImgDelete:false,
})
let data=await api.picDelete({
id:item.id, //获取需要删除的图片的id
})
if(data.code!==0){
console.log(api.stateCode[data.code]);
return;
}else if(data.code===0){
this.onChangePage();
message.success(`删除成功!`,2.5);
}
this.getData();
}
<Pagination hideOnSinglePage onChange={this.onChangePage} current={this.state.filter.page} pageSize={this.state.filter.pageSize} total={this.state.picTotal} /> //antd分页页标
受控组件和不受控组件
在HTML中,<textarea> 的值是通过子属性设置的。在React中,需要通过value设置。我们可以通过添加事件属性onChange监听内容的变化,onChange会在下列情况下被触发:
input或者textarea的内容改变
input的checked状态改变
select的状态改变
【受控组件】
设定了value的input就是一个受控组件。input里会一直展现这个值,用户的任何输入都是无效的。如果你想随着用户的输入改变,使用onChange事件,或者将value改为defaultValue
【非受控组件】
value没有值或者值设为null的input是一个不受控组件。用户的任何输入都会反映到输入框中
这个时候也可以监听onChange事件,内容的改变也会触发事件。
可以通过defaultValue给input设置默认值
指定编辑(如删除,更新等)
有一种需求是点击table中当前tr的“编辑”按钮后,跳转到新的编辑页面编辑当前tr的信息。
这种情况是需要动态获取id的
方法如下:
1、在route.js即路由页面将编辑页面的路由给一个/:id,如下
<Route path="/index/setting/code/code-edit/:id" component={codeEdit} /> //注意是/:id /别忘了
2、列表页面编辑按钮的Link跳转给一个动态id(该id由后端接口数据里给出),如下
<Link to={`/index/setting/code/code-edit/${codelist.id}?id=${codelist.id}`} className="mr-10px">编辑</Link>
3、在编辑页面对接接口时,参数应使用this.props.match.params
async getPictextData(){
let res =await api.pictextGetEdit(this.props.match.params) //这里传入的是this.props.match.params
if(res.code===0){
state.pictextEditData=res.data;
}else{
console.log(api.stateCode[res.code]);
}
this.setState(state)
}
提个醒:this.props.match.params是从另一个页面通过路由带到当前页面的数据。你可以在任何地方使用它。
这也表明了,这个传递的数据,不一定得是id,也可以是其他的数据。比如name,size等,通过/:name就可以传递
就比如说console.log(this.props.match.params)打印出来的是{"id":"1"}。你就可以通过this.props.match.params.id来获取到1。
但有一点,这个1可能是字符串类型的。和number类型的不是一个数据类型。所以在做判断是否相等的时候要注意,用==而不用===
在控制台打印出的数字,黑色的就是string类型。蓝色的是number类型
使用搜索框搜索数据(后端接口没有设置好,全部由前端操作)
state={
filter: {
name:"",
total: 0,
page:1,
pageSize:20,
}
}
handleProductSearch=(e)=>{
this.setState(
Object.assign(this.state.filter,{
name:e.target.value, //在对接接口之后。若想根据名称name搜索,将搜索框的event赋值给filter里的name便可
})
);
this.setState(state); //重新渲染页面
}
<Search
placeholder="请输入产品名称"
onSearch={this.handleProductSearch}
/>
使用搜索框搜索数据,(后端接口已经设置好的情况下)
state={
filter: {
name:"",
total: 0,
page:1,
pageSize:20,
}
}
productName=(e)=>{
this.setState(
Object.assign(this.state.filter,{
name:e.target.value, //在对接接口之后。若想根据名称name搜索,将搜索框的值赋值给filter里的name便可
})
);
}
handleProductSearch=(e)=>{
this.productInfoGet(); //调用获取产品信息的接口。此时的name已经是搜索的name。给接口接口会返回搜索后的结果
}
<Search
placeholder="请输入产品名称"
onChange={this.productName}
onSearch={this.handleProductSearch}
/>
======================================================================================================================================================
localStorage解释
localStorage.setItem("key","value");//以“key”为名称存储一个值“value”
localStorage.getItem("key");//获取名称为“key”的值
localStorage.removeItem("key");//删除名称为“key”的信息。
localStorage.clear();//清空localStorage中所有信息
state里的filter里得数据怎么setState?(使用Object.assign相关知识)
this.state={
visible:false,
filter: {
isloading: false,
name:"",
page:1,
pageSize:20,
}
}
这里的isloading怎么设置为true呢?
handleClick=()=>{
this.setState({
filter:Object.assign(this.state.filter,{
isloading:true
}),
});
}
动态添加css样式(如通过点击添加css样式)
state={
select: "",
}
handleSelect=(item)=>{
this.setState({
select: item.id
})
}
<div onClick={()=>{this.handleSelect(item)}} style={{border:this.state.select===item.id?"1px solid #28a745":null}}></div>
动态添加一组可编辑框(如点击添加按钮后会添加一个可输入数值的图文框)
思路:
1、state一个数组如Data,将数组的值与图文框绑定起来,通过map函数渲染到页面
2、当点击添加按钮后,执行函数,往list数组里push一组数据,数据会自动渲染到页面,即实现了动态添加图文框的功能
3、通过编辑数组的值即可动态编辑图文框里的内容
4、动态删除可以通过用数组的splice操作数组的下标index来实现删除。
总结:通过操作数组的数据来实现图文框的增删改,而不是通过dom操作
举个例子:
state={
title:"", //上传接口的参数
author:"",
digest:"",
content:"",
Data:[{id:"1",title:"",author:"",digest:"",content:""}],
boxid:"1", //定义图文框的id
msg:[],
changeBorder:"",
}
//添加图文框数组
addBox=()=>{
this.setState({
boxid:this.state.boxid++,
})
this.state.Data.push({id:`${this.state.boxid}`,title:"",author:"",digest:"",content:""})
this.setState(this.state)
}
//删除图文框
listbox_Delete=(item)=>{
let index= this.state.Data.indexOf(item) //获取需要删除的图文框的index
this.state.Data.splice(index,1) //使用splice方法删除数组数据
}
//向上移动图文框(通过改变替换index来实现)
listbox_up=(item)=>{
let index=this.state.Data.indexOf(item)
let arr=this.state.Data
let index2=index-1
if(index2!==0){
arr[index]=arr.splice(index2,1,arr[index])[0]
}
}
//向下移动图文框
listbox_down=(item)=>{
let index=this.state.Data.indexOf(item)
let arr=this.state.Data
let index2=index+1
if(index2!==arr.length){
arr[index]=arr.splice(index2,1,arr[index])[0]
}
}
//图文框数组map方式渲染到页面
{
this.state.Data.map((item,index)=>{
if(item.id==1){ //item.id==1时是主图文框的样式,否则是副图文框的样式
return <div onClick={()=>{this.handleSelect(item)}} key={index}> //map语法必须要带有key={index},不然会报错
<div >
<img src={item.picUrl} alt="" style={{"100%",height:"100%"}}/>
</div>
{item.title?<div className="indexbox_mask">{item.title}</div>:null}
</div>
}else{
return <div key={index} style={{border:this.state.changeBorder===item.id?"2px solid #28a745":null}} onClick={()=>{this.handleSelect(item)}}> //handleSelect,点击后选取当前框并与输入框绑定
<h4 style={{height:21}}>{item.title}</h4>
<div className="listbox_content">
<div className="listbox_img">
<img src={item.picUrl} alt=""/>
</div>
</div>
<div className="listbox_mask">
<Icon type="up"className="listbox_icon mr-10px" onClick={()=>{this.listbox_up(item)}} /> //图文框位置上移
<Icon type="down" className="listbox_icon" onClick={()=>{this.listbox_down(item)}} /> //图文框位置下移
<div className="float-r">
<Icon type="delete"className="listbox_icon" onClick={()=>{this.listbox_Delete(item)}}/> //删除图文框
</div>
</div>
</div>
}
})
}
<Button onClick={this.addBox}></Button> //添加图文框
//点击图文框后选定当前图文框,并与Input输入框绑定,之后在Input里输入的值都会存在当前数组里。再点击其他图文框后执行同样的操作,最后所有图文框里采集的数据会全部存在Data数组里,通过接口将数组传入后台,再在另一个页面获取并渲染该组数据,即完成了前后端的一个交接流程。
handleSelect=(item)=>{
console.log(item)
this.setState({
msg:item,
changeBorder:item.id,
title:item.title,
author:item.author,
digest:item.digest,
content:item.content,
})
}
handleGetTitle=(e)=>{ //与Input标签之间进行双向绑定
this.setState({
title:e.target.value,
})
this.state.msg.title=e.target.value; //使得输入框与当前选定的文本框绑定
}
<Input onChange={this.handleGetTitle} id={this.state.msg.id} value={this.state.title} placeholder="请输入标题"/>
antd图片上传
//判断用户上传文件的格式,不符合提醒用户,符合则返回true
function beforeUpload(file) {
if(file.type!=='image/png'&&file.type!=='image/jpeg'&&file.type!=='image/jpg'&&file.type!=='image/gif'){
message.error("上传失败!仅支持jpeg,png,jpg,gif图片格式");
return false;
}else if(file.size/1024/1024>4){
message.error("上传失败!仅支持大小在4M以内的图片上传");
return false;
}
return true;
}
//上传图片接口
UploadImg=()=>{
let self= this;
return {
name: 'media',
action: ` `, //这里写服务器地址
headers: { //设置请求头
token: cookie.load('usertoken'),
},
onChange(info) {
self.setState({
uploadWaiting:true
})
if (info.file.status !== 'uploading') {
console.log(info.file, info.fileList);
self.setState({
uploadWaiting:false,
})
}
if (info.file.status === 'done') {
message.success(`${info.file.name}上传成功!`);
self.setState({
uploadWaiting:false,
})
self.getData()
} else if (info.file.status === 'error') {
message.error(`${info.file.name}上传失败!`);
self.setState({
uploadWaiting:false,
})
}
},
}
}
<Upload {...this.UploadImg()} beforeUpload={beforeUpload}><Button>上传图片</Button></Upload>
列表数据相关操作(table)
constructor(props){
super(props);
this.columns=[
{title:"",
dataIndex:"ticket",
66,
render:(record)=>(<img src={record} width="50px" alt="" />)
},
{title:"产品名称",dataIndex:"name"},
{title:"产品类型",dataIndex:"type", render(a, productlist, index){
return <span>{productist.type || "无"}</span>
}},
{title:"对应编码",dataIndex:"keyword"},
{title:"生产时间",dataIndex:"createtime"},
{title:"到期时间",dataIndex:"expiretime",render(a,productlist,index){
let date=new Date(new Date(productlist.expireTime).getTime());
let nowdate=new Date(new Date().getTime());
var overtime=<span className="text-danger">已过期</span>;
var forevertime=<span className="text-success">永不</span>
var times=`${date.getFullYear()}-${date.getMonth() + 1<10?"0"+(date.getMonth()+1):date.getMonth()+1}-${date.getDate()<10?"0"+date.getDate():date.getDate()} ${date.getHours()<10?"0"+date.getHours():date.getHours()}:${date.getMinutes()<10?"0"+date.getMinutes():date.getMinutes()}:${date.getSeconds()<10?"0"+date.getSeconds():date.getSeconds()}`
return <span>{productlist.mode===1?(date<nowdate?overtime:times):forevertime}</span>
}},
{title:"操作",
dataIndex:"operation",
key:"operation",
225,
render:(a, qrcodelist, index)=>{
return <span>
<a href="# " className="mr-10px" onClick={()=>{this.copyLink(qrcodelist)}}>复制链接</a>
<a href={`${productlist.ticket}`} target={"_blank"} download={`${productlist.name}`} className="mr-10px">下载产品</a>
<Link to={`/index/setting/product/product-edit/${productlist.id}?id=${productlist.id}`} className="mr-10px">编辑</Link>
{productlist.mode===2 ? <a href="# " onClick={()=>{this.showModal2(productlist)}}>删除</a> : <a href="# " onClick={() => {this.extendProduct(productlist)}}>延时上架</a>}
</span>
}
},
];
this.state={
visible:false,
visible2:false,
productlist:[],
filter: {
isloading: false,
name:"",
total: 0,
page:1,
pageSize:20,
}
}
}
<Table
columns={this.columns}
dataSource={this.state.productlist}
rowKey={record=>record.id} //表中的每个记录应该有唯一的“key”支持,或者将“rowKey”设置为唯一的主键,否则会报错。这里是给表格设置rowKey
pagination={{onChange:this.onChangePage, current: this.state.filter.page, pageSize: this.state.filter.pageSize , total: this.state.filter.total }} />
注意:这里涉及到两种render写法,两种写法的this指向不同,其他效果差不多
1、render:(a,b)=>{
let time=a,
let overtime=b,
return <div>......</div>
}
2、render(a,b){
let time=a,
let overtime=b,
return <div>......</div>
}
滚动监听及回到顶部
state={
backTop:false,
backTopShow:false,
}
componentWillMount=()=>{
window.addEventListener("scroll",this.handleScroll,true) //滚动监听
}
handleScroll=()=>{
if(document.documentElement.scrollTop>1000){
this.setState({
backTopShow:true, //当滚动到距离顶部1000px的距离时,“回到顶部”按钮显示
})
}else{
this.setState({
backTopShow:false,
})
}
if(document.documentElement.scrollTop===0){ //当滚动到 0时,清除定时器
clearInterval(this.scrollTimes)
}
}
backToTop=()=>{
if(document.documentElement.scrollTop>0){
this.scrollTimes=setInterval(()=>{
document.documentElement.scrollTop=document.documentElement.scrollTop-60;
},5)
}
}
{this.state.backTopShow?<Button onClick={this.backToTop}>回到顶部</Button>:null} //“回到顶部”按钮
关于接口调用函数的写法
间接调用,被动触发式用这种写法,这种写法用onClick等主动触发可能会引起Cannot read property 'setState' of undefined错误
async userInfoGet(){
let res=await api.getUserInfo()
if(res.code===0){
this.setState({
userNickname:res.data.nickname
})
}else{
console.log(api.stateCode[res.code]);
}
}
componentWillMount(){
this.userInfoGet()
} //在别的函数里间接触发
直接调用,主动触发式如onClick等,用这种写法
userInfoGet=async()=>{
let res=await api.getUserInfo()
if(res.code===0){
this.setState({
userNickname:res.data.nickname
})
}else{
console.log(api.stateCode[res.code]);
}
}
<Button onClick={this.userInfoGet}></Button> //主动调用
将React.Component外部的代码移入到其内部
外部:
const props = {
name: 'file',
action: 'https://www.mocky.io/v2/5cc8019d300000980a055e76',
headers: {
authorization: 'authorization-text',
},
onChange(info) {
if (info.file.status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (info.file.status === 'done') {
message.success(`${info.file.name} file uploaded successfully`);
} else if (info.file.status === 'error') {
message.error(`${info.file.name} file upload failed.`);
}
},
};
export default class Demo extends React.Component{}
内部:
export default class Demo extends React.Component{
state={}
pluginZipUpload=()=>{
let self=this; //这里改动 let self=this
return{ //这里改动return
name: 'plugin',
action: "https://www.mocky.io/v2/5cc8019d300000980a055e76",
headers:{
authorization: 'authorization-text',
},
onChange(info) {
if (info.file.status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (info.file.status === 'done') {
self.setState({
pluginAddFile:Object.assign(self.state.pluginAddFile,{ //这里this换成self
plugin: info.file.originFileObj
})
})
console.log(self.state.pluginAddFile.plugin)
} else if (info.file.status === 'error') {
message.error(`${info.file.name}上传失败`);
}
},
}
};
}
如何知道函数里需要传几个参数或者判断函数里有几个参数
es5里可以用arguments打印
es6里可以用拓展运算符...a方法
在函数里写入...params
handleUpload=(...a)=>{}
console.log(...a)即可打印出来里面需要的参数
可搭配debugger使用