Skip to content

Hooks 组合式函数

说明

项目封装了一系列组合式函数(Hooks),位于 src/hooks/src/utils/ 目录,提供权限检查、异步 loading、防重复提交、实时时钟、项目配置等可复用逻辑。

Hooks 目录结构

src/hooks/
├── event/
│ ├── useEventListener.ts   # DOM 事件监听
│ ├── useBreakpoint.ts      # 响应式断点
│ └── useWindowSizeFn.ts    # 窗口大小变化回调
├── setting/
│ ├── useProjectSetting.ts  # 项目配置(权限模式、水印等)
│ └── useDesignSetting.ts   # 设计配置(主题色、深色模式)
├── web/
│ ├── usePermission.ts      # 权限检查(hasPermission、hasSomePermission)
│ ├── useECharts.ts         # ECharts 封装
│ ├── usePage.ts            # 页面跳转
│ ├── useTags.ts            # 标签页管理
│ └── useCopyToClipboard.ts # 剪贴板复制
├── core/
│ └── useTimeout.ts         # 定时器封装
├── useDomWidth.ts          # DOM 宽度
├── useOnline.ts            # 网络状态
├── useBattery.ts           # 电量信息
├── useTime.ts              # 实时时钟
└── use-async.ts            # 异步 loading 管理

usePermission

权限检查 Hook,从 user store 获取权限列表进行判断:

typescript
// src/hooks/web/usePermission.ts

import { useUserStore } from '@/store/modules/user';

export function usePermission() {
    const userStore = useUserStore();

    function _somePermissions(accesses: string[]) {
        const permissionsList = userStore.getPermissions;
        if (permissionsList[0] == '*:*:*') return true;  // 超级管理员
        return accesses.some((item) => permissionsList.includes(item));
    }

    // 是否包含其中某个权限(用于 v-perm 指令)
    function hasSomePermission(accesses?: string | string[]): boolean {
        const permissionsList = userStore.getPermissions;
        if (permissionsList[0] === '*:*:*') return true;
        const list = Array.isArray(accesses) ? accesses : [];
        return list.some((access) => permissionsList.includes(access));
    }

    // 是否包含所有权限(用于 v-perms 指令)
    function hasEveryPermission(accesses?: string | string[]): boolean {
        const list = Array.isArray(accesses) ? accesses : [];
        const permissionsList = userStore.getPermissions;
        if (permissionsList[0] === '*:*:*') return true;
        return list.every((access) => permissionsList.includes(access));
    }

    // 判断是否存在权限(可用于 v-if 显示逻辑)
    function hasPermission(accesses: string[]): boolean {
        if (!accesses || !accesses.length) return true;
        return _somePermissions(accesses);
    }

    return { hasPermission, hasEveryPermission, hasSomePermission };
}

使用示例:

typescript
import { usePermission } from '@/hooks/web/usePermission';

const { hasPermission, hasSomePermission } = usePermission();

// v-if 条件渲染
const showAddBtn = computed(() => hasPermission(['sys:position:add']));

// 检查是否有任一权限
const canOperate = hasSomePermission(['sys:position:update', 'sys:position:delete']);

useLockFn

防重复提交 Hook,函数执行期间自动锁定,位于 src/utils/useLockFn.ts

typescript
// src/utils/useLockFn.ts
import { ref } from 'vue';

export function useLockFn(fn: (...args: any[]) => Promise<any>) {
    const isLock = ref(false);
    const lockFn = async (...args: any[]) => {
        if (isLock.value) return;  // 锁定期间忽略调用
        isLock.value = true;
        try {
            await fn(...args);
        } finally {
            isLock.value = false;  // 无论成功失败都解锁
        }
    };
    return { isLock, lockFn };
}

实际用法(edit.vue 中的提交按钮):

typescript
// src/views/system/position/edit.vue
import { useLockFn } from '@/utils/useLockFn';

const handleSubmit = async () => {
    await formRef.value?.validate();
    props.positionId ? await positionUpdate(formData) : await positionAdd(formData);
    message('操作成功');
    emit('update:visible', false);
    emit('success');
};

