Files
djangoadmin-wiki/DjangoAdmin_FastAPI_NaiveVue/zh/volume4/frontend/hooks.md
T
2026-09-14 10:33:29 +08:00

261 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Hooks 组合式函数
:::tip 说明
项目封装了一系列组合式函数(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
<n-button :loading="subLoading" type="primary" @click="submit">确定</n-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);
```
:::tip 温馨提示
Hooks 是 Vue3 组合式 API 的核心模式,将可复用逻辑抽离为函数。`usePermission` 供 `v-perm` 指令和 `v-if` 条件使用,`useLockFn` 防止按钮重复点击,`useAsync` 统一管理 loading 状态。
:::
## 总结
项目提供了 usePermission(权限检查)、useLockFn(防重复提交)、useAsync(异步 loading)、useTime(实时时钟)、useProjectSetting(项目配置)、useDesignSetting(设计配置)、useWindowSizeFn(窗口变化)等组合式函数,将常用逻辑封装为可复用的函数,避免 Mixin 的命名冲突问题。