本篇文章是对工作常用的需求以及对应的npm库记录,方便以后遇到同样的需求可以直接用CV大法(对不起我太懒了),由于目前工作用的是React技术栈,用的UI库是Antd,主要针对于中后台开发常见的需求,有些可能在vue的用法上不太一样,以后也会经常记录一下,如果有更好的建议欢迎大佬们评论指出来!
1、根据时间区间获取不同数据
经常遇到根据选择不同的下拉框的值然后刷新不同的数据类似于下图这种,主要还是组件的简单封装和时间处理的函数,当点击确认时会根据时间的选择重新获取数据,两个下拉框联动是主要是在useEffect里根据选择的type依赖把另外两个属性的值设为空,然后在DatePicker的组件的value这个属性根据年或月或季绑定对应的值,这个type就是第一个select的年度、季度、月度
const DatePickerComp = (props) => {
const { type, timeOpen, values, handleChange, handleClick, handOK } = props;
return (
<DatePicker
picker={type}
open={timeOpen}
allowClear={false}
value={values}
onChange={handleChange}
onClick={handleClick}
renderExtraFooter={() => (
<div className={style.timeFooterBtnWrap}>
<div onClick={handOK} className={style.timeFooterBtn}>
确认
</div>
</div>
)}
/>
);
};
const PickerWithType = ({ type, onChange }) => {
const [timeOpen, setTimeOpen] = useState(false);
const [yearTimes, setYearTimes] = useState(moment().year(new Date().getFullYear()));
const [quarterTimes, setQuarterTimes] = useState('');
const [monthTimes, setMonthTimes] = useState('');
const handleClick = () => {
setTimeOpen(!timeOpen);
};
//根据类型,点击ok的回调
const handOK = () => {
setTimeOpen(false);
if (type === 'year') {
onChange(yearTimes);
} else if (type === 'month') {
onChange(monthTimes);
} else {
onChange(quarterTimes);
}
};
//根据类型,选择日期时候的回调
const handleYearChange = (date) => {
setYearTimes(date);
};
const handleQuarterChange = (date) => {
setQuarterTimes(date);
};
const handleMonthChange = (date) => {
setMonthTimes(date);
};
useEffect(() => {
if (type === 'year') {
setQuarterTimes('');
setMonthTimes('');
} else if (type === 'month') {
setYearTimes('');
setQuarterTimes('');
} else {
setMonthTimes('');
setYearTimes('');
}
}, [type]);
return (
<Fragment>
<DatePickerComp
type={type}
timeOpen={timeOpen}
handleClick={handleClick}
handOK={handOK}
handleChange={
type === 'year'
? handleYearChange
: type === 'month'
? handleMonthChange
: handleQuarterChange
}
values={type === 'year' ? yearTimes : type === 'month' ? monthTimes : quarterTimes}
/>
</Fragment>
);
};
//点击ok时,根据不同类型的时间选择,保存的时间就是一个月或三个月或一年
//new Date(year, Number(month) - 1, 1) 表示这个月的1号
//new Date(year, month, 0) 表示下个月的0号就是上个月的最后一天
//然后在获取数据的useEffct里,依赖就是onChange函数保存的time,时间变换,请求接口的参数就会变然后 重新获取对应的数据
const onChange = (dateString) => {
let Times = [];
if (dateString) {
if (timeType === 'year') {
const startTime = `${moment(dateString).format('YYYY')}-01-01 00:00:00`;
const endTime = `${moment(dateString).format('YYYY')}-12-31 23:59:59`;
Times = [startTime, endTime];
} else if (timeType === 'month') {
const year = moment(dateString).format('YYYY');
const month = moment(dateString).format('MM');
const monthStartTime = `${moment(new Date(year, Number(month) - 1, 1)).format(
'YYYY-MM-DD',
)} 00:00:00`;
const monthStartEnd = `${moment(new Date(year, month, 0)).format('YYYY-MM-DD')} 23:59:59`;
Times = [monthStartTime, monthStartEnd];
} else {
const year = moment(dateString).format('YYYY');
const month = moment(dateString).format('MM');
const monthStartTime = `${moment(new Date(year, Number(month) - 1, 1)).format(
'YYYY-MM-DD',
)} 00:00:00`;
const monthStartEnd = `${moment(new Date(year, Number(month) + 2, 0)).format(
'YYYY-MM-DD',
)} 23:59:59`;
Times = [monthStartTime, monthStartEnd];
}
}
setTime(Times);
};
2、上传excel文件
要注意的方面为:
1、要不然是action方法,传到后台接口,要不然就自己写一个手动的 customRequest
2、beforeUpload 方法,判断上传文件的类型
3、onChange方法:上传时对文件的操作,如只允许上传一个,要把处理后的文件set保存
4、fileList方法:上传的文件
export const isFileType = (file, ...fileTypes) => {
const type = (file?.name || '').split('.').slice(-1)?.[0] || '';
return fileTypes.includes(file?.type) || fileTypes.includes(type);
}
//判断是否符合文件类型
beforeUpload={(file) => {
if (
isFileType(file, 'application/vnd.ms-excel', 'xls') ||
isFileType(file, 'application/vnd.ms-excel', 'xlsx')
) {
return true;
}
message.error(`${file.name} 不符合上传类型.`);
return false;
}}
3、去除对象中空的数据
一般用在表单提交,当多个input框时有可能有些不是必传的,可以把拿到的表单数据去空处理完后传给后台
const removeValue = (obj) => {
const newObj = {}
Object.keys[obj].forEach(key => {
if(obj[key]){
newObj[key] = obj[key]
}
})
return newObj
}
4、点击 全屏/退出全屏
// 全屏操作
const handleFullScreen = useCallback(() => {
function fullScreen() {
const el = document.documentElement;
const rfs =
el.requestFullScreen ||
el.webkitRequestFullScreen ||
el.mozRequestFullScreen ||
el.msRequestFullScreen;
// typeof rfs != "undefined" && rfs
if (rfs) {
rfs.call(el);
} else if (typeof window.ActiveXObject !== 'undefined') { //判断是否有ActiveXObject控件
// for IE,这里其实就是模拟了按下键盘的F11,使浏览器全屏
const wscript = new window.ActiveXObject('WScript.Shell');
if (wscript != null) {
wscript.SendKeys('{F11}');
}
}
}
function exitScreen() {
const el = document;
const cfs =
el.cancelFullScreen ||
el.webkitCancelFullScreen ||
el.mozCancelFullScreen ||
el.exitFullScreen;
// typeof cfs != "undefined" && cfs
if (cfs) {
cfs.call(el);
} else if (typeof window.ActiveXObject !== 'undefined') {
// for IE,这里和fullScreen相同,模拟按下F11键退出全屏
const wscript = new window.ActiveXObject('WScript.Shell');
if (wscript != null) {
wscript.SendKeys('{F11}');
}
}
}
if (isFullScreen) {
setIsFullScreen(false);
exitScreen();
} else {
setIsFullScreen(true);
fullScreen();
}
resetTranslate(!isFullScreen);
}, [isFullScreen]);
一定要记得给外容器加样式!!!,不然全屏之后有多余的区域
//外容器加 position: relative
&.fullscreen {
position: fixed !important;
top: 0;
left: 0;
z-index: 10000;
margin: 0 !important;
padding: 10px !important;
width: 100% !important;
height: 100% !important;
background-color: #fff;
}
5、点击打印 PDF
//这里用到的 useReactToPrint hook 是 'react-to-print'库的,记得引入
const handleToPrint = useReactToPrint({
content: () => treeRef.current,
pageStyle: '',
});
const handlePrint = () => {
if (treeRef.current) {
const contianer = document.getElementsByClassName('rd3t-tree-container')?.[0];
const [svg] = contianer?.childNodes || [];
const [gEle] = svg?.childNodes || [];
if (gEle) {
// 将svg中的g元素 缩小至视窗大小范围再进行下载。
// console.log(gEle.getBBox());
const { width: svgW } = gEle.getBBox();
const { clientWidth } = contianer;
let scale = 0.5;
if (svgW < clientWidth) {
scale = 0.5;
} else {
scale = (isFullScreen ? 0.7 : 1) / (svgW / clientWidth);
}
gEle.setAttribute(
'transform',
`translate(${isFullScreen ? window.innerWidth / 2.5 : 460}, 200) scale(${scale})`,
);
}
handleToPrint();
}
6、搜索部分高亮
根据有subStr分出需要高亮的部分,有子节点则递归,我这里用在树结构中
const loop = (treeData, val) => {
treeData.map(item => {
const index = item.indexOf(val)
let { title } = index
if(index >= 0){
const beforeStr = item.subStr(0, val)
const afterStr = item.subStr(index + val.length || 0)
title =
index > -1 ? (
<span>{beforeStr}</span>
<span style={{color: '#E4996'}}>{val}</span>
<span>{afterStr}</span>
): (
<span>{title}</span>
)
}
if (item.children) {
return { ...item, title, key:item.key, children: {...loop(item.children, val)} }
}
return {
...item, title, key: item.key
}
})
}
7、isNaN本意是通过Number方法把参数转换成数字类型,如若转换成功,则返回false,反之返回true,它只是判断参数是否能转成数字,不能用来判断是否严格等于NaN。
8、表格的序号1-10处理
const renderTableIndex = (pageNum, pageSize = 10){
return (text, record, index){
const genNumber = Number.parseInt(index,10) + 1 + ( pageNum - 1 ) * pageSize
const newNumber = Number.isNaN(genNumber) ? Nzh.decodeS(genNumber) : genNumber
return newNumber.toString()
}
}
//TODO: 组件里column里直接传 pagenum 和 pageSize
render: renderTableIndex(pageNum, pageSize),
9、各种流文件转换
一般在下载文件或者验证码图片后端会传一个流的图片地址数据过来,这时候res是一个流,打印出来是一堆乱码
第一步是在请求接口的函数要声明,responseType: 'blob'
export async function downloadFile(params) {
const { path, proxyPort = 'api', ids, reqParams, ...rest } = params;
return request(`/${proxyPort}/${params.path}?${stringify(rest)}`, {
method: 'POST',
data: ids,
...reqParams, //这里的reqParams更好的封装了一下,更规范以及能更扩展
});
}
//下载文件的函数
const downFiles = useCallback(payload, callback = () => {}) => {
dispatch({
type: 'common/downFile',
payload: {
path: `aaa/abc`, //自己接口的路径
proxyPort,
...payload,
reqParams: {
responseType: 'blob',
},
}
})
}
多个下载文件
const downloadFile = () => {
const idArgs = propertyRightsMaterialsList.map((el) => el.id); //拿到要下载多个文件的所有ID
downloadFiles(
{
ids: idArgs,
isExport: false,
},
(res) => {
const link = document.createElement('a');
const blob = new Blob([res], { type: 'application/zip' });
const url = URL.createObjectURL(blob);
link.download = `${projectName}.zip`;
link.href = url;
link.click();
},
);
};
//后端传过来的是 图片地址流 时,验证码需求可能遇到
.then(res => {
let blob = new Blob([res], { type: 'img/jpeg' });
let url = (URL || webkitURL).createObjectURL(blob);
let imgDiv = document.getElementById('secret');
imgDiv.src = url; //这个可以
// imgDiv.setAttribute('src',url)//这个写法也可以
imgDiv.onload = function(e) {
URL.revokeObjectURL(url);
};
});
//下载excel流
.then((res) => {
const link = document.createElement('a');
const blob = new Blob([res], { type: 'application/vnd.ms-excel;utf-8' });
const url = URL.createObjectURL(blob);
link.download = `文件${moment().format('YYYY-MM-DD-HH-mm-ss')}.xls`;
link.href = url;
link.click();
});
目前只是遇到的一部分常用需求总结,以后还会更新的,第一次写这种也不知道有没有用......刚工作两个月有一个感悟,一定要和后端关系处理好点!!!!不然他返回的数据不合需求那就需要你处理,有可能后端处理很简单,但咱们前端很难去处理,而且有的后端他写好接口自己本地调试通他扔你给他就不管了,所以一定要和后端关系弄好。