// isLock 绑定按钮的 loading 状态,lockFn 作为点击处理函数
const { isLock: subLoading, lockFn: submit } = useLockFn(handleSubmit);
vue
<el-button :loading="subLoading" type="primary" @click="submit">确定</el-button>

useAsync

异步操作 loading 管理,位于 src/hooks/use-async.ts

typescript
// src/hooks/use-async.ts
import { isReactive, isRef } from 'vue';

function setLoading(loading, val) {
    if (loading != undefined && isRef(loading)) {
        loading.value = val;
    } else if (loading != undefined && isReactive(loading)) {
        loading.loading = val;
    }
}

export const useAsync = async (func: Promise<any>, loading: any): Promise<any> => {
    setLoading(loading, true);
    return await func.finally(() => setLoading(loading, false));
};

使用示例:

typescript
const loading = ref(false);
const fetchData = async () => {
    await useAsync(getPositionList(params), loading);
};

useTime

实时时钟 Hook,每秒更新当前时间,位于 src/hooks/useTime.ts

typescript
// src/hooks/useTime.ts
import { ref, onMounted, onUnmounted } from 'vue';

export function useTime() {
    let timer;
    const year = ref(0);
    const month = ref(0);
    const week = ref('');
    const day = ref(0);
    const hour = ref<number | string>(0);
    const minute = ref<number | string>(0);
    const second = ref(0);

    const updateTime = () => {
        const date = new Date();
        year.value = date.getFullYear();
        month.value = date.getMonth() + 1;
        week.value = '日一二三四五六'.charAt(date.getDay());
        day.value = date.getDate();
        hour.value = (date.getHours() + '')?.padStart(2, '0');
        minute.value = (date.getMinutes() + '')?.padStart(2, '0');
        second.value = date.getSeconds();
    };

    updateTime();
    onMounted(() => { timer = setInterval(() => updateTime(), 1000); });
    onUnmounted(() => { clearInterval(timer); });

    return { month, day, hour, minute, second, week };
}

useProjectSetting

项目配置 Hook,读取项目全局设置:

typescript
// src/hooks/setting/useProjectSetting.ts
export function useProjectSetting() {
    const projectStore = useProjectSettingStore();
    return {
        getPermissionMode: computed(() => projectStore.permissionMode),  // 'BACK' | 'FRONT'
        getIsWaterMark: computed(() => projectStore.isWaterMark),
        // ... 其他配置
    };
}

useDesignSetting

设计配置 Hook,管理主题色和深色模式:

typescript
// src/hooks/setting/useDesignSetting.ts
export function useDesignSetting() {
    return {
        getAppTheme: computed(() => designStore.appTheme),
        getDarkTheme: computed(() => designStore.darkTheme),
        // ... 其他设计配置
    };
}

useWindowSizeFn

窗口大小变化回调 Hook,用于表格高度自适应:

typescript
// src/hooks/event/useWindowSizeFn.ts

export function useWindowSizeFn(callback: Fn, wait?: number) {
    // 监听 window.resize 事件,防抖执行回调
}

BasicTable 组件中使用此 Hook 自动计算表格高度:

typescript
// src/components/Table/src/Table.vue
useWindowSizeFn(computeTableHeight, 280);

温馨提示

Hooks 是 Vue3 组合式 API 的核心模式,将可复用逻辑抽离为函数。usePermissionv-perm 指令和 v-if 条件使用,useLockFn 防止按钮重复点击,useAsync 统一管理 loading 状态。

总结

项目提供了 usePermission(权限检查)、useLockFn(防重复提交)、useAsync(异步 loading)、useTime(实时时钟)、useProjectSetting(项目配置)、useDesignSetting(设计配置)、useWindowSizeFn(窗口变化)等组合式函数,将常用逻辑封装为可复用的函数,避免 Mixin 的命名冲突问题。

小蚂蚁云团队 · 提供技术支持