如何用ts搭建一个vue3通用项目底座 | 第四篇:utils函数

前言

前面三篇的源码在gitee上可以查看common-template,1.0.1版本是规范篇结束,1.0.2版本是vite配置篇结束,后面都是一些细节补充,各位可以根据自己需要阅读。

1、log工具函数

在src目录下新建utils文件,里面新建log.ts文件,用来输出warn和error的工具函数。

// log.ts
const projectName = import.meta.env.VITE_GLOB_APP_TITLE;

export function warn(message: string) {
  console.warn(`[${projectName} warn]:${message}`);
}

export function error(message: string) {
  throw new Error(`[${projectName} error]:${message}`);
}

比较简单,没什么可说的。

2、env环境相关函数

新建env.ts文件,这里存放环境变量的工具函数。

// env.ts
import type { GlobEnvConfig } from '/#/config';
import { warn } from '/@/utils/log';
import pkg from '../../package.json';
import { getConfigFileName } from '../../build/getConfigFileName';

/**
 * 缓存秘钥前缀
 */
export function getCommonStoragePrefix() {
  const { VITE_GLOB_APP_SHORT_NAME } = getAppEnvConfig();
  return `${VITE_GLOB_APP_SHORT_NAME}__${getEnvMode()}`.toUpperCase();
}

/**
 * 根据版本生成缓存密钥
 */
export function getStorageShortName() {
  return `${getCommonStoragePrefix()}${`__${pkg.version}`}__`.toUpperCase();
}

/**
 * 获取全局配置(打包时将独立提取配置)
 */
export function getENV(): GlobEnvConfig {
  const isMode = isDevMode();
  const ENV_NAME = getConfigFileName(import.meta.env);

  return isMode ? (import.meta.env as unknown as GlobEnvConfig) : window[ENV_NAME];
}

/**
 * @description: 获取环境变量
 * @example: development
 */
export function getEnvMode(): string {
  return import.meta.env.MODE;
}

/**
 * @description: 是否是开发模式
 */
export function isDevMode(): boolean {
  return import.meta.env.DEV;
}

/**
 * @description: 是否是生产模式
 */
export function isProdMode(): boolean {
  return import.meta.env.PROD;
}

export function getAppEnvConfig() {
  const ENV = getENV();

  const {
    VITE_GLOB_APP_TITLE,
    VITE_GLOB_API_URL,
    VITE_GLOB_APP_SHORT_NAME,
    VITE_GLOB_API_URL_PREFIX,
    VITE_GLOB_UPLOAD_URL,
  } = ENV;

  if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) {
    warn(`VITE_GLOB_APP_SHORT_NAME变量只能是字符/下划线,请在环境变量中修改并重新运行.`);
  }

  return {
    VITE_GLOB_APP_TITLE,
    VITE_GLOB_API_URL,
    VITE_GLOB_APP_SHORT_NAME,
    VITE_GLOB_API_URL_PREFIX,
    VITE_GLOB_UPLOAD_URL,
  };
}

每个函数的作用都在注释里写着。
这里涉及到一个/#/config路径文件,代表在types目录下的config文件,在src/types目录下新建config.d.ts文件全局类型文件。

// config.d.ts
export interface GlobEnvConfig {
  // 站点名称
  VITE_GLOB_APP_TITLE: string;
  // 服务接口url
  VITE_GLOB_API_URL: string;
  // 服务接口url前缀
  VITE_GLOB_API_URL_PREFIX?: string;
  // 项目简称
  VITE_GLOB_APP_SHORT_NAME: string;
  // 上传url
  VITE_GLOB_UPLOAD_URL?: string;
}

3、cipher加密解密函数

这个工具函数用到了crypto-js插件,安装一下。

// package.json
"crypto-js": "^4.1.1",
"@types/crypto-js": "^4.1.1",

新建cipher.ts文件

