业务通用函数总结-持续中

  1. 移动端安全高度问题
	@constant: constant(safe-area-inset-top);
	@env: env(safe-area-inset-top);
	@supports ((height: constant(safe-area-inset-top)) or (height: env(safe-area-inset-top))) and (-webkit-overflow-scrolling: touch) {
	  .container {
	    :global(.navbar) {
	      padding-top: calc(~"@{constant} * 2");
	      padding-top: calc(~"@{env} * 2");
	      height: calc(~"@{constant} * 2 + 0.9rem");
	      height: calc(~"@{env} * 2 + 0.9rem");
	    }
	  }
}
  1. ios键盘弹起,出现底部空白问题
	/*js监听ios手机键盘弹起和收起的事件*/
	document.body.addEventListener('focusin',()=>{//软键盘弹起事件
			console.log("键盘弹起");
	});
	document.body.addEventListener('focusout',()=>{//软键盘关闭事件
			document.body.scrollTop=0;
			document.documentElement.scrollTop=0;
    });
  1. android键盘弹起可视区域高度变化做兼容
	letoriginalHeight=document.documentElement.clientHeight||document.body.clientHeight;
	window.onresize=function(){
		//键盘弹起与隐藏都会引起窗口的高度发生变化
		letresizeHeight=document.documentElement.clientHeight||document.body.clientHeight;
		this.isFixedBottom=resizeHeight>=originalHeight;
	}
  1. 图片分享
import html2canvas from "html2canvas"; // html2Canvas插件

function shareComponents(ref, bgLoaded, qrLoaded, shareFun){
    // 图片加载完成截图
    if (bgLoaded && qrLoaded) {
        html2canvas(ref, {
            scale: 2,
            useCORS: true,
            allowTaint: true,
            width: ref.share.clientWidth,
            height: ref.share.clientHeight,
            dpi: window.devicePixelRatio * 4
        }).then(canvas => {
            setTimeout(() => {
                const context = canvas.getContext('2d');
                context.mozImageSmoothingEnabled = false;
                context.webkitImageSmoothingEnabled = false;
                context.msImageSmoothingEnabled = false;
                context.imageSmoothingEnabled = false;
                imgUrl = canvas.toDataURL();
                // 分享options
                const options = {
                    shareData: {
                        image: imgUrl
                    },
                    typeList: typeList,
                    callback: (res) => {
                        // 分享之后的callback
                    }
                }
                // 调起分享
                shareFun(options)
            }, 500)
        }).catch(err => {
            alert('分享图片生成失败,请稍后重试')
        });

    } else {
        setTimeout(() => {
            // 图片未加载完成,延迟执行截图
        }, 500)
    }
}
  1. 滚动一定高度
	const $ = window.$;
	componentDidUpdate() {
		// 业务逻辑:一个滚动的回复列表,需要定位到当前回复所需位置,然后滚动
        const { selectedId, selectedType, replyList } = this.props.relaxProps;
        const scrollItem = replyList.find(item => item.get('replyId') == selectedId);
        if (selectedType !== 0 && scrollItem) {
            const dom = `#replay-item-${selectedId}`;
            const parentDom = '#comment-replay';
            setTimeout(() => {
                const top = $(dom).offset().top;
                if (top > 500) {
                	// 400取值因为当前卡片头部是400px;
                    $(parentDom).animate({ scrollTop: top - 400 }, 500);
                }
            });
        }
    }
  1. 解决ios滚动条卡顿问题:
body,html{   
  -webkit-overflow-scrolling: touch;
}
  1. 解决ios光标移动位置不准确问题
  text-indent: -999px;  // 直接隐藏光标
  1. 解决ios12.x(复现机型iphoneX)某些页面不能滚动问题
  min-height: 100vh; // 给子元素一个min-height,帮助safari建立ScrollView。
  1. less中使用动画
// demo场景:让一张图片旋转
.batchImg {
	// 定义动画样式
    .animation(myDongHua, 1s, linear, infinite);
  }

  // 动画定义
  .donghua(@DHname) {
    @keyframes @DHname {
      0% {
        -webkit-transform: rotate(0deg);
        transform: rotate(0deg);
      }
      100% {
        -webkit-transform: rotate(360deg);
        transform: rotate(360deg);
      }
    }
  };

  // 调用动画
  .donghua(myDongHua);

  // 声明动画
  .animation(@animation-name,@animation-duration,@animation-timing-function,@animation-iteration-count) {
    animation: @arguments;
  }
  1. antd组件批量上传的时候,需要对上传个数做拦截,本身的macCount不满足业务需求;
// 使用截流的方式,只让其出发一次,并返回false
const tips = throttle(function () {
        message.error('一次最多上传20份简历')
    }, 300)
    
