js封装

363 阅读8分钟

1.取消前后空格

str.replace(/(^\s*)|(\s*$)/g, "");

2.事件封装方法

    addEvent(elem,type,handle){
        if(elem.addEventListener){
           elem.addEventListener(type,handle,false);
        }else if(elem.attachEvent){
           elem.attachEvent("on" + type,function(){
               handle.call(elem);
            })
        }else{
           elem["on" + type] = handle;
        }
    },

3.获取浏览器可视区域的高度

    clientHeight(){
        if(window.innerHeight !== undefined){
            return window.innerHeight
        }else if(document.compatMode === "CSS1Compat"){
            return document.documentElement.clientHeight
        }else{
            return document.body.clientWidth
        }
    },

4.获取滚动条滚动的高度

let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;

5.回到顶部

    backTop() {
        window.scrollTo(0,0)
    },

6.回到顶部按钮显示隐藏函数

    btnShow() {
        let _this = this;
        let btn = _this.$refs.topBtn;
        this.addEvent(window,'scroll',()=> {
            let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
            let clientHeight = _this.clientHeight();
            if(scrollTop > clientHeight){
                btn.style.display = 'block';
            }else{
                btn.style.display = 'none';
            }
        })
    }

7.vue路由监听

    watch: {
        $route(to,from){
            this.getCurrentRoute();
        }
    },
    methods: {
        getCurrentRoute(){
            let current = this.$route.path;
        }
    }

8.获取base64图片的大小

    // 获取base64图片的大小
      getImgByteSize(data) {
        const equalIndex = data.indexOf('='); // 获取=号下标
        let strLength, fileLength;
        if (equalIndex > 0) {
          const str = data.substring(0, equalIndex); // 去除=号
          strLength = str.length;
          fileLength = strLength - (strLength / 8) * 2; // 真实的图片byte大小
        } else {
          strLength = data.length;
          fileLength = strLength - (strLength / 8) * 2;
        } 
        console.log(fileLength)
        return Math.floor(fileLength); // 向下取整
      },

9.压缩base64图片

    // 压缩图片
    compressImg(base64, callback){
        let imgSize = this.getImgByteSize(base64);
        let maxSize = 1024 * 1024 * 8 // 图片最大为8M
        let compressSize = 1024 * 300 // 压缩目标为300kb 
        // 如果图片小于300kb则返回,否则压缩图片
        if(imgSize > maxSize){
            alert('图片最大为8M,请重新上传')
            return;
        }
        if(imgSize < compressSize){
            callback(base64)
            return ;
        }
        let newImage = new Image();
        let quality = 0.65;    //压缩系数0-1之间
        let that = this;
        newImage.src = base64;
        newImage.setAttribute("crossOrigin", 'Anonymous');	//url为外域时需要
        var imgWidth, imgHeight;
        this.addEvent(newImage, 'load', function() {
            imgWidth = this.width;
            imgHeight = this.height;
            var canvas = document.createElement("canvas");
            var ctx = canvas.getContext("2d");
            canvas.width = imgWidth;
            canvas.height = imgHeight
            ctx.clearRect(0, 0, imgWidth, imgHeight);
            ctx.drawImage(this, 0, 0, imgWidth, imgHeight);
            var base64 = canvas.toDataURL("image/jpeg", quality); //压缩语句
            // 如想确保图片压缩到自己想要的尺寸,如要求在小于300kb之间,请加以下语句,quality初始值根据情况自定
            imgSize = that.getImgByteSize(base64);
            // 500kb以内,压缩质量0.01为单位递减。
            let nearSize = 500 *1024
            while (imgSize > compressSize) { 
            if(imgSize > nearSize){
                quality -= 0.1
            }else{
                quality -= 0.01;
            }
            base64 = canvas.toDataURL("image/jpeg", quality);
            imgSize = that.getImgByteSize(base64);
            }
            callback(base64);//必须通过回调函数返回,否则无法及时拿到该值
        })
      },

10.js判断数据为空的方法

    //js判断数据为空的方法
    function isBlank(str){
        if (Object.prototype.toString.call(str) ==='[object Undefined]'){//空
            return true
        } else if (
            Object.prototype.toString.call(str) === '[object String]' || 
            Object.prototype.toString.call(str) === '[object Array]') { //字条串或数组
            return str.length==0?true:false
        } else if (Object.prototype.toString.call(str) === '[object Object]') {
            return JSON.stringify(str)=='{}'?true:false
        }else{
            return true
        }
    }

