vue3 常用hooks

86 阅读4分钟

useCachePreload.ts

import { ref, onMounted } from 'vue';

/**
 * 缓存/预加载的 hook
 * @param {Function} loadFunction - 用于加载数据的函数
 * @param {Object} options - 配置选项
 * @param {number} options.cacheTime - 缓存的有效时间(毫秒),默认 30 分钟
 * @returns {Object} - 包含加载状态、数据和加载函数的对象
 */
function useCachePreload<T>(loadFunction: () => T, { cacheTime = 30 * 60 * 1000 } = {}) {
  // 加载状态
  const loading = ref(false);
  // 数据
  const data = ref<any>(null);
  // 上次加载的时间
  const lastLoadTime = ref(0);

  /**
   * 加载数据的函数
   */
  const loadData = async () => {
    loading.value = true;
    try {
      // 如果缓存未过期,直接使用缓存数据
      if (Date.now() - lastLoadTime.value < cacheTime && data.value) {
        return;
      }
      const newData = await loadFunction();
      data.value = newData;
      lastLoadTime.value = Date.now();
    } catch (error) {
      // TODO
    } finally {
      loading.value = false;
    }
  };

  // 组件挂载时加载数据
  onMounted(loadData);

  // 返回相关数据和函数供组件使用
  return {
    loading,
    data,
    loadData
  };
}

export default useCachePreload;

useChart.ts

import type { EChartsOption } from 'echarts';
import * as echarts from 'echarts/core';
import {
  BarChart,
  LineChart,
  PieChart,
  MapChart,
  PictorialBarChart,
  RadarChart,
  ScatterChart
} from 'echarts/charts';

import {
  TitleComponent,
  TooltipComponent,
  GridComponent,
  PolarComponent,
  AriaComponent,
  ParallelComponent,
  LegendComponent,
  RadarComponent,
  ToolboxComponent,
  DataZoomComponent,
  VisualMapComponent,
  TimelineComponent,
  CalendarComponent,
  GraphicComponent
} from 'echarts/components';

import { SVGRenderer, CanvasRenderer } from "echarts/renderers";

echarts.use([
  LegendComponent,
  TitleComponent,
  TooltipComponent,
  GridComponent,
  PolarComponent,
  AriaComponent,
  ParallelComponent,
  BarChart,
  LineChart,
  PieChart,
  MapChart,
  RadarChart,
  PictorialBarChart,
  RadarComponent,
  ToolboxComponent,
  DataZoomComponent,
  VisualMapComponent,
  TimelineComponent,
  CalendarComponent,
  GraphicComponent,
  ScatterChart
]);

export enum RenderType {
  SVGRenderer = 'SVGRenderer',
  CanvasRenderer = 'CanvasRenderer'
}
export enum ThemeType {
  Light = 'light',
  Dark = 'dark',
  Default = 'default'
}

export default function useChart(elRef: Ref<HTMLDivElement>, autoChartSize = false, animation: boolean = false, render: RenderType = RenderType.SVGRenderer, theme: ThemeType = ThemeType.Default) {
  // 渲染模式
  echarts.use(render === RenderType.SVGRenderer ? SVGRenderer : CanvasRenderer)
  // echart实例
  let chartInstance: echarts.ECharts | null = null;

  // 初始化echart
  const initCharts = () => {
    const el = unref(elRef)
    if (!el || !unref(el)) {
      return
    }
    chartInstance = echarts.init(el, theme);
  }

  // 更新/设置配置
  const setOption = (option: EChartsOption) => {
    nextTick(() => {
      if (!chartInstance) {
        initCharts();
        if (!chartInstance) return;
      }

      chartInstance.setOption(option)
      hideLoading()
    })

  }

  // 获取echart实例
  function getInstance(): echarts.ECharts | null {
    if (!chartInstance) {
      initCharts();
    }
    return chartInstance;
  }

  // 更新大小
  function resize() {
    chartInstance?.resize();
  }

  // 监听元素大小
  function watchEl() {
    // 给元素添加过渡
    if (animation) { elRef.value.style.transition = 'width 1s, height 1s' }
    const resizeObserver = new ResizeObserver((entries => resize()))
    resizeObserver.observe(elRef.value);
  }

  // 显示加载状
  function showLoading() {
    if (!chartInstance) {
      initCharts();
    }
    chartInstance?.showLoading()
  }
  // 显示加载状
  function hideLoading() {
    if (!chartInstance) {
      initCharts();
    }
    chartInstance?.hideLoading()
  }

  onMounted(() => {
    window.addEventListener('resize', resize)
    if (autoChartSize) watchEl();
  })

  onUnmounted(() => {
    window.removeEventListener('resize', resize)
  })

  return {
    setOption,
    getInstance,
    showLoading,
    hideLoading
  }
}