// cipher.ts
import { encrypt, decrypt } from 'crypto-js/aes';
import { parse } from 'crypto-js/enc-utf8';
import pkcs7 from 'crypto-js/pad-pkcs7';
import ECB from 'crypto-js/mode-ecb';
import md5 from 'crypto-js/md5';
import UTF8 from 'crypto-js/enc-utf8';
import Base64 from 'crypto-js/enc-base64';

export interface EncryptionParams {
  key: string;
  iv: string;
}

export class AesEncryption {
  private key;
  private iv;

  constructor(opt: Partial<EncryptionParams> = {}) {
    const { key, iv } = opt;
    if (key) {
      this.key = parse(key);
    }
    if (iv) {
      this.iv = parse(iv);
    }
  }

  get getOptions() {
    return {
      mode: ECB,
      padding: pkcs7,
      iv: this.iv,
    };
  }

  // 加密
  encryptByAES(cipherText: string) {
    return encrypt(cipherText, this.key, this.getOptions).toString();
  }

  // 解密
  decryptByAES(cipherText: string) {
    return decrypt(cipherText, this.key, this.getOptions).toString(UTF8);
  }
}

// UTF8解密
export function encryptByBase64(cipherText: string) {
  return UTF8.parse(cipherText).toString(Base64);
}

// Base64解密
export function decodeByBase64(cipherText: string) {
  return Base64.parse(cipherText).toString(UTF8);
}

// md5解密
export function encryptByMd5(password: string) {
  return md5(password).toString();
}

4、is判断函数

新建is.ts函数,用来判断数据类型

// is.ts
const toString = Object.prototype.toString;

// 判断是否符合[object type],是返回true
export function isType(val: unknown, type: string) {
  return toString.call(val) === `[object ${type}]`;
}

// 判断数据类型是否为undefined,是返回false
export function isDef<T = unknown>(val?: T): val is T {
  return typeof val !== 'undefined';
}

// 判断数据类型是否为undefined,是返回true
export function isUnDef<T = unknown>(val?: T): val is T {
  return !isDef(val);
}

// 判断数据类型是否为Object且不为null,是返回true
export function isObj(val: any): val is Record<any, any> {
  return val !== null && isType(val, 'Object');
}

// 判断数据是否为空,是返回true
export function isBlank<T = unknown>(val: T): val is T {
  if (isArr(val) || isStr(val)) {
    return val.length === 0;
  }

  if (val instanceof Map || val instanceof Set) {
    return val.size === 0;
  }

  if (isObj(val)) {
    return Object.keys(val).length === 0;
  }

  return false;
}

// 判断数据是否为Date类型,是返回true
export function isTime(val: unknown): val is Date {
  return isType(val, 'Date');
}

// 判断数据是否为Null类型,是返回true
export function hasNull(val: unknown): val is null {
  return val === null;
}

// 判断数据是否为null或者undef,是返回ture
export function isNullOrUnDef(val: unknown): val is null | undefined {
  return isUnDef(val) || hasNull(val);
}

// 判断数据是否是Number类型,是返回true
export function isNum(val: unknown): val is number {
  return isType(val, 'Number');
}

// 判断是否是promise类型,是返回true
export function isPromise<T = any>(val: unknown): val is Promise<T> {
  return isType(val, 'Promise') && isObj(val) && isFn(val.then) && isFn(val.catch);
}

// 判断数据是否是String类型,是返回true
export function isStr(val: unknown): val is string {
  return isType(val, 'String');
}

// 判断是否是function,是返回true
export function isFn(val: unknown): val is Function {
  return typeof val === 'function';
}

// 判断数据是否是Boolean,是返回true
export function isBol(val: unknown): val is boolean {
  return isType(val, 'Boolean');
}

// 判断数据是否是RegExp,是返回true
export function isRegEx(val: unknown): val is RegExp {
  return isType(val, 'RegExp');
}

// 判断是否是数组,是返回true
export function isArr(val: any): val is Array<any> {
  return val && Array.isArray(val);
}