11.判断数据类型

    isString (o) { //是否字符串
        return Object.prototype.toString.call(o).slice(8, -1) === 'String'
    }

    isNumber (o) { //是否数字
        return Object.prototype.toString.call(o).slice(8, -1) === 'Number'
    }

    isBoolean (o) { //是否boolean
        return Object.prototype.toString.call(o).slice(8, -1) === 'Boolean'
    }

    isFunction (o) { //是否函数
        return Object.prototype.toString.call(o).slice(8, -1) === 'Function'
    }

    isNull (o) { //是否为null
        return Object.prototype.toString.call(o).slice(8, -1) === 'Null'
    }

    isUndefined (o) { //是否undefined
        return Object.prototype.toString.call(o).slice(8, -1) === 'Undefined'
    }

    isObj (o) { //是否对象
        return Object.prototype.toString.call(o).slice(8, -1) === 'Object'
    }

    isArray (o) { //是否数组
        return Object.prototype.toString.call(o).slice(8, -1) === 'Array'
    }

    isDate (o) { //是否时间
        return Object.prototype.toString.call(o).slice(8, -1) === 'Date'
    }

    isRegExp (o) { //是否正则
        return Object.prototype.toString.call(o).slice(8, -1) === 'RegExp'
    }

    isError (o) { //是否错误对象
        return Object.prototype.toString.call(o).slice(8, -1) === 'Error'
    }

    isSymbol (o) { //是否Symbol函数
        return Object.prototype.toString.call(o).slice(8, -1) === 'Symbol'
    }

    isPromise (o) { //是否Promise对象
        return Object.prototype.toString.call(o).slice(8, -1) === 'Promise'
    }

    isSet (o) { //是否Set对象
        return Object.prototype.toString.call(o).slice(8, -1) === 'Set'
    }
    
    isBlob (o) { //是否Blob对象
        return Object.prototype.toString.call(o).slice(8, -1) === 'Blob'
      }

12.判断浏览器类型

    browserType(){
        var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串
        var isOpera = userAgent.indexOf("Opera") > -1; //判断是否Opera浏览器
        var isIE = userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1 && !isOpera; //判断是否IE浏览器
        var isIE11 = userAgent.indexOf('Trident') > -1 && userAgent.indexOf("rv:11.0") > -1;
        var isEdge = userAgent.indexOf("Edge") > -1 && !isIE; //判断是否IE的Edge浏览器  
        var isFF = userAgent.indexOf("Firefox") > -1; //判断是否Firefox浏览器
        var isSafari = userAgent.indexOf("Safari") > -1 && userAgent.indexOf("Chrome") == -1; //判断是否Safari浏览器
        var isChrome = userAgent.indexOf("Chrome") > -1 && userAgent.indexOf("Safari") > -1; //判断Chrome浏览器

        if (isIE) {
            var reIE = new RegExp("MSIE (\\d+\\.\\d+);");
            reIE.test(userAgent);
            var fIEVersion = parseFloat(RegExp["$1"]);
            if(fIEVersion == 7) return "IE7"
            else if(fIEVersion == 8) return "IE8";
            else if(fIEVersion == 9) return "IE9";
            else if(fIEVersion == 10) return "IE10";
            else return "IE7以下"//IE版本过低
        }
        if (isIE11) return 'IE11';
        if (isEdge) return "Edge";
        if (isFF) return "FF";
        if (isOpera) return "Opera";
        if (isSafari) return "Safari";
        if (isChrome) return "Chrome";
    }

13.表单常用校验信息

    /^1[3|4|5|6|7|8][0-9]{9}$/.test(str); //校验手机号
    `/(^\d{18}$)|(^\d{17}(\d|X|x)$)/.test(str); //校验身份证号`

14.数组相关

    const uniqueArray = arr => [...new Set(arr)]; //数组去重
    const randomArray = arr => arr.sort(() => Math.random() - 0.5); //随机打乱数组顺序