const upLoadProps: UploadProps = {
        name: 'file',
        showUploadList: false,
        multiple: true,
        withCredentials: true,
        action: `XXXXXXXX`,
        beforeUpload: (file, fileList) => {
            if (fileList.length > 20) {
                tips();
                return false;
            }
            ...
        },
       ...
}
  1. 在react中使用防抖方式
import _ from 'lodash'
const debounceTime = _.debounce(function (params, selectedList) {
   // 需防抖的操作函数
}, 500,{leading: true})


function onConfirm(selectedList) {
   ...
   // 触发时机调用
   debounceTime(params, selectedList);
}
  1. 倒计时功能

(1)react版本:

    const [count, setCount] = useState(59)
    const [checkLoading, setCheckLoading] = useState<boolean>(false);
	const countDown = () => {
	        const active = setInterval(() => {
	            setCount((preSecond) => {
	                if (preSecond <= 1) {
	                    setCheckLoading(false)
	                    clearInterval(active)
	                    // 重置秒数
	                    return 59
	                }
	                return preSecond - 1
	            })
	        }, 1000)
	    }

(2)js版本:

// 触发计时事件
$("#getCheckCode").click(function () {
     Settime();
});

// 倒计时函数及dom文字处理
function Settime(obt) {
    var countdown = 60;
    settime('#getCheckCode');

    function settime(obt) {
        if (countdown === 0) {
            $(obt).attr("disabled", false);
            $(obt).text("获取验证码");
            countdown = 60;
            return;
        }
        if (countdown === 60) {
            var phone = $('#checkPhone').val();
            if (!phone) {
                alert('请输入手机号')
                return;
            }
            sendCode();
        }
        $(obt).attr("disabled", true);
        $(obt).html("(" + countdown + ")s后重新获取");
        countdown--;
        setTimeout(function () {
            settime(obt)
        }, 1000)
    }
};

/*发送验证码*/
function sendCode() {
    var phone = $('#checkPhone').val();
    $.ajax({
        url: 'XXXXXX',
        type: "get",
        contentType: 'application/json',
        data: {
            phone: phone,
        },
        success: function (result) {
            if (result.code === 200) {
                singleAccept("验证码发送成功,请注意查收");
            } else {
                errorTip("验证码发送失败,请重新发送");
            }
        }
    });
}
  1. 上传文件类型判断
       beforeUpload: file => {
            const splitList = file.name.split('.')
            const type = splitList[splitList.length - 1].toLocaleLowerCase()
            const typeList = ['doc', 'docx', 'pdf', 'jpg', 'png', 'html']
            const isTrueFileType = typeList.some(item => item === type)
            if (!isTrueFileType) {
                message.error(`${file.name}文件类型错误`);
            }
            return isTrueFileType;
        },
  1. 批量文件上传进度的计算
		let counts = 0;
    	const [count, setCount] = useState(0); // 当前已上传数量,注意在处理完成之后需要清空
        const [totalCount, setTotalCount] = useState(0); //需上传文件总数
        // beforeUpload中获取上传文件总数
		beforeUpload: (file, fileList) => {
            setTotalCount(fileList.length)
            ...
        },
        //onChange中做出数量计算
        onChange: (info) => {
            const {status, name, response} = info.file;
            if (status === 'done') {
                counts += 1;
                const diffCount = totalCount - counts;
                if (diffCount > 0) {
                    setCount(counts)
                } else {
                	//全部上传完成之后的操作
                	...
                }
            } else if (status === 'error') {
                message.error(`${name} 上传失败,请重新上传.`);
            }
        },
  1. 循环调用接口,中间增加条件判断,适时停止
    const [flag, setFlag] = useState(false)
    useEffect(() => {
        const timer = setInterval(() => {
            if (flag) {
                const params = {
                    pageSize: state.pageSize,
                    pageNumber: 1
                }
                FetchData(params).then((res: any) => {
                    if (!res) {
                        setFlag(false)
                        return message.error('获取数据失败')
                    }
                    // 无关业务代码
                    // ...
                    const newRes = res?.list?.length ? res?.list : []
                    // 判断是否继续进行调用,最好和服务端商量确定一个标志参数
                    const hasContinues = newRes.some(item => item.parseStatus === 'ing')
                    if(!hasContinues){
                    	//置空某些状态参数
                        setCount(0)
                        counts=0;
                    }
                    // 设置是否继续调用
                    setFlag(hasContinues)
                })
            }
        }, 2000);
        return () => clearInterval(timer);
    }, [flag])

  1. iframe动态设置src后,浏览器记录历史记录,在返回的时候造成路由错位。使用key做区分。
  <iframe src={`${fileUrl}#toolbar=0`} width={'100%'} height={'100%'} key={fileUrl}/>
  1. 宽度固定,超出宽度…展示,并toopTips提示;