// 判断数据是否是Window类型,是返回true
export function isWindow(val: any): val is Window {
  return typeof window !== 'undefined' && isType(val, 'Window');
}

// 判断是否为Dom元素,是返回true
export function isEle(val: unknown): val is Element {
  return isObj(val) && !!val.tagName;
}

// 判断是否为Map类型,是返回true
export function hasMap(val: unknown): val is Map<any, any> {
  return isType(val, 'Map');
}

// 判断是否为网址,是返回true
export function isUrl(path: string): boolean {
  const reg = /^http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- .\/?%&=]*)?/;
  return reg.test(path);
}

4、cache缓存函数

新建cache文件,里面存放如下几个文件。
在这里插入图片描述
直接上代码

// index.ts
import { getStorageShortName } from '/@/utils/env';
import { createStorage as create, CreateStorageParams } from './storageCache';
import { enableStorageEncryption } from '/@/settings/encryptionSetting';
import { DEFAULT_CACHE_TIME } from '/@/settings/encryptionSetting';

export type Options = Partial<CreateStorageParams>;

const createOptions = (storage: Storage, options: Options = {}): Options => {
  return {
    // 调试模式下没有加密
    hasEncrypt: enableStorageEncryption,
    storage,
    prefixKey: getStorageShortName(),
    ...options,
  };
};

export const WebStorage = create(createOptions(sessionStorage));

export const createStorage = (storage: Storage = sessionStorage, options: Options = {}) => {
  return create(createOptions(storage, options));
};

export const createSessionStorage = (options: Options = {}) => {
  return createStorage(sessionStorage, { ...options, timeout: DEFAULT_CACHE_TIME });
};

export const createLocalStorage = (options: Options = {}) => {
  return createStorage(localStorage, { ...options, timeout: DEFAULT_CACHE_TIME });
};

export default WebStorage;

// memory.ts
export interface Cache<V = any> {
  value?: V;
  timeoutId?: ReturnType<typeof setTimeout>;
  time?: number;
  alive?: number;
}

const NOT_ALIVE = 0;

export class Memory<T = any, V = any> {
  private cache: { [key in keyof T]?: Cache<V> } = {};
  private alive: number;

  constructor(alive = NOT_ALIVE) {
    // 单位秒
    this.alive = alive * 1000;
  }

  get getCache() {
    return this.cache;
  }

  setCache(cache) {
    this.cache = cache;
  }

  get<K extends keyof T>(key: K) {
    return this.cache[key];
  }

  set<K extends keyof T>(key: K, value: V, expires?: number) {
    let item = this.get(key);

    if (!expires || (expires as number) <= 0) {
      expires = this.alive;
    }
    if (item) {
      if (item.timeoutId) {
        clearTimeout(item.timeoutId);
        item.timeoutId = undefined;
      }
      item.value = value;
    } else {
      item = { value, alive: expires };
      this.cache[key] = item;
    }

    if (!expires) {
      return value;
    }
    const now = new Date().getTime();
    /**
     * 防止setTimeout最大延迟值溢出
     * 最大延迟值2,147,483,647 ms
     * https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#maximum_delay_value
     */
    item.time = expires > now ? expires : now + expires;
    item.timeoutId = setTimeout(
      () => {
        this.remove(key);
      },
      expires > now ? expires - now : expires,
    );

    return value;
  }

  remove<K extends keyof T>(key: K) {
    const item = this.get(key);
    Reflect.deleteProperty(this.cache, key);
    if (item) {
      clearTimeout(item.timeoutId!);
      return item.value;
    }
  }

  resetCache(cache: { [K in keyof T]: Cache }) {
    Object.keys(cache).forEach((key) => {
      const k = key as any as keyof T;
      const item = cache[k];
      if (item && item.time) {
        const now = new Date().getTime();
        const expire = item.time;
        if (expire > now) {
          this.set(k, item.value, expire);
        }
      }
    });
  }