15.驼峰和连字符相互转化

    // 连字符转驼峰
    const camelizeRE = /-(\w)/g;
    camelize = str => {
        return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
    }
    // 驼峰转连字符
    const hyphenateRE = /\B([A-Z])/g;
    export const hyphenate = str => {
        return str.replace(hyphenateRE, '-$1').toLowerCase()
    }

replace里面回调函数详解

;

16.axios全局拦截封装

主要包括,开发和生产环境请求地址,全局loading设置,请求头中增加token,避免同一个请求触发多次,以及添加路由跳转取消当前所有请求,以及对响应信息统一判断等功能。

    import axios from 'axios';
    import Toast from 'show-toast';
    import store from '@/store';
    import { dataType } from '@/util'
    const { isBlob } = dataType;
    let myFlag = '';
    let baseURL = '/api';
    if(process.env.NODE_ENV == 'production') {
      baseURL = 'http://www.abc.com';
    }
    let generateKey = function (url, method, data) {
      let strData = JSON.stringify(data);
      return (`${url || ''}${method || ''}${strData || ''}`);
    };
    
    const instance = axios.create({
      baseURL,
      timeout: 9000,
      withCredentials: true,
    });
    // 请求拦截
    instance.interceptors.request.use(function (config) {
      let requestKey = generateKey(config.url, config.method, config.data);
      if (myFlag == requestKey) {
        return Promise.reject({
          code: 'CANCEL'
        });
      }
      store.commit('showLoading',true); //显示加载中
      config.cancelToken = new axios.CancelToken(function (cancel) {
        store.commit('pushToken', {cancelToken: cancel})
      })
      myFlag = requestKey;
      
      // 请求头添加token
      config.headers.token = store.state.userToken;
      return config;
    }, function (error) {
      // Do something with request error
      return Promise.reject(error);
    });
    
    
    /* 拦截返回预处理---获取数据 */
    instance.interceptors.response.use(function (response) {
      myFlag = '';
      store.commit('showLoading',false); // 关闭loading
      // if (response.status !== 200) {
      const data = response.data;
      // 判断返回的数据是否是二进制对象,因为如果是二进制则只会返回文件流,没有状态码的,所以需要单独判断。
      if(isBlob(data)) {
        if(data.type=="application/vnd.ms-excel"){
          data.success = true;
        }else{
          Toast('下载失败!')
        }
        return response
      }else {
        if(data.ret == 0){
          data.success = true;
        }else if(data.ret == 1053){
          Toast('登录失效,请重新登录');
          store.dispatch('clearUserInfo').then(()=> location.href = "#/login");
        }else{
          data && Toast(data.msg || '请求数据失败!!!');
        }
        return data;
      }
    }, function (error) {
      myFlag = '';
      store.commit('showLoading',false); // 关闭loading
      // 如果是连续发起的请求。
      if (error.code === 'CANCEL') {
        return Promise.reject(error);
      }
      if(error.message.indexOf('Network') > -1) {
        Toast('暂无网络!!!')
        return Promise.reject(error);
      }
      if(error.message.indexOf('timeout') > -1){
        Toast('请求超时!!!')
        return Promise.reject(error);
      }
      // Toast('服务器故障!!!');
      return Promise.reject(error);
    });
    export default instance;

17.vuex基本配置。

主要配置有:登录,退出等接口,以及全局loading,由于路由跳转取消请求的数组,用户基本信息,以及路由相关函数等。