import React, { CSSProperties, PropsWithChildren, useState} from 'react'
import styles from './index.module.less'
import { Tooltip } from 'antd';
interface IProps{
    className?: string
    style?: CSSProperties
    text?:any
}

export default function Ellipsis(props: PropsWithChildren<IProps>) {
    const {text} = props;
    const [tipVisible, setTipVisible] = useState(false)
    const textOver = (e)=>{
        if(e.target.scrollWidth>e.target.clientWidth){
            setTipVisible(true)
        }
    }
    const textLeave = ()=>{
        setTipVisible(false)
    }
    return <>
      <Tooltip visible={tipVisible} placement="top" title={<span dangerouslySetInnerHTML={{__html:text}}></span>}>
        <div className={styles.ellipsisWrap} onMouseOver={textOver} onMouseLeave={textLeave} dangerouslySetInnerHTML={{__html:text}}>
        </div>
      </Tooltip>
    </>
}

.ellipsisWrap{
  width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
  1. 页面字体根据窗口大小进行缩放
    使用zoom进行缩放
const setPageZoom = ()=>{
        if (window.innerWidth < 1700) {
            const zoomData = window.innerWidth/1700 > 0.7 ?window.innerWidth/1700:0.7
            document.querySelector('body').style.cssText = `zoom:${zoomData}`
        } else {
            document.querySelector('body').style.cssText = 'zoom:1'
        }
}

useEffect(()=>{
        setPageZoom();
        window.addEventListener('resize',setPageZoom);
        return ()=>{
            window.removeEventListener('resize',setPageZoom)
        }
    },[])
  1. antd-popover父级节点的位置偏差
    使用id,挂在class
<div id={`popoverContainerWrap`}>
	<Popover
		getPopupContainer={()=>document.getElementById(`popoverContainerWrap`)}
		...props
	>
</div>
  1. 时间组件添加至今,并设置可选择时间

思路:

  1. 在antd-DatePicker组件基础上,使用renderExtraFooter提供的属性进行扩展。如下面第一段代码;
  2. 如果是时间段的选择:需要前后制约某个时间段是否可选;如下第二段代码:(在第一段代码上进行一层封装)
import React, {PropsWithChildren, useState, useEffect} from 'react'
import styles from './index.module.less'
import type {Moment} from 'moment';
import type {DatePickerProps} from 'antd/es/date-picker';
import moment from 'moment';
import {DatePicker} from 'antd';
import {useControllableValue} from "ahooks";
import {RangePickerProps} from "antd/es/date-picker";

type PropsType = DatePickerProps & {
    showToNow?: boolean,// 是否显示至今
    toNowText?: string,// 显示至今的文案
    toNowKey?: string,// 显示至今的key
    value?: any,// 显示至今的key
    onChange?: Function, // 回调函数
    controlDate?: unknown, // 禁止时间
    typeTime?: number, // 类型 1为开始时间,2为结束时间,0为单个
}
// 适用年月日的业务组件 如有其他场景再添加
export default function DatePicker(props: PropsWithChildren<PropsType>) {
    const {showToNow, toNowKey = 'toNow', toNowText = '至今', controlDate, picker, onChange, typeTime} = props;
    const [pickerValue, setPickerValue] = useState<any>()
    const [open, setOpen] = useState(false)
    const [flag, setFlag] = useState(true)
    const [value, setValue] = useControllableValue(props, {defaultValue: null})
    const formatMap = {
        year: 'YYYY',
        month: 'YYYY-MM',
        date: 'YYYY-MM-DD',
    }
    const formatPicker = formatMap[picker] || 'YYYY-MM-DD'
    // 处理默认值
    useEffect(() => {
        if (!value || !flag) return;
        // @ts-ignore
        if (value === toNowKey || value === toNowText) {
            setPickerValue(moment(moment(new Date()).format('YYYY-MM-DD HH')));
        } else {
            setPickerValue(moment(moment(value).format(formatPicker)));
        }
    }, [value])
    // 选择日期回调
    const handleChang = (date, dateString) => {
        setFlag(false)
        if (!date) {
            setPickerValue(null)
            setValue(null)
        } else {
            setPickerValue(moment(date.format(formatPicker)));
            setValue(moment(date.format(formatPicker)))
        }
        // onChange && onChange(date, dateString)
        setOpen(false)
    }
    // 点击至今回调
    const handleNow = () => {
        setFlag(false)
        // onChange && onChange(moment(new Date()), moment(new Date()).format(formatPicker))
        setPickerValue(moment(moment(new Date()).format('YYYY-MM-DD HH')));
        setValue(moment(moment(new Date()).format('YYYY-MM-DD HH')))
        setOpen(false)
    }
    // 显示过滤
    const format = (value: Moment) => {
        if (pickerValue && pickerValue._f?.includes('HH')) {
            return toNowText;
        } else {
            return value.format(formatPicker);
        }
    };
    const footer = () => {
        return <>
            {
                showToNow ? <div onClick={handleNow} className={styles.datePickerFooter}>
                    {toNowText}
                </div> : ""
            }
        </>
    }

    // eslint-disable-next-line arrow-body-style
    const disabledDateFun: RangePickerProps['disabledDate'] = current => {
        const currentDay = current && current >= moment().endOf('day')
        if(typeTime === 1){
            return current && current > moment(controlDate).endOf('day') || currentDay;
        }else if(typeTime === 2){
            return current && current < moment(controlDate).endOf('day') || currentDay;
        }else{
            return currentDay;
        }
    };

    return <DatePicker
        disabledDate={(current) => disabledDateFun(current)}
        renderExtraFooter={footer}
        {...props}
        onBlur={() => setOpen(false)}
        onClick={() => setOpen(true)}
        open={open}
        value={pickerValue}
        onChange={handleChang}
        format={format}
    />
}
备注: flag状态是为了解决一些场景中的问题而进行的useEffect的控制,如不要可忽略。
import React, {CSSProperties, PropsWithChildren, useEffect, useState} from 'react';
import JdDatePicker from "@/components/atoms/DatePicker";
import {useControllableValue} from "ahooks";
import {Form} from 'antd';
import {View} from "@jd/ea-project-components";

interface IProps {
    style?: CSSProperties
    width?: number
    isDisable?: boolean
    type?: string
    fromKey?: any[]
    isShowNow?: boolean
}

export default function FormRangePicker(props: PropsWithChildren<IProps>) {
    const {
        width,
        isDisable = false,
        type,
        fromKey=[],
        isShowNow=true
    } = props
    const [value = [null, null], setValue] = useControllableValue(props, {defaultValue: [null, null]})
    const [startValue, setStartValue] = useState(null)
    const [endValue, setEndValue] = useState(null)
    useEffect(() => {
        setStartValue(value[0])
        setEndValue(value[1])
    }, [])

    useEffect(() => {
        setValue([startValue, endValue])
    }, [startValue,endValue])

    return <View>
        <Form.Item rules={[{required: true, message: '请选择开始时间'}]} name={fromKey[0]} fieldKey={fromKey[0]}>
            <JdDatePicker
                ...
            />
        </Form.Item>
        <span style={{padding: 5}}>~</span>
        <Form.Item rules={[{required: true, message: '请选择结束时间'}]} name={fromKey[1]} fieldKey={fromKey[1]}>
            <JdDatePicker
                key={'endTime'}
                placeholder={`请选择结束时间`}
                disabled={isDisable}
                picker={type as any}
                style={{width}}
                showToNow={isShowNow}
                toNowKey={'endTime'}
                onChange={setEndValue}
                controlDate={startValue}
                typeTime={2}
            />
        </Form.Item>
    </View>
}
备注:主要使用controlDate参数进行disable时间的控制。
  1. antd-Popover组件: onVisibleChange使用

解决业务场景:使用 echarts图表信息需要在Popover的content中绘制出来,useEffect中无法监听dom渲染情况,导致没有时机对图表信息进行绘制。

// 控制图表渲染变量
const [renderChart, setChartRneder]=useState(false)

// 挂在图表的dom
const chartsRef = useRef(null);

// 渲染
  useEffect(()=>{
      chartsRef.current && getOptions()
  },[renderChart])

// 图表信息
const getOptions = ()=>{
      let myChart = echarts.init(chartsRef.current);
      myChart?.clear()
      let option = {
          color: ['#4C7CFF'],
          ...//option参数
      };
      option && myChart?.setOption(option);
  }
  
<Popover
        content={content}
        ...// 业务场景props
        onVisibleChange={(visible) => {
        	// 可在此事件中监听popover的展示,使用renderChart变量控制图表不会重复渲染
            if(visible && !renderChart){
                setChartRneder(true)
            }
        }}
    >

前后端接口加解密实现:

const CryptoJS = require('crypto-js');  //引用AES源码js
const keyBytes = CryptoJS.enc.Utf8.parse('xxxxxxxx');
const ivBytes = CryptoJS.enc.Hex.parse('xxxxxxxxxxxxxxxxxxx');

function Encrypt(data) {
    const encrypted = CryptoJS.AES.encrypt(data, keyBytes, {
        iv: ivBytes,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    });

    return encrypted.toString(); // 返回 Base64 编码的字符串
}

function Decrypt(encryptedData) {
    const decrypted = CryptoJS.AES.decrypt(encryptedData, keyBytes, {
        iv: ivBytes,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    });
    const decryptedStr = decrypted.toString(CryptoJS.enc.Utf8);
    try {
        const tempJson = JSON.parse(decryptedStr);
        return tempJson;
    } catch (error) {
        return decryptedStr;
    }
}

export {
    Decrypt ,
    Encrypt
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值