useConfirm.ts

import { ElMessageBox, ElMessage } from "element-plus";
import { ResultEnum } from '/@/enums/httpEnum';

/**
 * @description 操作单条数据信息 (二次确认【删除、禁用、启用、重置密码】)
 * @param {Function} api 操作数据接口的api方法 (必传)
 * @param {Object} params 携带的操作数据参数 {id,params} (必传)
 * @param {String} message 提示信息 (必传)
 * @param {String} confirmType icon类型 (不必传,默认为 warning)
 * @returns {Promise}
 */
const useConfirm = (
  api: (params: any) => Promise<any>,
  params: any = {},
  message: string,
  confirmType: 'warning'
) => {
  return new Promise((resolve, reject) => {
    ElMessageBox.confirm(`${message}`, "温馨提示", {
      confirmButtonText: "确定",
      cancelButtonText: "取消",
      type: confirmType,
      draggable: true
    }).then(async () => {
      const res = await api(params);
      if (!res) return reject(false);
      ElMessage({
        type: "success",
        message: `操作成功!`
      });
      resolve(true);
    });
  });
};

export default useConfirm;

useDraggable.ts

import { ref, onMounted, onUnmounted } from 'vue';

/**
 * 让元素具有可拖拽功能的 hook
 * @param {HTMLElement} element - 要设置为可拖拽的元素
 * @returns {Object} 包含拖拽相关状态和方法的对象
 */
function useDraggable(element) {
  // 初始时,元素的位置
  const initialPosition = { x: 0, y: 0 };

  // 鼠标按下时的位置
  const mouseDownPosition = { x: 0, y: 0 };

  // 元素是否正在被拖拽
  const isDragging = ref(false);

  // 鼠标按下事件处理函数
  const handleMouseDown = (event: { preventDefault: () => void; clientX: number; clientY: number; }) => {
    event.preventDefault();
    isDragging.value = true;
    mouseDownPosition.x = event.clientX;
    mouseDownPosition.y = event.clientY;
    initialPosition.x = element.offsetLeft;
    initialPosition.y = element.offsetTop;
    document.addEventListener('mousemove', handleMouseMove);
    document.addEventListener('mouseup', handleMouseUp);
  };

  // 鼠标移动事件处理函数
  const handleMouseMove = (event: { clientX: number; clientY: number; }) => {
    if (isDragging.value) {
      const deltaX = event.clientX - mouseDownPosition.x;
      const deltaY = event.clientY - mouseDownPosition.y;
      element.style.left = initialPosition.x + deltaX + 'px';
      element.style.top = initialPosition.y + deltaY + 'px';
    }
  };

  // 鼠标抬起事件处理函数
  const handleMouseUp = () => {
    isDragging.value = false;
    document.removeEventListener('mousemove', handleMouseMove);
    document.removeEventListener('mouseup', handleMouseUp);
  };

  // 组件挂载时为元素添加鼠标按下事件监听
  onMounted(() => {
    element.addEventListener('mousedown', handleMouseDown);
  });

  // 组件卸载时移除事件监听
  onUnmounted(() => {
    element.removeEventListener('mousedown', handleMouseDown);
  });

  // 返回复制状态和复制操作
  return {
    isDragging
  };
}

export default useDraggable;

useSelection.ts

import { ref, computed } from "vue";

/**
 * @description 表格多选数据操作
 * @param {String} rowKey 当表格可以多选时,所指定的 guid
 * */