此处还增加了权限等配置,如果是管理员则展示所有路由,如果不是管理员,则需要根据具体权限permission来动态生成路由。

    import Vue from 'vue'
    import Vuex from 'vuex'
    import { login, logout } from '@/service/api/user';
    import { getUserInfo } from '@/util'
    import { publicRoutes, asyncRoutes, redirectRoutes, addRedirect, generateRoutes} from '@/router/permission';
    import { Loading } from 'element-ui';
    Vue.use(Vuex)
    let userInfo = getUserInfo();
    export default new Vuex.Store({
      state: {
        loading: false, // 是否展示loading
        loadingCount: 0, // 当发起多个请求的时候,会有用
        cancelTokenArr: [], //取消请求token数组
        username: userInfo('email') || '', // 用户名
        loginStatus: !!userInfo('email'), // 是否登录标志
        userToken: userInfo('token') || '', // token
        permission: userInfo('permission') || [], // 权限列表
        admin: userInfo('privilegeLevel') == 0 ? true : false,// 是否是管理员 0:管理员,1:普通领导
        // admin: true,// 是否是管理员 0:管理员,1:普通领导
        routes: publicRoutes,
        hasUpdateRoutes: false,
      },
      mutations: {
        showLoading(state,newValue) {
          // isChangeRoute 表示是否是路由跳转
          if(newValue) {
            state.loadingCount ++;
            state.loading = newValue;
          }else{
            state.loadingCount --;
            if(state.loadingCount <= 0) {
              state.loading = newValue;
              state.loadingCount = 0;
            }
          }
          // state.loading = newValue;
        },
        pushToken (state, payload) {
          state.cancelTokenArr.push(payload.cancelToken)
        },
        clearToken ({ cancelTokenArr }) {
          cancelTokenArr.forEach(item => {
            item('路由跳转取消请求')
          })
          cancelTokenArr = []
        },
        updateUsername(state, newValue) {
          state.username = newValue;
        },
        updateLoginStatus(state, newValue) {
          state.loginStatus = newValue
        },
        updateUserToken(state, newValue) {
          state.userToken = newValue
        },
        updatePermission(state, newValue) {
          state.permission = newValue
        },
        updateAdmin(state, newValue) {
          state.admin = newValue == 0 ? true : false;
        },
        updateRoutes(state, newValue) {
          state.routes = newValue;
        },
        updateRoutesFlag(state, newValue){
          state.hasUpdateRoutes = newValue;
        }
      },
      actions: {
        doLogin({ commit }, data){
          return new Promise((resolve, reject) => {
            login(data).then(res => {
              if(res.success) {
                let userInfo = res.datas;
                localStorage.setItem("userInfo", JSON.stringify(userInfo));
                commit('updateUsername', userInfo.email)
                commit('updateLoginStatus', true)
                commit('updateUserToken', userInfo.token)
                commit('updatePermission', userInfo.permission)
                commit('updateAdmin', userInfo.privilegeLevel)
                commit('updateRoutesFlag', false)
                commit('updateRoutes', publicRoutes)
                resolve(res);
              }else {
                reject(res)
              }
            }).catch(err => reject(err))
          })
        },
        clearUserInfo({commit}) {
          return new Promise(resolve => {
            localStorage.removeItem("userInfo");
            commit('updateUsername', '')
            commit('updateLoginStatus', false)
            commit('updateUserToken', '')
            commit('updatePermission', [])
            commit('updateAdmin', '')
            commit('updateRoutesFlag', false)
            commit('updateRoutes', publicRoutes)
            resolve()
          })
        },
        doLogout({ dispatch }) {
          return logout()
        },
        perfectRoutes({ dispatch, commit, state}) {
          return new Promise(resolve => {
            let arr = [];
            arr = state.admin ? arr.concat(asyncRoutes) : arr.concat(generateRoutes(asyncRoutes, state.permission));
            arr = addRedirect(arr)
            arr.push(redirectRoutes);
            let all = publicRoutes.concat(arr)
            commit('updateRoutes', all)
            commit('updateRoutesFlag', true)
            resolve(all)
          })
        }
      },
    })

18.权限配置(permission.js)