  clear() {
    Object.keys(this.cache).forEach((key) => {
      const item = this.cache[key];
      item.timeoutId && clearTimeout(item.timeoutId);
    });
    this.cache = {};
  }
}

// persistent.ts
import type { ProjectConfig } from '/#/config';

import { createLocalStorage, createSessionStorage } from '/@/utils/cache';
import { Memory } from './memory';
import { PROJ_CFG_KEY, APP_LOCAL_CACHE_KEY, APP_SESSION_CACHE_KEY } from '/@/enums/cacheEnum';
import { DEFAULT_CACHE_TIME } from '/@/settings/encryptionSetting';
import { toRaw } from 'vue';

interface BasicStore {
  [PROJ_CFG_KEY]: ProjectConfig;
}

type LocalStore = BasicStore;

type SessionStore = BasicStore;

export type BasicKeys = keyof BasicStore;
type LocalKeys = keyof LocalStore;
type SessionKeys = keyof SessionStore;

const ls = createLocalStorage();
const ss = createSessionStorage();

const localMemory = new Memory(DEFAULT_CACHE_TIME);
const sessionMemory = new Memory(DEFAULT_CACHE_TIME);

function initPersistentMemory() {
  const localCache = ls.get(APP_LOCAL_CACHE_KEY);
  const sessionCache = ss.get(APP_SESSION_CACHE_KEY);
  localCache && localMemory.resetCache(localCache);
  sessionCache && sessionMemory.resetCache(sessionCache);
}

export class Persistent {
  static getLocal<T>(key: LocalKeys) {
    return localMemory.get(key)?.value as Nullable<T>;
  }

  static setLocal(key: LocalKeys, value: LocalStore[LocalKeys], immediate = false): void {
    localMemory.set(key, toRaw(value));
    immediate && ls.set(APP_LOCAL_CACHE_KEY, localMemory.getCache);
  }

  static removeLocal(key: LocalKeys, immediate = false): void {
    localMemory.remove(key);
    immediate && ls.set(APP_LOCAL_CACHE_KEY, localMemory.getCache);
  }

  static clearLocal(immediate = false): void {
    localMemory.clear();
    immediate && ls.clear();
  }

  static getSession<T>(key: SessionKeys) {
    return sessionMemory.get(key)?.value as Nullable<T>;
  }

  static setSession(key: SessionKeys, value: SessionStore[SessionKeys], immediate = false): void {
    sessionMemory.set(key, toRaw(value));
    immediate && ss.set(APP_SESSION_CACHE_KEY, sessionMemory.getCache);
  }

  static removeSession(key: SessionKeys, immediate = false): void {
    sessionMemory.remove(key);
    immediate && ss.set(APP_SESSION_CACHE_KEY, sessionMemory.getCache);
  }
  static clearSession(immediate = false): void {
    sessionMemory.clear();
    immediate && ss.clear();
  }

  static clearAll(immediate = false) {
    sessionMemory.clear();
    localMemory.clear();
    if (immediate) {
      ls.clear();
      ss.clear();
    }
  }
}

function storageChange(e: any) {
  const { key, newValue, oldValue } = e;

  if (!key) {
    Persistent.clearAll();
    return;
  }

  if (!!newValue && !!oldValue) {
    if (APP_LOCAL_CACHE_KEY === key) {
      Persistent.clearLocal();
    }
    if (APP_SESSION_CACHE_KEY === key) {
      Persistent.clearSession();
    }
  }
}

window.addEventListener('storage', storageChange);

initPersistentMemory();

// storageCache.ts
import { cacheCipher } from '/@/settings/encryptionSetting';
import type { EncryptionParams } from '/@/utils/cipher';
import { AesEncryption } from '/@/utils/cipher';
import { isNullOrUnDef } from '/@/utils/is';