const useSelection = (rowKey: string = 'guid') => {
  const isSelected = ref<boolean>(false);
  const selectedList = ref<{ [key: string]: any }[]>([]);

  // 当前选中的所有 ids 数组
  const selectedListIds = computed((): string[] => {
    return selectedList.value.map(item => item[rowKey]);
  });

  // 列表选中行数
  const selectListCount = computed((): number => {
    return selectedList.value.length;
  });

  /**
   * @description 多选操作
   * @param {Array} rowArr 当前选择的所有数据
   * @return void
   */
  const onSelectionChange = (rowArr: { [key: string]: any }[]) => {
    rowArr.length ? (isSelected.value = true) : (isSelected.value = false);
    selectedList.value = rowArr;
  };

  return {
    isSelected,
    selectedList,
    selectedListIds,
    selectListCount,
    onSelectionChange
  };
};

export default useSelection;

useTable.ts

import { computed, reactive, toRefs } from "vue";

// 默认分页
const DEFAULT_PAGE_SIZE = 10;

export namespace Table {
  export interface Pagination {
    pageIndex: number;
    pageSize: number;
    total: number;
  }
  export interface StateProps {
    loading: Boolean;
    searchParam: Object;
    totalParam: Object;
    tableData: any[];
    pages: Pagination;
  }
}

/**
 * @description table 页面操作方法封装
 * @param {Function} api 获取表格数据 api 方法(必传)
 * @param {Object} initParam 获取数据查询参数(非必传,默认为{})
 * @param {Boolean} isShowPage 是否有分页(非必传,默认为true)
 * @param {Function} onError 对后台返回报错处理的方法(非必传)
 * */
export const useTable = (
  api: (params: any) => Promise<any>,
  initParam: object = {},
  immediate: boolean = true,
  isShowPage: boolean = true,
  onError?: (error: any) => void
) => {
  const state = reactive<Table.StateProps>({
    loading: false,
    searchParam: {}, // 查询参数(只包括查询)
    totalParam: {}, // 查询参数(包括分页查询)
    tableData: [],
    pages: {
      pageIndex: 1,
      pageSize: DEFAULT_PAGE_SIZE,
      total: 0
    }
  });

  /**
   * @description 分页查询参数(只包括分页和表格字段排序,其他排序方式可自行配置)
   * */
  const pageParam = computed(() => {
    return {
      pageIndex: state.pages.pageIndex,
      pageSize: state.pages.pageSize
    };
  });

  /**
   * @description 获取表格数据
   * @return void
   * */
  const getTableList = async () => {
    if (!api) return;
    state.loading = true;
    try {
      // 先把初始化参数和分页参数放到总参数里面
      Object.assign(state.totalParam, initParam, isShowPage ? pageParam.value : {});
      const { pages, data } = await api({ ...state.totalParam });
      state.tableData = data ?? [];
      // 解构后台返回的分页数据 (如果有分页更新分页信息)
      isShowPage && updatePages(pages);
    } catch (error) {
      onError && onError(error);
    } finally {
      state.loading = false;
    }
  };

  // 是否立即触发
  const isImmediate = immediate ?? true;
  onMounted(() => isImmediate && getTableList());

  /**
   * @description 更新分页信息
   * @param {Object} pages 后台返回的分页数据
   * @return void
   * */
  const updatePages = (pages: Table.Pagination) => {
    Object.assign(state.pages, pages);
  };

  /**
   * @description 表格数据查询
   * @return void
   * */
  const onSearch = () => {
    state.pages.pageIndex = 1;
    getTableList();
  };

  /**
   * @description 表格数据重置
   * @return void
   * */
  const onReset = () => {
    state.pages.pageIndex = 1;
    getTableList();
  };

  /**
   * @description 表格数据刷新
   * @return void
   * */
  const onRefresh = () => {
    getTableList();
  };

  /**
   * @description 分页组件改变
   * @param {Object} pageIndex 当前页数
   * @param {Object} pageSize 当前条数
   * @return void
   * */
  const onPageChange = (pages: TableDemoPageType) => {
    state.pages.pageIndex = pages.pageIndex;
    state.pages.pageSize = pages.pageSize;
    getTableList();
  };

  return {
    ...toRefs(state),
    getTableList,
    onPageChange,
    onSearch,
    onReset,
    onRefresh
  }
};

export default useTable;