签入版本
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
# 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
|
||||
<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);
|
||||
```
|
||||
|
||||
:::tip 温馨提示
|
||||
Hooks 是 Vue3 组合式 API 的核心模式,将可复用逻辑抽离为函数。`usePermission` 供 `v-perm` 指令和 `v-if` 条件使用,`useLockFn` 防止按钮重复点击,`useAsync` 统一管理 loading 状态。
|
||||
:::
|
||||
|
||||
## 总结
|
||||
|
||||
项目提供了 usePermission(权限检查)、useLockFn(防重复提交)、useAsync(异步 loading)、useTime(实时时钟)、useProjectSetting(项目配置)、useDesignSetting(设计配置)、useWindowSizeFn(窗口变化)等组合式函数,将常用逻辑封装为可复用的函数,避免 Mixin 的命名冲突问题。
|
||||
Reference in New Issue
Block a user