export interface CreateStorageParams extends EncryptionParams {
  prefixKey: string;
  storage: Storage;
  hasEncrypt: boolean;
  timeout?: Nullable<number>;
}
export const createStorage = ({
  prefixKey = '',
  storage = sessionStorage,
  key = cacheCipher.key,
  iv = cacheCipher.iv,
  timeout = null,
  hasEncrypt = true,
}: Partial<CreateStorageParams> = {}) => {
  if (hasEncrypt && [key.length, iv.length].some((item) => item !== 16)) {
    throw new Error('当hasEncrypt为true时,密钥或iv必须为16位!');
  }

  const encryption = new AesEncryption({ key, iv });

  /**
   * 缓存Class
   * 构造参数可以传递到sessionStorage、localStorage,
   * @class Cache
   * @example
   */
  const WebStorage = class WebStorage {
    private storage: Storage;
    private prefixKey?: string;
    private encryption: AesEncryption;
    private hasEncrypt: boolean;
    /**
     *
     * @param {*} storage
     */
    constructor() {
      this.storage = storage;
      this.prefixKey = prefixKey;
      this.encryption = encryption;
      this.hasEncrypt = hasEncrypt;
    }

    private getKey(key: string) {
      return `${this.prefixKey}${key}`.toUpperCase();
    }

    /**
     * 设置缓存
     * @param {string} key
     * @param {*} value
     * @param {*} expire 过期时间(秒)
     * @memberof Cache
     */
    set(key: string, value: any, expire: number | null = timeout) {
      const stringData = JSON.stringify({
        value,
        time: Date.now(),
        expire: !isNullOrUnDef(expire) ? new Date().getTime() + expire * 1000 : null,
      });
      const stringifyValue = this.hasEncrypt
        ? this.encryption.encryptByAES(stringData)
        : stringData;
      this.storage.setItem(this.getKey(key), stringifyValue);
    }

    /**
     * 读取缓存
     * @param {string} key
     * @param {*} def
     * @memberof Cache
     */
    get(key: string, def: any = null): any {
      const val = this.storage.getItem(this.getKey(key));
      if (!val) return def;

      try {
        const decVal = this.hasEncrypt ? this.encryption.decryptByAES(val) : val;
        const data = JSON.parse(decVal);
        const { value, expire } = data;
        if (isNullOrUnDef(expire) || expire >= new Date().getTime()) {
          return value;
        }
        this.remove(key);
      } catch (e) {
        return def;
      }
    }

    /**
     * 基于密钥删除缓存
     * @param {string} key
     * @memberof Cache
     */
    remove(key: string) {
      this.storage.removeItem(this.getKey(key));
    }

    /**
     * 删除此instance的所有缓存
     */
    clear(): void {
      this.storage.clear();
    }
  };
  return new WebStorage();
};

在解释cache缓存函数之前,先新建几个文件。
在src目录下新建settings文件夹,里面用来存放项目配置等,先新建一个encryptionSetting.ts加密配置文件。

// encryptionSetting.ts
import { isDevMode } from '/@/utils/env';

// 系统默认缓存时间(秒)
export const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7;

// aes加密密钥
export const cacheCipher = {
  key: '_11111000001111@',
  iv: '@11111000001111_',
};

// 系统缓存是否使用aes加密
export const enableStorageEncryption = !isDevMode();

注意在非开发模式下会对缓存进行aes加密。
接着在src/enums目录下新建一个cacheEnum.ts缓存枚举文件。

// cacheEnum.ts
// project config key
export const PROJ_CFG_KEY = 'PROJ__CFG__KEY__';

// base global local key
export const APP_LOCAL_CACHE_KEY = 'COMMON__LOCAL__KEY__';

// base global session key
export const APP_SESSION_CACHE_KEY = 'COMMON__SESSION__KEY__';

在src/types/config.d.ts文件里添加下面代码。

