antd design pro前端填坑笔记
1、react组件调用dispatch页面不渲染
state参数是引用类型,如果直接返回state,dva会认为没有修改state,所以不会刷新
建议
return {...state}
2、github/gitlab 拉取合并请求代码
通过git拉取github/gitlab上的Pull Request(PR)/Merge Request(MR)到本地进行code review
Github:
git fetch origin pull/3188/head:pr3188
3188是PR的id
https://github.com/apache/carbondata/pull/3188
Gitlab:
git fetch remote merge-requests/MERGE_REQUEST_ID/head:BRANCH_NAME
修改别人提交的代码(合并和修改commit)
1、合并commit
git rebase -i COMMIT_ID(修改的上一个ID)
2、修改最后提交的commit
git commit --amend 修改最后提交的commit
3、代码回退
a) 在未add
之前,直接git checkout file_name
即可;
b)在提交之后,通过git reset --hard commit_id
即可
暂存区处理
1、git stash
这个指令会把所有未提交的修改都保存起来。
2、git stash pop
将缓存堆中的第一个stash删除,并将对应修改应用到当前目录下。
3、git stash apply
将指定缓存堆栈中的stash应用到工作目录中,默认是 stash@{0},但并不会删除stash拷贝
4、git stash drop
删除指定的stash
5、git stash list
查看所有的暂存区
6、git stash clear
清除所有的暂存区
7、git stash show
查看stash修改情况,默认为stash@{0},加-p查看特定stash的全部diff,
8、git stash branch + 分支名
如果你储藏了一些工作,暂时不去理会,然后继续在你储藏工作的分支上工作,你在重新应用工作时可能会碰到一些问题。如果尝试应用的变更是针对一个你那之后修改过的文件,你会碰到一个归并冲突并且必须去化解它。如果你想用更方便的方法来重新检验你储藏的变更,你可以运行 git stash branch,这会创建一个新的分支,检出你储藏工作时的所处的提交,重新应用你的工作,如果成功,将会丢弃储藏。
3、组件样式覆盖
对组件添加一层div标签,并将组件样式写在div样式里面,防止样式全局污染,例如,Input` 自带的类名为 input-style,定义新的类名在div上,这会覆盖div内部Input样式
// Input 组件
import styls from './input.less'
<div className={style["input-bx"]}>
<Input/>
</div>
.input-box :global{
.input-style{
background-color: pink;
}
}
4、不同命名空间组件相互调用
对于不同命名空间的组价调用,会报错找不到命名空间,出现这种问题的原因是model service mock 不是全局的,将组件下面的这三个文件分别剪切放在全局的三个文件夹中即可。
5、antd 组件图片模拟上传测试
直接上代码,主要参考antd组件上传图片代码API
index.jsx文件
import React, {useState, useEffect} from 'react';
import request from "@/utils/request";
import {EyeOutlined, DeleteOutlined, PlusOutlined} from '@ant-design/icons';
import {Upload, Button, Modal, message} from 'antd';
import styles from './style.less';
const getBase64 = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
}
const maxNum = 8;
const Avatar = props => {
const [fileList, setFileList] = useState([]);
const [showImg, setShowImg] = useState([]);
const [previewVisible, setPreviewVisible] = useState(false);
const [uploading, setUploading] = useState(false);
const [previewImage, setPreviewImage] = useState("");
const [previewTitle, setPreviewTitle] = useState("");
useEffect(() => {
getImgAll();
}, []);
// 获取图片
const getImgAll = () => {
request(`/api/sys/img/get`, {
method: 'POST',
}).then(res => {
if (res.status === 'ok') {
setShowImg(res.data);
} else {
message.error("获取图片失败!")
}
}).catch(err => {
throw err;
})
}
// 开始上传图片
const handleUpload = async (e) => {
e.stopPropagation();
setUploading(true);
for (let i = 0; i < fileList.length; i++) {
fileList[i] = {
lastModified: fileList[i].lastModified,
name: fileList[i].name,
size: fileList[i].size,
type: fileList[i].type,
uid: fileList[i].uid,
webkitRelativePath: fileList[i].webkitRelativePath,
url: await getBase64(fileList[i].originFileObj)
};
}
request(`/api/sys/img/upload`, {
method: 'POST',
data: fileList,
}).then(res => {
setUploading(false);
setFileList([]);
message.destroy();
message.success({content: '上传成功.', duration: 1, onClose: () => getImgAll()});
}).catch(err => {
setUploading(false);
message.destroy();
message.error('上传失败.');
})
}
//删除已经上传图片
const onRemoveImg = file => {
message.destroy();
request(`/api/sys/img/delete`, {
method: 'POST',
data: file,
}).then(res => {
if (res.status === 'ok') {
getImgAll();
message.info("删除成功!");
} else {
message.error("删除失败!")
}
}).catch(err => {
throw err;
})
}
// 删除未上传图片
const onRemove = file => {
const index = fileList.indexOf(file);
const newFileList = fileList.slice();
newFileList.splice(index, 1);
setFileList(newFileList);
}
const handlePreview = async file => {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj);
}
setPreviewImage(file.url || file.preview);
setPreviewVisible(true);
setPreviewTitle(file.name || file.url.substring(file.url.lastIndexOf('/') + 1));
};
const ImgBox = props => {
const {file} = props;
return (
<div className={styles["gl-box"]}>
<img className={styles["gl-img"]} src={file.url} alt=""/>
<div className={styles["img-box"]}>
<EyeOutlined onClick={() => handlePreview(file)} className={styles["gl-icon"]}/>
<DeleteOutlined onClick={() => onRemoveImg(file)} className={styles["gl-icon"]}/>
</div>
</div>
)
};
return (
<div>
<div key="img-box">
{
showImg.map((i, j) => <ImgBox key={j} file={i}/>)
}
</div>
{
showImg.length >= maxNum ? `文件仅支持上传${maxNum}张图片,若更改请删除后重新上传。` :
(<Upload
onRemove={(file) => onRemove(file)}
beforeUpload={(file) =>false}
fileList={fileList}
listType="picture-card"
onPreview={(file) => handlePreview(file)}
onChange={(info) => {
if (info.fileList.length + showImg.length > maxNum) {
message.destroy();
message.warning(`仅支持上传${maxNum}张图片`)
}
setFileList(info.fileList.splice(0,maxNum-showImg.length));
}}
>
<div><PlusOutlined/></div>
<Button
size={"small"}
type="primary"
onClick={(e) => handleUpload(e)}
disabled={fileList.length === 0}
loading={uploading}
style={{position: "relative", bottom: -20}}
>
{uploading ? '...' : '上传'}
</Button>
</Upload>)}
<Modal
visible={previewVisible}
title={previewTitle}
footer={null}
onCancel={() => setPreviewVisible(false)}
>
<img alt="example" style={{width: '100%'}} src={previewImage}/>
</Modal>
</div>
);
}
export default Avatar;
style.less文件
.gl-box{
display:inline-block;
height: 105px;
width: 105px;
border:1px solid #eee;
margin: 5px;
overflow: hidden;
text-align: center;
}
.gl-icon{
font-size:16px;
color:#eee;
margin: 0 5px;
}
.gl-img{
height: 104px;
width: 104px;
z-index:1;
cursor: pointer;
}
.img-box {
height: 104px;
width: 104px;
position: relative;
line-height: 104px;
bottom: 0;
z-index: 2;
background-color: rgba(0, 0, 0, 0.5);
vertical-align: middle;
}
.gl-box:hover{
.img-box{
transition: all .3s ease;
transform: translateY(-104px);
}
}
全局mock文件
(放在全局mock文件里面即可)
img.js
let imgList = [
{
url:'https://i01piccdn.sogoucdn.com/905894db522971e6',
lastModified: 1591877022945,
name: "direwolf.jpg",
size: 41033,
type: "image/jpeg",
uid: "rc-upload-1592210832893-2",
webkitRelativePath: "",
},
]
const imgData = (req, res) => {
imgList = imgList.concat(req.body);//imgList=[...imgList,...req.body]
res.json({status: 'ok', data: req.body});
}
const getData = (req, res) => {
res.json({status: 'ok', data: imgList});
}
const deleteData = (req, res) => {
imgList.splice(imgList.indexOf(imgList.filter(i=>i.url===req.body.url)[0]), 1);
res.json({status: 'ok', data: imgList});
}
export default {
'POST /api/sys/img/upload': imgData,
'POST /api/sys/img/get': getData,
'POST /api/sys/img/delete': deleteData,
};
6、自定义可编辑单元格组件不渲染问题
对于业务需求需要对单元格内容可编辑,并对组件实时渲染,例如自己封装的Checkbox组件如下,具体功能是根据操作实现对列数据的展示效果。
import React,{useState} from 'react';
import {Checkbox} from "antd";
const CheckboxCom=props=>{
// row,text 为表格的row 行数据和值
// type 是自定义属性,判断是否固定列 是否显示列等 例如是 fixed
const {row,onChangeItem,type,text} = props;
const [ch,setChecked] = useState(undefined);
const onChange = value=>{
setChecked(value);
row[type] = value;
onChangeItem(row);
}
return (<Checkbox onChange={e=>onChange(e.target.checked)} checked={ch||text} />)
}
export default CheckboxCom;
当组件渲染的时候,组件会根据表格数据,动态绑定值。
但是在设置恢复默认设置的时候,并未渲染组件。通过判断ch
是否存在来回填checked
就可以解决问题了,也就是在没有改变值的情况下,checked
充当了 defaultValue
的作用。
7、自定义表格最后一行渲染
在summary
属性中定义antd 表格的汇总行
官网
/**
* Created by lidianzhong on 2020-07-08.
* To: More pain, more gain.
*/
import React, {useState} from 'react';
import {Table, Pagination, Checkbox} from 'antd';
import {SearchOutlined, PlusCircleTwoTone, MinusCircleTwoTone} from '@ant-design/icons';
import Mock from "mockjs";
const dataSource = Array(100)
.fill(0, 0, 100)
.map((item, i) => {
return Mock.mock({
index: i + 1,
key: i + 1,
"name|1": ['胡彦斌', "kankan", "kankan1"],
"money|1-100": 32,
address: Mock.mock("@city()"),
address0: Mock.mock("@city()"),
address1: Mock.mock("@city()"),
address2: Mock.mock("@city()"),
address3: Mock.mock("@city()"),
address4: Mock.mock("@city()"),
address5: Mock.mock("@city()"),
address6: Mock.mock("@city()"),
})
});
const defaultPageSize = 20;
const TableComponents = () => {
const [filteredInfo, setFilteredInfo] = useState({});
const [sortedInfo, setSortedInfo] = useState({});
const [data, setData] = useState([]);
useState(() => {
queryFun(1, defaultPageSize);
}, [])
const onHeaderRow = (column, index) => {
console.log(column, index);
}
const onRow = (column, index) => {
// console.log(column, index);
}
const children1=[];
for (let i = 0; i < 7; i++) {
children1.push({
title: '住址' + i, dataIndex: 'address' + i, key: 'address' + i, width: 200
});
}
let columns = [
{
title: "序号",
dataIndex: 'index',
key: 'index',
width: 100,
fixed: 'left',
},
{
title: <span>姓名<Checkbox/></span>,
dataIndex: 'name',
key: 'name',
width: 100,
align: 'center',
ellipsis: true,
className: 'kankan',
// colSpan:1,
// defaultFilteredValue:'kankan',
// defaultSortOrder:'ascend',
// filterDropdownVisible:true,
// filtered:true,
filterMultiple: true,
// filterDropdown:()=><div>filterDropdown</div>,
onCell: (record, rowIndex) => {
return {
onClick: event => {
console.log("onCell单击行触发")
}, // 点击行
onDoubleClick: event => {
console.log("onCell双击行触发")
},
onContextMenu: event => {
console.log("onCell菜单触发")
},
onMouseEnter: event => {
console.log("onCell鼠标移入触发")
}, // 鼠标移入行
onMouseLeave: event => {
console.log("onCell鼠标移出触发")
},
};
},
// onFilter:(e)=>{},
// onFilterDropdownVisibleChange:()=>{},
onHeaderCell: column => {
return {
onClick: event => {
console.log("onHeaderCell单击行触发")
}, // 点击行
onDoubleClick: event => {
console.log("onHeaderCell双击行触发")
},
onContextMenu: event => {
console.log("onHeaderCell菜单触发")
},
onMouseEnter: event => {
console.log("onHeaderCell鼠标移入触发")
}, // 鼠标移入行
onMouseLeave: event => {
console.log("onHeaderCell鼠标移出触发")
},
};
},
filters: [
{text: 'kankan', value: 'kankan'},
{text: 'kankan1', value: 'kankan1'},
{text: '胡彦斌', value: '胡彦斌'},
],
filteredValue: filteredInfo.name || null,
onFilter: (value, record) => record.name.includes(value),
},
{
title: '身价', dataIndex: 'money', key: 'money', width: 100,
defaultSortOrder: 'ascend',
sortDirections: ["ascend", "descend"],
showSorterTooltip: true,
sorter: (a, b) => a.money - b.money,
sortOrder: sortedInfo.columnKey === 'money' && sortedInfo.order,
},
{
title: '住址', children: [
{title: '住址', dataIndex: 'address1', key: 'address1', width: 200},
...children1,
]
},
];
const columnss = columns.reduce((total,curr,index,arr)=>{
if(curr.children){
return total.concat(curr.children);
}else{
return total.concat(curr);
}
},[]);
console.log(columnss);
/**
* @fun self-define summery.
* @param currentData
* @returns {*}
*/
const onSummary = (currentData) => {
const a = {
index: '总计',
key:"total",
name: '',
money: 0,
address: '',
address0: "",
address1: "",
address2: "",
address3: "",
address4: "",
address5: "",
address6: "",
};
const data = currentData.reduce((total, currentValue, currentIndex, arr) => {
return {
...a,
money: total.money + currentValue.money,
}
}, a)
return <>
<Table.Summary.Row>
<Table.Summary.Cell key={0} index={0}/>
{columnss.map((i, j) =><Table.Summary.Cell key={j+1} index={j+1}>{data[i.dataIndex]}</Table.Summary.Cell>)
}
</Table.Summary.Row>
</>
}
const titleFunction = enable => {
return enable[0].name + " " + enable[0].address;
}
const expandableSelect = {
rowExpandable: record => record.name !== 'kankan1',
childrenColumnName: '展开',
defaultExpandAllRows: false,
// defaultExpandedRowKeys: [1],
expandIcon: ({expanded, onExpand, record}) => {
//https://codesandbox.io/s/fervent-bird-nuzpr?file=/index.js:1450-1677
return expanded ? (
<MinusCircleTwoTone onClick={e => onExpand(record, e)}/>
) : (
<PlusCircleTwoTone onClick={e => onExpand(record, e)}/>
)
},
expandIconColumnIndex: 0,
// expandedRowKeys: [2,6],
expandedRowRender: (record, index, indent, expanded) => {
// console.log(record, index, indent, expanded);
return <div style={{backgroundColor: 'pink'}}>
<p>record:{record.name}</p>
<p>index:{index}</p>
<p>indent:{indent}</p>
<p>expanded:{expanded}</p>
</div>
},
indentSize: 20,
expandRowByClick: true,
onExpand: (expanded, record) => {
console.log(expanded, record);
},
onExpandedRowsChange: (expandedRows) => {
console.log("expandedRows", expandedRows)
}
}
const handleChange = (pagination, filters, sorter) => {
console.log('Various parameters', pagination, filters, sorter);
setFilteredInfo(filters);
setSortedInfo(sorter);
};
function queryFun(page, size) {
setData([...dataSource].splice((page - 1) * size, size));
}
return (<div style={{padding: 50}}>
<Table
// title={(currentPageData) => titleFunction(currentPageData)}
dataSource={data}
columns={columns}
showHeader={true}
bordered={true}
summary={(currentData) => onSummary(currentData)}
scroll={{x: 800, y: 400}}
size={'small'}
onChange={(pagination, filters, sorter) => handleChange(pagination, filters, sorter)}
// onHeaderRow={(column, index)=> onHeaderRow(column, index)}
// onRow={(column, index)=> onRow(column, index)}
onRow={(record, index) => {
return {
onClick: event => {
console.log("单击行触发")
}, // 点击行
onDoubleClick: event => {
console.log("双击行触发")
},
onContextMenu: event => {
console.log("菜单触发")
},
onMouseEnter: event => {
console.log("鼠标移入触发")
}, // 鼠标移入行
onMouseLeave: event => {
console.log("鼠标移出触发")
},
};
}}
onHeaderRow={column => {
return {
onClick: () => {
console.log("点击表头行触发", column);
}, // 点击表头行
};
}}
// sortDirections={["ascend","descend"]}
// showSorterTooltip={true}
expandable={expandableSelect}
pagination={false}
/>
<Pagination
style={{float: 'right'}}
onChange={(page, pageSize) => {
queryFun(page, pageSize);
}}
onShowSizeChange={(current, size) => {
queryFun(current, size);
}}
total={dataSource.length}
showSizeChanger
showQuickJumper
pageSizeOptions={[10, 20, 50, 100, 200]}
defaultCurrent={1}
defaultPageSize={defaultPageSize}
showTotal={total => `总计 ${total} 条`}
/>
</div>)
}
export default TableComponents;
挖坑,填坑持续更新中。。。
同时也希望大家留言评论。。。
干了这碗酒
寄语
关于具体逻辑,在代码注释中已经给出。想学东西,希望读懂每一行代码,而不是简单的复制看效果。
不为别人,只为自己也要变得优秀。
主页
冰原狼主页:https://kankan.fun/
CSDN主页:https://blog.csdn.net/qq_38025939/
Github主页:https://github.com/kankanol1
明天的你一定会感谢现在拼命的自己!