主要内容有公共路由,动态路由,重定向路由。

    import { _import } from '@/util'
    // 创建公共路由
    export const publicRoutes = [
      {
        path: '/login',
        name: 'login',
        component: _import('login'),
        meta: {title: '登录'}
      },
      {
        path: '/404',
        name: '404',
        component: _import('404'),
        meta: {title: '404'}
      },
    ]
    // 注意因为要把这句放在最后,才不会影响其他的路由,所以单独拿出来了
    export const redirectRoutes = {
      path: '*',
      redirect: '/404'
    }
    export const asyncRoutes = [
      {
        path: '/',
        component: _import('layout'),
        meta: {title: '首页', auth: 3},
        children: [
          {
            path: 'saleManager',
            component: _import('saleManager'),
            meta: {title: '销售管理', auth: 3},
            icon: 'el-icon-menu',
            children: [
              // {
              //   path: '/',
              //   redirect: 'businessGroup',
              //   meta: {title: '事业群排名', auth: 3},
              // },
              {
                path: 'businessGroup',
                name: 'businessGroup',
                component: _import('saleManager/businessGroup'),
                meta: {title: '事业群排名', auth: 1},
              },
              {
                path: 'division',
                name: 'division',
                component: _import('saleManager/division'),
                meta: {title: '分部排名', auth: 2},
              },
              {
                path: 'team',
                name: 'team',
                component: _import('saleManager/team'),
                meta: {title: '团队排名', auth: 3},
              },
              {
                path: 'advisor',
                name: 'advisor',
                component: _import('saleManager/advisor'),
                meta: {title: '投顾排名', auth: 3},
              },
            ]
          },
          {
            path: 'newCustomerManager',
            name: 'newCustomerManager',
            component: _import('newCustomerManager'),
            meta: {title: '拉新目标管理'},
            icon: 'el-icon-s-custom',
          },
          {
            path: 'dateMarkManager',
            name: 'dateMarkManager',
            component: _import('dateMarkManager'),
            meta: {title: '日期标记管理'},
            icon: 'el-icon-s-claim',
          },
          // {
          //   path: '/',
          //   redirect: 'saleManager'
          // },
        ]
      },
    ]
    
    export const addRedirect = function(routes) {
      routes.forEach(item => {
        if(item.children && item.children.length > 0) {
          let obj = {};
          obj.path = '/',
          obj.redirect = item.children[0].path;
          item.children.push(obj);
          addRedirect(item.children)
        }
      })
      return routes;
    }
    export const generateRoutes = function(routes, permission) {
      let res = []
      routes.forEach(route => {
        let cur = {};
        let auth = route.meta && route.meta.auth;
        if(permission && permission.includes(auth)) {
          Object.keys(route).forEach(key => {
            if(key=="children" && route[key].length > 0) {
              cur[key] = generateRoutes(route[key], permission)
            }else {
              cur[key] = route[key]
            }
          })
          res.push(cur)
        }
      });
      return res;
    }

19.全局导航(router.js)

主要内容有自定义$addRoutes方法,进度条,登录拦截,路由跳转取消请求得调用,以及动态生成路由函数。

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    import store from '../store'
    import NProgress from 'nprogress'
    import 'nprogress/nprogress.css'
    Vue.use(VueRouter)
    
    
    const router = new VueRouter({
      routes: store.state.routes
    })
    // 自定义$addRoutes方法,如果直接使用router.addRoutes定义的话,仅仅只是添加路由,原有路由还不会变。
    // 所以会出现“Duplicate named routes definition:”的错误提示
    router.$addRoutes = (params) => {
      router.matcher = new VueRouter().matcher;
      router.addRoutes(params)
    }
    router.beforeEach((to, from, next) => {
      NProgress.start()
      if (to.meta.title) {
        document.title = `${to.meta.title} - 恒大财富战报管理系统`;
      }
      store.commit('clearToken'); // 取消请求
      store.commit('showLoading', false); 
      if(to.path != '/login' && !store.state.loginStatus) {
        let redirect = to.fullPath;
        // 注意在这里面加入NProgress.done();原因是,当点击退出按钮的时候,通过手动在地址栏输入路由时,进度条不会消失的状况。
        NProgress.done();
        return next({
          path: '/login',
          query: {
            redirect
          }
        })
      }else if(store.state.loginStatus && !store.state.hasUpdateRoutes) {
        store.dispatch('perfectRoutes').then((res) => {
          // 注意虽然将routes存入vuex中,登录以后,vuex里面的路由会改变,但是router.options.routes里的路由还是原来的路由
          // 因此,我们需要手动赋值给router.options。
          router.options.routes = res;
          // 注意如果不加入下面这句,页面会出现空白,虽然url有路由,但是却显示空。
          // 但是如果只加入这句不加上面那句,虽然页面会显示,但是router.options.routes的内容并不会改变
          // 重写方法也是为了,当第二个用户登录的时候,防止权限交叉。
          router.$addRoutes(res);
          // 替代之前的路由,防止留下历史记录。
          NProgress.done();
          return next({...to, replace: true});
        })
      }
      next()
    })
    
    router.afterEach(() => {
      NProgress.done();
    })
    export default router

})  