// src/types/config.d.ts
export interface ProjectConfig {
  // 会话超时处理
  sessionTimeoutProcessing: SessionTimeoutProcessingEnum;
  // 使用错误处理程序插件
  useErrorHandle: boolean;
  // 切换接口时是否删除未关闭的消息并通知
  closeMessageOnSwitch: boolean;
  // 切换接口时是否取消已发送但未响应的http请求
  removeAllHttpPending: boolean;
}

cache缓存函数主要使用Persistent类和createLocalStorage、createSessionStorage两个函数,用来做整个项目的缓存功能。token的本地存储也可用这个缓存函数。

5、domUtils元素操作函数

// domUtils.ts
// 指定元素添加事件监听器
export function on(
  element: Element | HTMLElement | Document | Window,
  event: string,
  handler: EventListenerOrEventListenerObject,
): void {
  if (element && event && handler) {
    element.addEventListener(event, handler, false);
  }
}

// 指定元素移除事件监听器
export function off(
  element: Element | HTMLElement | Document | Window,
  event: string,
  handler: Fn,
): void {
  if (element && event && handler) {
    element.removeEventListener(event, handler, false);
  }
}

// 指定元素只添加一次事件监听器
export function once(el: HTMLElement, event: string, fn: EventListener): void {
  const listener = function (this: any, ...args: unknown[]) {
    if (fn) {
      fn.apply(this, args);
    }
    off(el, event, listener);
  };
  on(el, event, listener);
}

这里Fn类型报错,在types文件夹下新建index.d.ts文件。

// index.d.ts
declare interface Fn<T = any, R = T> {
  (...arg: T[]): R;
}

6、dateUtils时间操作工具

// dateUtils.ts
/**
 * 独立的时间操作工具
 */
import dayjs from 'dayjs';

const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
const DATE_FORMAT = 'YYYY-MM-DD';

// 初始化日期时间
export function formatToDateTime(date?: dayjs.ConfigType, format = DATE_TIME_FORMAT): string {
  return dayjs(date).format(format);
}

// 初始化时间
export function formatToDate(date?: dayjs.ConfigType, format = DATE_FORMAT): string {
  return dayjs(date).format(format);
}

export const dateUtil = dayjs;

7、mitt事件传递工具

// mitt.ts
/**
 * copy to https://github.com/developit/mitt
 * 展开clear方法
 */

export type EventType = string | symbol;

// 事件处理程序可以采用可选的事件参数
// 并且不应返回值
export type Handler<T = any> = (event?: T) => void;
export type WildcardHandler = (type: EventType, event?: any) => void;

// 一个类型的所有当前注册的事件处理程序的数组
export type EventHandlerList = Array<Handler>;
export type WildCardEventHandlerList = Array<WildcardHandler>;

// 事件类型及其对应的事件处理程序的映射.
export type EventHandlerMap = Map<EventType, EventHandlerList | WildCardEventHandlerList>;

export interface Emitter {
  all: EventHandlerMap;

  on<T = any>(type: EventType, handler: Handler<T>): void;
  on(type: '*', handler: WildcardHandler): void;

  off<T = any>(type: EventType, handler: Handler<T>): void;
  off(type: '*', handler: WildcardHandler): void;

  emit<T = any>(type: EventType, event?: T): void;
  emit(type: '*', event?: any): void;
  clear(): void;
}

/**
 * Mitt: 小型功能事件传递工具.
 * @name mitt
 * @returns {Mitt}
 */