20.获取用户信息

    // 获取当前用户的所有信息
    export const user = function() {
      return JSON.parse(localStorage.getItem('userInfo'));
    }
    // 获取当前用户的指定信息 注意此函数为了避免多次执行user函数
    export const getUserInfo = function() {
      let userInfo = user();
      return info => userInfo == null ? null : userInfo[info]    
    }

21.base64转为二进制

    export const base64_to_blob = function(data) {
      let bstr = window.atob(data),
      n = bstr.length, 
      u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      return u8arr;
    }

22.下载文件

    export const blob_to_url = function(data, name, type) {
      if(!dataType.isBlob(data)) {
        return;
      }
      let blob = new Blob([data], {type});
      let objectUrl = window.URL.createObjectURL(blob);  //生成一个url
      let downloadElement = document.createElement('a');
      downloadElement.href = objectUrl;
      downloadElement.download = name; 
      document.body.appendChild(downloadElement);
      downloadElement.click(); //点击下载
      document.body.removeChild(downloadElement); //下载完成移除元素
      window.URL.revokeObjectURL(objectUrl); //释放blob对象
    }    

23.将小数转为固定位数的百分数。

    // 将小数变成百分比,n是代表保留几位小数
    export const toPercent = function(num, n) {
      if(num === 0) {
        return num+'%';
      }
      let str = (num * 100) + '',
        index = str.indexOf('.'),
        res = (index > -1) ? str.substring(0, index+n+1) : str;
        // console.log(num, str , res)
      return res + '%'
    }

24.路由懒加载

    // 注意"[request]"是传入的变量名
  let _import = file => () => import(/* webpackChunkName: "[request]" */ `@/views/${file}.vue`)

    {
        path: '/about',
        name: 'about',
        component: _import('about')
    }

需要注意的是: 如上图,如果想要打包的文件不含有“About-vue”中的vue字样,则直接可以把上面的${file}.vue中的“.vue”去掉即可。

25.五星评级

变量rate是1到5的值,然后执行上面代码 ★★★★★☆☆☆☆☆".slice(5 - rate, 10 - rate);

26.浮点类型取整

    var a = ~~2.33

    var b= 2.33 | 0

    var c= 2.33 >> 0

27.数字变为金钱格式化

1234567890 --> 1,234,567,890

    let reg =  /(?=(\B)(\d{3})+$)/g || /\B(?=(\d{3})+$)/g
    let res = '12345678'.replace(reg,',');

28.两个变量交换的方法

    var a = 11, b = 22;
    // 第一种方法
    a ^= b;
    b ^= a;
    a ^= b;
    // 第二种方法
    a = a + b;
    b = a - b;
    a = a - b;

29.一行代码实现深拷贝

let res = JSON.parse(JSON.stringify({})

30.数组去重

    let arr = [1,2,3,2,1,3,4,1];
    // 第一种
    let res1 = [...new Set(arr)]
    // 第二种
    let res2 = Array.from(new Set(arr))

31.取数组中的最大值和最小值

Math.max(...arr1); Math.min(...arr2)

32.防抖和节流

  • 防抖:最后一次为准。
    function debounce (callback, time) {
		var timer;
		return function() {
			if(timer) {
				clearTimeout(timer)
			}else{
				timer = setTimeout(()=> {
					callback();
				},time)
			}
		}
	}
	window.addEventListener('scroll', debounce(()=> {
		console.log('防抖')
	},200) ,false)
  • 节流
    function throttle(callback, time) {
		var timer;
		return function() {
			if(!timer){
				timer = setTimeout(()=> {
					callback();
					clearTimeout(timer);
					timer = null;
				},time)
			}
			
		}
	}
	window.addEventListener('scroll', throttle(()=> {
		console.log('节流')
	},200) ,false)

33.将数据转为树形(tree)结构

    function buildTree(list) {
        let res = [],
        temp = {};
         list.forEach(item => {
            temp[item.menuId] = item
        })
            for(let i in temp){
            let itm = temp[i]
            if(itm.parentId > 100) {
                let parentId = itm.parentId;
                if(!temp[parentId].child) {
                    temp[parentId].child = []
                }
                temp[parentId].child.push(itm)
            }else {
                res.push(itm)
            }
        }
        return res
    }