export default function mitt(all?: EventHandlerMap): Emitter {
  all = all || new Map();

  return {
    /**
     * 事件名称到已注册处理程序函数的映射.
     */
    all,

    /**
     * 为给定类型注册事件处理程序.
     * @param {string|symbol} type 要侦听的事件类型,或所有事件的“*”类型
     * @param {Function} handler 响应给定事件而调用的函数
     * @memberOf mitt
     */
    on<T = any>(type: EventType, handler: Handler<T>) {
      const handlers = all?.get(type);
      const added = handlers && handlers.push(handler);
      if (!added) {
        all?.set(type, [handler]);
      }
    },

    /**
     * 删除给定类型的事件处理程序.
     * @param {string|symbol} type 要从中注销`handler`的事件类型,或`“*”`
     * @param {Function} handler 要删除的处理程序函数
     * @memberOf mitt
     */
    off<T = any>(type: EventType, handler: Handler<T>) {
      const handlers = all?.get(type);
      if (handlers) {
        handlers.splice(handlers.indexOf(handler) >>> 0, 1);
      }
    },

    /**
     * 调用给定类型的所有处理程序.
     * 如果存在,则在类型匹配的处理程序之后调用“*”处理程序.
     *
     * 注意:不支持手动启动“*”处理程序.
     *
     * @param {string|symbol} type 要调用的事件类型
     * @param {Any} [evt] 传递给每个处理程序的任何值(推荐使用且功能强大的对象)
     * @memberOf mitt
     */
    emit<T = any>(type: EventType, evt: T) {
      ((all?.get(type) || []) as EventHandlerList).slice().map((handler) => {
        handler(evt);
      });
      ((all?.get('*') || []) as WildCardEventHandlerList).slice().map((handler) => {
        handler(type, evt);
      });
    },

    /**
     * 全部清除
     */
    clear() {
      this.all.clear();
    },
  };
}

实际上就是兄弟组件传参的逻辑。
使用mitt除了可以做兄弟组件传参,还可以用来监视路由。

8、index基本工具函数

引入lodash-es函数库

// package.json
"lodash-es": "^4.17.21",
"@types/lodash-es": "^4.17.7",
// index.ts
import { type App, type Component } from 'vue';
import { isArr, isObj } from '/@/utils/is';
import { cloneDeep, mergeWith } from 'lodash-es';

export const noop = () => {};

/**
 * 将对象作为参数添加到URL
 * @param baseUrl url
 * @param obj
 * @returns {string}
 * eg:
 *  let obj = {a: '3', b: '4'}
 *  setObjToUrlParams('www.baidu.com', obj)
 *  ==>www.baidu.com?a=3&b=4
 */
export function setObjToUrlParams(baseUrl: string, obj: any): string {
  let parameters = '';
  for (const key in obj) {
    parameters += key + '=' + encodeURIComponent(obj[key]) + '&';
  }
  parameters = parameters.replace(/&$/, '');
  return /\?$/.test(baseUrl) ? baseUrl + parameters : baseUrl.replace(/\/?$/, '?') + parameters;
}

/**
 递归合并两个对象。
 @param target 目标对象,合并后结果存放于此。
 @param source 要合并的源对象。
 @returns 合并后的对象。
 */
export function deepMerge<T extends object | null | undefined, U extends object | null | undefined>(
  target: T,
  source: U,
): T & U {
  return mergeWith(cloneDeep(target), source, (objValue, srcValue) => {
    if (isObj(objValue) && isObj(srcValue)) {
      return mergeWith(cloneDeep(objValue), srcValue, (prevValue, nextValue) => {
        return isArr(prevValue) ? prevValue.concat(nextValue) : undefined;
      });
    }
  });
}

// https://github.com/vant-ui/vant/issues/8302
// 用来修复tsx组件缺少事件的typescript定义
type EventShim = {
  new (...args: any[]): {
    $props: {
      onClick?: (...args: any[]) => void;
    };
  };
};

export type WithInstall<T> = T & {
  install(app: App): void;
} & EventShim;

export type CustomComponent = Component & { displayName?: string };

/**
 * 全局注册函数
 * @param component 需要全局注册的模块
 * @returns 注册的模块
 */
export const withInstall = <T extends CustomComponent>(component: T) => {
  (component as Record<string, unknown>).install = (app: App) => {
    const compName = component.name || component.displayName;
    if (!compName) return;
    app.component(compName, component);
  };
  return component as WithInstall<T>;
};

里面有一些常用函数。

结语

本篇内容是1.0.3的内容。未完待续…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值