物业代码生成
This commit is contained in:
19
packages/effects/hooks/README.md
Normal file
19
packages/effects/hooks/README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# @vben/hooks
|
||||
|
||||
用于多个 `app` 公用的 hook,继承了 `@vben/hooks` 的所有能力。业务上有通用 hooks 可以放在这里。
|
||||
|
||||
## 用法
|
||||
|
||||
### 添加依赖
|
||||
|
||||
```bash
|
||||
# 进入目标应用目录,例如 apps/xxxx-app
|
||||
# cd apps/xxxx-app
|
||||
pnpm add @vben/hooks
|
||||
```
|
||||
|
||||
### 使用
|
||||
|
||||
```ts
|
||||
import { useNamespace } from '@vben/hooks';
|
||||
```
|
33
packages/effects/hooks/package.json
Normal file
33
packages/effects/hooks/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "@vben/hooks",
|
||||
"version": "5.5.6",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
|
||||
"directory": "packages/effects/hooks"
|
||||
},
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"sideEffects": [
|
||||
"**/*.css"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"default": "./src/index.ts"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@vben-core/composables": "workspace:*",
|
||||
"@vben/preferences": "workspace:*",
|
||||
"@vben/stores": "workspace:*",
|
||||
"@vben/types": "workspace:*",
|
||||
"@vben/utils": "workspace:*",
|
||||
"@vueuse/core": "catalog:",
|
||||
"vue": "catalog:",
|
||||
"vue-router": "catalog:",
|
||||
"watermark-js-plus": "catalog:"
|
||||
}
|
||||
}
|
9
packages/effects/hooks/src/index.ts
Normal file
9
packages/effects/hooks/src/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export * from './use-app-config';
|
||||
export * from './use-content-maximize';
|
||||
export * from './use-design-tokens';
|
||||
export * from './use-hover-toggle';
|
||||
export * from './use-pagination';
|
||||
export * from './use-refresh';
|
||||
export * from './use-tabs';
|
||||
export * from './use-watermark';
|
||||
export * from '@vben-core/composables';
|
40
packages/effects/hooks/src/use-app-config.ts
Normal file
40
packages/effects/hooks/src/use-app-config.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type {
|
||||
ApplicationConfig,
|
||||
VbenAdminProAppConfigRaw,
|
||||
} from '@vben/types/global';
|
||||
|
||||
/**
|
||||
* 由 vite-inject-app-config 注入的全局配置
|
||||
*/
|
||||
export function useAppConfig(
|
||||
env: Record<string, any>,
|
||||
isProduction: boolean,
|
||||
): ApplicationConfig {
|
||||
// 生产环境下,直接使用 window._VBEN_ADMIN_PRO_APP_CONF_ 全局变量
|
||||
const config = isProduction
|
||||
? window._VBEN_ADMIN_PRO_APP_CONF_
|
||||
: (env as VbenAdminProAppConfigRaw);
|
||||
|
||||
const {
|
||||
VITE_GLOB_API_URL,
|
||||
VITE_GLOB_APP_CLIENT_ID,
|
||||
VITE_GLOB_ENABLE_ENCRYPT,
|
||||
VITE_GLOB_RSA_PRIVATE_KEY,
|
||||
VITE_GLOB_RSA_PUBLIC_KEY,
|
||||
VITE_GLOB_SSE_ENABLE,
|
||||
} = config;
|
||||
|
||||
return {
|
||||
// 后端地址
|
||||
apiURL: VITE_GLOB_API_URL,
|
||||
// 客户端key
|
||||
clientId: VITE_GLOB_APP_CLIENT_ID,
|
||||
enableEncrypt: VITE_GLOB_ENABLE_ENCRYPT === 'true',
|
||||
// RSA私钥
|
||||
rsaPrivateKey: VITE_GLOB_RSA_PRIVATE_KEY,
|
||||
// RSA公钥
|
||||
rsaPublicKey: VITE_GLOB_RSA_PUBLIC_KEY,
|
||||
// 是否开启sse
|
||||
sseEnable: VITE_GLOB_SSE_ENABLE === 'true',
|
||||
};
|
||||
}
|
24
packages/effects/hooks/src/use-content-maximize.ts
Normal file
24
packages/effects/hooks/src/use-content-maximize.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { updatePreferences, usePreferences } from '@vben/preferences';
|
||||
/**
|
||||
* 主体区域最大化
|
||||
*/
|
||||
export function useContentMaximize() {
|
||||
const { contentIsMaximize } = usePreferences();
|
||||
|
||||
function toggleMaximize() {
|
||||
const isMaximize = contentIsMaximize.value;
|
||||
|
||||
updatePreferences({
|
||||
header: {
|
||||
hidden: !isMaximize,
|
||||
},
|
||||
sidebar: {
|
||||
hidden: !isMaximize,
|
||||
},
|
||||
});
|
||||
}
|
||||
return {
|
||||
contentIsMaximize,
|
||||
toggleMaximize,
|
||||
};
|
||||
}
|
321
packages/effects/hooks/src/use-design-tokens.ts
Normal file
321
packages/effects/hooks/src/use-design-tokens.ts
Normal file
@@ -0,0 +1,321 @@
|
||||
import { reactive, watch } from 'vue';
|
||||
|
||||
import { preferences, usePreferences } from '@vben/preferences';
|
||||
import { convertToRgb, updateCSSVariables } from '@vben/utils';
|
||||
|
||||
/**
|
||||
* 用于适配各个框架的设计系统
|
||||
*/
|
||||
|
||||
export function useAntdDesignTokens() {
|
||||
const rootStyles = getComputedStyle(document.documentElement);
|
||||
|
||||
const tokens = reactive({
|
||||
borderRadius: '' as any,
|
||||
colorBgBase: '',
|
||||
colorBgContainer: '',
|
||||
colorBgElevated: '',
|
||||
colorBgLayout: '',
|
||||
colorBgMask: '',
|
||||
colorBorder: '',
|
||||
colorBorderSecondary: '',
|
||||
colorError: '',
|
||||
colorInfo: '',
|
||||
colorPrimary: '',
|
||||
colorSuccess: '',
|
||||
colorTextBase: '',
|
||||
colorWarning: '',
|
||||
zIndexPopupBase: 2000, // 调整基础弹层层级,避免下拉等组件被弹窗或者最大化状态下的表格遮挡
|
||||
});
|
||||
|
||||
const getCssVariableValue = (variable: string, isColor: boolean = true) => {
|
||||
const value = rootStyles.getPropertyValue(variable);
|
||||
return isColor ? `hsl(${value})` : value;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => preferences.theme,
|
||||
() => {
|
||||
tokens.colorPrimary = getCssVariableValue('--primary');
|
||||
|
||||
tokens.colorInfo = getCssVariableValue('--primary');
|
||||
|
||||
tokens.colorError = getCssVariableValue('--destructive');
|
||||
|
||||
tokens.colorWarning = getCssVariableValue('--warning');
|
||||
|
||||
tokens.colorSuccess = getCssVariableValue('--success');
|
||||
|
||||
tokens.colorTextBase = getCssVariableValue('--foreground');
|
||||
|
||||
getCssVariableValue('--primary-foreground');
|
||||
|
||||
tokens.colorBorderSecondary = tokens.colorBorder =
|
||||
getCssVariableValue('--border');
|
||||
|
||||
tokens.colorBgElevated = getCssVariableValue('--popover');
|
||||
|
||||
tokens.colorBgContainer = getCssVariableValue('--card');
|
||||
|
||||
tokens.colorBgBase = getCssVariableValue('--background');
|
||||
|
||||
const radius = Number.parseFloat(getCssVariableValue('--radius', false));
|
||||
// 1rem = 16px
|
||||
tokens.borderRadius = radius * 16;
|
||||
|
||||
tokens.colorBgLayout = getCssVariableValue('--background-deep');
|
||||
tokens.colorBgMask = getCssVariableValue('--overlay');
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
return {
|
||||
tokens,
|
||||
};
|
||||
}
|
||||
|
||||
export function useNaiveDesignTokens() {
|
||||
const rootStyles = getComputedStyle(document.documentElement);
|
||||
|
||||
const commonTokens = reactive({
|
||||
baseColor: '',
|
||||
bodyColor: '',
|
||||
borderColor: '',
|
||||
borderRadius: '',
|
||||
cardColor: '',
|
||||
dividerColor: '',
|
||||
errorColor: '',
|
||||
errorColorHover: '',
|
||||
errorColorPressed: '',
|
||||
errorColorSuppl: '',
|
||||
invertedColor: '',
|
||||
modalColor: '',
|
||||
popoverColor: '',
|
||||
primaryColor: '',
|
||||
primaryColorHover: '',
|
||||
primaryColorPressed: '',
|
||||
primaryColorSuppl: '',
|
||||
successColor: '',
|
||||
successColorHover: '',
|
||||
successColorPressed: '',
|
||||
successColorSuppl: '',
|
||||
tableColor: '',
|
||||
textColorBase: '',
|
||||
warningColor: '',
|
||||
warningColorHover: '',
|
||||
warningColorPressed: '',
|
||||
warningColorSuppl: '',
|
||||
});
|
||||
|
||||
const getCssVariableValue = (variable: string, isColor: boolean = true) => {
|
||||
const value = rootStyles.getPropertyValue(variable);
|
||||
return isColor ? convertToRgb(`hsl(${value})`) : value;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => preferences.theme,
|
||||
() => {
|
||||
commonTokens.primaryColor = getCssVariableValue('--primary');
|
||||
commonTokens.primaryColorHover = getCssVariableValue('--primary-600');
|
||||
commonTokens.primaryColorPressed = getCssVariableValue('--primary-700');
|
||||
commonTokens.primaryColorSuppl = getCssVariableValue('--primary-800');
|
||||
|
||||
commonTokens.errorColor = getCssVariableValue('--destructive');
|
||||
commonTokens.errorColorHover = getCssVariableValue('--destructive-600');
|
||||
commonTokens.errorColorPressed = getCssVariableValue('--destructive-700');
|
||||
commonTokens.errorColorSuppl = getCssVariableValue('--destructive-800');
|
||||
|
||||
commonTokens.warningColor = getCssVariableValue('--warning');
|
||||
commonTokens.warningColorHover = getCssVariableValue('--warning-600');
|
||||
commonTokens.warningColorPressed = getCssVariableValue('--warning-700');
|
||||
commonTokens.warningColorSuppl = getCssVariableValue('--warning-800');
|
||||
|
||||
commonTokens.successColor = getCssVariableValue('--success');
|
||||
commonTokens.successColorHover = getCssVariableValue('--success-600');
|
||||
commonTokens.successColorPressed = getCssVariableValue('--success-700');
|
||||
commonTokens.successColorSuppl = getCssVariableValue('--success-800');
|
||||
|
||||
commonTokens.textColorBase = getCssVariableValue('--foreground');
|
||||
|
||||
commonTokens.baseColor = getCssVariableValue('--primary-foreground');
|
||||
|
||||
commonTokens.dividerColor = commonTokens.borderColor =
|
||||
getCssVariableValue('--border');
|
||||
|
||||
commonTokens.modalColor = commonTokens.popoverColor =
|
||||
getCssVariableValue('--popover');
|
||||
|
||||
commonTokens.tableColor = commonTokens.cardColor =
|
||||
getCssVariableValue('--card');
|
||||
|
||||
commonTokens.bodyColor = getCssVariableValue('--background');
|
||||
commonTokens.invertedColor = getCssVariableValue('--background-deep');
|
||||
|
||||
commonTokens.borderRadius = getCssVariableValue('--radius', false);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
return {
|
||||
commonTokens,
|
||||
};
|
||||
}
|
||||
|
||||
export function useElementPlusDesignTokens() {
|
||||
const { isDark } = usePreferences();
|
||||
const rootStyles = getComputedStyle(document.documentElement);
|
||||
|
||||
const getCssVariableValueRaw = (variable: string) => {
|
||||
return rootStyles.getPropertyValue(variable);
|
||||
};
|
||||
|
||||
const getCssVariableValue = (variable: string, isColor: boolean = true) => {
|
||||
const value = getCssVariableValueRaw(variable);
|
||||
return isColor ? convertToRgb(`hsl(${value})`) : value;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => preferences.theme,
|
||||
() => {
|
||||
const background = getCssVariableValue('--background');
|
||||
const border = getCssVariableValue('--border');
|
||||
const accent = getCssVariableValue('--accent');
|
||||
|
||||
const variables: Record<string, string> = {
|
||||
'--el-bg-color': background,
|
||||
'--el-bg-color-overlay': getCssVariableValue('--popover'),
|
||||
'--el-bg-color-page': getCssVariableValue('--background-deep'),
|
||||
'--el-border-color': border,
|
||||
'--el-border-color-dark': border,
|
||||
'--el-border-color-extra-light': border,
|
||||
'--el-border-color-hover': accent,
|
||||
'--el-border-color-light': border,
|
||||
'--el-border-color-lighter': border,
|
||||
|
||||
'--el-border-radius-base': getCssVariableValue('--radius', false),
|
||||
'--el-color-danger': getCssVariableValue('--destructive-500'),
|
||||
'--el-color-danger-dark-2': isDark.value
|
||||
? getCssVariableValue('--destructive-400')
|
||||
: getCssVariableValue('--destructive-600'),
|
||||
'--el-color-danger-light-3': isDark.value
|
||||
? getCssVariableValue('--destructive-600')
|
||||
: getCssVariableValue('--destructive-400'),
|
||||
'--el-color-danger-light-5': isDark.value
|
||||
? getCssVariableValue('--destructive-700')
|
||||
: getCssVariableValue('--destructive-300'),
|
||||
'--el-color-danger-light-7': isDark.value
|
||||
? getCssVariableValue('--destructive-800')
|
||||
: getCssVariableValue('--destructive-200'),
|
||||
'--el-color-danger-light-8': isDark.value
|
||||
? getCssVariableValue('--destructive-900')
|
||||
: getCssVariableValue('--destructive-100'),
|
||||
'--el-color-danger-light-9': isDark.value
|
||||
? getCssVariableValue('--destructive-950')
|
||||
: getCssVariableValue('--destructive-50'),
|
||||
|
||||
'--el-color-error': getCssVariableValue('--destructive-500'),
|
||||
'--el-color-error-dark-2': isDark.value
|
||||
? getCssVariableValue('--destructive-400')
|
||||
: getCssVariableValue('--destructive-600'),
|
||||
'--el-color-error-light-3': isDark.value
|
||||
? getCssVariableValue('--destructive-600')
|
||||
: getCssVariableValue('--destructive-400'),
|
||||
'--el-color-error-light-5': isDark.value
|
||||
? getCssVariableValue('--destructive-700')
|
||||
: getCssVariableValue('--destructive-300'),
|
||||
'--el-color-error-light-7': isDark.value
|
||||
? getCssVariableValue('--destructive-800')
|
||||
: getCssVariableValue('--destructive-200'),
|
||||
'--el-color-error-light-8': isDark.value
|
||||
? getCssVariableValue('--destructive-900')
|
||||
: getCssVariableValue('--destructive-100'),
|
||||
'--el-color-error-light-9': isDark.value
|
||||
? getCssVariableValue('--destructive-950')
|
||||
: getCssVariableValue('--destructive-50'),
|
||||
|
||||
'--el-color-info-light-5': border,
|
||||
'--el-color-info-light-8': border,
|
||||
'--el-color-info-light-9': getCssVariableValue('--info'), // getCssVariableValue('--secondary'),
|
||||
|
||||
'--el-color-primary': getCssVariableValue('--primary-500'),
|
||||
'--el-color-primary-dark-2': isDark.value
|
||||
? getCssVariableValue('--primary-400')
|
||||
: getCssVariableValue('--primary-600'),
|
||||
'--el-color-primary-light-3': isDark.value
|
||||
? getCssVariableValue('--primary-600')
|
||||
: getCssVariableValue('--primary-400'),
|
||||
'--el-color-primary-light-5': isDark.value
|
||||
? getCssVariableValue('--primary-700')
|
||||
: getCssVariableValue('--primary-300'),
|
||||
'--el-color-primary-light-7': isDark.value
|
||||
? getCssVariableValue('--primary-800')
|
||||
: getCssVariableValue('--primary-200'),
|
||||
'--el-color-primary-light-8': isDark.value
|
||||
? getCssVariableValue('--primary-900')
|
||||
: getCssVariableValue('--primary-100'),
|
||||
'--el-color-primary-light-9': isDark.value
|
||||
? getCssVariableValue('--primary-950')
|
||||
: getCssVariableValue('--primary-50'),
|
||||
|
||||
'--el-color-success': getCssVariableValue('--success-500'),
|
||||
'--el-color-success-dark-2': isDark.value
|
||||
? getCssVariableValue('--success-400')
|
||||
: getCssVariableValue('--success-600'),
|
||||
'--el-color-success-light-3': isDark.value
|
||||
? getCssVariableValue('--success-600')
|
||||
: getCssVariableValue('--success-400'),
|
||||
'--el-color-success-light-5': isDark.value
|
||||
? getCssVariableValue('--success-700')
|
||||
: getCssVariableValue('--success-300'),
|
||||
'--el-color-success-light-7': isDark.value
|
||||
? getCssVariableValue('--success-800')
|
||||
: getCssVariableValue('--success-200'),
|
||||
'--el-color-success-light-8': isDark.value
|
||||
? getCssVariableValue('--success-900')
|
||||
: getCssVariableValue('--success-100'),
|
||||
'--el-color-success-light-9': isDark.value
|
||||
? getCssVariableValue('--success-950')
|
||||
: getCssVariableValue('--success-50'),
|
||||
|
||||
'--el-color-warning': getCssVariableValue('--warning-500'),
|
||||
'--el-color-warning-dark-2': isDark.value
|
||||
? getCssVariableValue('--warning-400')
|
||||
: getCssVariableValue('--warning-600'),
|
||||
'--el-color-warning-light-3': isDark.value
|
||||
? getCssVariableValue('--warning-600')
|
||||
: getCssVariableValue('--warning-400'),
|
||||
'--el-color-warning-light-5': isDark.value
|
||||
? getCssVariableValue('--warning-700')
|
||||
: getCssVariableValue('--warning-300'),
|
||||
'--el-color-warning-light-7': isDark.value
|
||||
? getCssVariableValue('--warning-800')
|
||||
: getCssVariableValue('--warning-200'),
|
||||
'--el-color-warning-light-8': isDark.value
|
||||
? getCssVariableValue('--warning-900')
|
||||
: getCssVariableValue('--warning-100'),
|
||||
'--el-color-warning-light-9': isDark.value
|
||||
? getCssVariableValue('--warning-950')
|
||||
: getCssVariableValue('--warning-50'),
|
||||
|
||||
'--el-fill-color': getCssVariableValue('--accent'),
|
||||
'--el-fill-color-blank': background,
|
||||
'--el-fill-color-light': getCssVariableValue('--accent'),
|
||||
'--el-fill-color-lighter': getCssVariableValue('--accent-lighter'),
|
||||
|
||||
'--el-fill-color-dark': getCssVariableValue('--accent-dark'),
|
||||
'--el-fill-color-darker': getCssVariableValue('--accent-darker'),
|
||||
|
||||
// 解决ElLoading背景色问题
|
||||
'--el-mask-color': isDark.value
|
||||
? 'rgba(0,0,0,.8)'
|
||||
: 'rgba(255,255,255,.9)',
|
||||
|
||||
'--el-text-color-primary': getCssVariableValue('--foreground'),
|
||||
|
||||
'--el-text-color-regular': getCssVariableValue('--foreground'),
|
||||
};
|
||||
|
||||
updateCSSVariables(variables, `__vben_design_styles__`);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
}
|
68
packages/effects/hooks/src/use-hover-toggle.ts
Normal file
68
packages/effects/hooks/src/use-hover-toggle.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import type { Arrayable, MaybeElementRef } from '@vueuse/core';
|
||||
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import { computed, onUnmounted, ref, unref, watch } from 'vue';
|
||||
|
||||
import { isFunction } from '@vben/utils';
|
||||
|
||||
import { useElementHover } from '@vueuse/core';
|
||||
|
||||
/**
|
||||
* 监测鼠标是否在元素内部,如果在元素内部则返回 true,否则返回 false
|
||||
* @param refElement 所有需要检测的元素。如果提供了一个数组,那么鼠标在任何一个元素内部都会返回 true
|
||||
* @param delay 延迟更新状态的时间
|
||||
* @returns 返回一个数组,第一个元素是一个 ref,表示鼠标是否在元素内部,第二个元素是一个控制器,可以通过 enable 和 disable 方法来控制监听器的启用和禁用
|
||||
*/
|
||||
export function useHoverToggle(
|
||||
refElement: Arrayable<MaybeElementRef>,
|
||||
delay: (() => number) | number = 500,
|
||||
) {
|
||||
const isHovers: Array<Ref<boolean>> = [];
|
||||
const value = ref(false);
|
||||
const timer = ref<ReturnType<typeof setTimeout> | undefined>();
|
||||
const refs = Array.isArray(refElement) ? refElement : [refElement];
|
||||
refs.forEach((refEle) => {
|
||||
const eleRef = computed(() => {
|
||||
const ele = unref(refEle);
|
||||
return ele instanceof Element ? ele : (ele?.$el as Element);
|
||||
});
|
||||
const isHover = useElementHover(eleRef);
|
||||
isHovers.push(isHover);
|
||||
});
|
||||
const isOutsideAll = computed(() => isHovers.every((v) => !v.value));
|
||||
|
||||
function setValueDelay(val: boolean) {
|
||||
timer.value && clearTimeout(timer.value);
|
||||
timer.value = setTimeout(
|
||||
() => {
|
||||
value.value = val;
|
||||
timer.value = undefined;
|
||||
},
|
||||
isFunction(delay) ? delay() : delay,
|
||||
);
|
||||
}
|
||||
|
||||
const watcher = watch(
|
||||
isOutsideAll,
|
||||
(val) => {
|
||||
setValueDelay(!val);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const controller = {
|
||||
enable() {
|
||||
watcher.resume();
|
||||
},
|
||||
disable() {
|
||||
watcher.pause();
|
||||
},
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
timer.value && clearTimeout(timer.value);
|
||||
});
|
||||
|
||||
return [value, controller] as [typeof value, typeof controller];
|
||||
}
|
58
packages/effects/hooks/src/use-pagination.ts
Normal file
58
packages/effects/hooks/src/use-pagination.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import { computed, ref, unref } from 'vue';
|
||||
|
||||
/**
|
||||
* Paginates an array of items
|
||||
* @param list The array to paginate
|
||||
* @param pageNo The current page number (1-based)
|
||||
* @param pageSize Number of items per page
|
||||
* @returns Paginated array slice
|
||||
* @throws {Error} If pageNo or pageSize are invalid
|
||||
*/
|
||||
function pagination<T = any>(list: T[], pageNo: number, pageSize: number): T[] {
|
||||
if (pageNo < 1) throw new Error('Page number must be positive');
|
||||
if (pageSize < 1) throw new Error('Page size must be positive');
|
||||
|
||||
const offset = (pageNo - 1) * Number(pageSize);
|
||||
const ret =
|
||||
offset + pageSize >= list.length
|
||||
? list.slice(offset)
|
||||
: list.slice(offset, offset + pageSize);
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function usePagination<T = any>(list: Ref<T[]>, pageSize: number) {
|
||||
const currentPage = ref(1);
|
||||
const pageSizeRef = ref(pageSize);
|
||||
|
||||
const totalPages = computed(() =>
|
||||
Math.ceil(unref(list).length / unref(pageSizeRef)),
|
||||
);
|
||||
|
||||
const paginationList = computed(() => {
|
||||
return pagination(unref(list), unref(currentPage), unref(pageSizeRef));
|
||||
});
|
||||
|
||||
const total = computed(() => {
|
||||
return unref(list).length;
|
||||
});
|
||||
|
||||
function setCurrentPage(page: number) {
|
||||
if (page < 1 || page > unref(totalPages)) {
|
||||
throw new Error('Invalid page number');
|
||||
}
|
||||
currentPage.value = page;
|
||||
}
|
||||
|
||||
function setPageSize(pageSize: number) {
|
||||
if (pageSize < 1) {
|
||||
throw new Error('Page size must be positive');
|
||||
}
|
||||
pageSizeRef.value = pageSize;
|
||||
// Reset to first page to prevent invalid state
|
||||
currentPage.value = 1;
|
||||
}
|
||||
|
||||
return { setCurrentPage, total, setPageSize, paginationList };
|
||||
}
|
16
packages/effects/hooks/src/use-refresh.ts
Normal file
16
packages/effects/hooks/src/use-refresh.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { useTabbarStore } from '@vben/stores';
|
||||
|
||||
export function useRefresh() {
|
||||
const router = useRouter();
|
||||
const tabbarStore = useTabbarStore();
|
||||
|
||||
async function refresh() {
|
||||
await tabbarStore.refresh(router);
|
||||
}
|
||||
|
||||
return {
|
||||
refresh,
|
||||
};
|
||||
}
|
132
packages/effects/hooks/src/use-tabs.ts
Normal file
132
packages/effects/hooks/src/use-tabs.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import type { ComputedRef } from 'vue';
|
||||
import type { RouteLocationNormalized } from 'vue-router';
|
||||
|
||||
import { useTabbarStore } from '@vben/stores';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
export function useTabs() {
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const tabbarStore = useTabbarStore();
|
||||
|
||||
async function closeLeftTabs(tab?: RouteLocationNormalized) {
|
||||
await tabbarStore.closeLeftTabs(tab || route);
|
||||
}
|
||||
|
||||
async function closeAllTabs() {
|
||||
await tabbarStore.closeAllTabs(router);
|
||||
}
|
||||
|
||||
async function closeRightTabs(tab?: RouteLocationNormalized) {
|
||||
await tabbarStore.closeRightTabs(tab || route);
|
||||
}
|
||||
|
||||
async function closeOtherTabs(tab?: RouteLocationNormalized) {
|
||||
await tabbarStore.closeOtherTabs(tab || route);
|
||||
}
|
||||
|
||||
async function closeCurrentTab(tab?: RouteLocationNormalized) {
|
||||
await tabbarStore.closeTab(tab || route, router);
|
||||
}
|
||||
|
||||
async function pinTab(tab?: RouteLocationNormalized) {
|
||||
await tabbarStore.pinTab(tab || route);
|
||||
}
|
||||
|
||||
async function unpinTab(tab?: RouteLocationNormalized) {
|
||||
await tabbarStore.unpinTab(tab || route);
|
||||
}
|
||||
|
||||
async function toggleTabPin(tab?: RouteLocationNormalized) {
|
||||
await tabbarStore.toggleTabPin(tab || route);
|
||||
}
|
||||
|
||||
async function refreshTab(name?: string) {
|
||||
await tabbarStore.refresh(name || router);
|
||||
}
|
||||
|
||||
async function openTabInNewWindow(tab?: RouteLocationNormalized) {
|
||||
await tabbarStore.openTabInNewWindow(tab || route);
|
||||
}
|
||||
|
||||
async function closeTabByKey(key: string) {
|
||||
await tabbarStore.closeTabByKey(key, router);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前标签页的标题
|
||||
*
|
||||
* @description 支持设置静态标题字符串或动态计算标题
|
||||
* @description 动态标题会在每次渲染时重新计算,适用于多语言或状态相关的标题
|
||||
*
|
||||
* @param title - 标题内容
|
||||
* - 静态标题: 直接传入字符串
|
||||
* - 动态标题: 传入 ComputedRef
|
||||
*
|
||||
* @example
|
||||
* // 静态标题
|
||||
* setTabTitle('标签页')
|
||||
*
|
||||
* // 动态标题(多语言)
|
||||
* setTabTitle(computed(() => t('page.title')))
|
||||
*/
|
||||
async function setTabTitle(title: ComputedRef<string> | string) {
|
||||
tabbarStore.setUpdateTime();
|
||||
await tabbarStore.setTabTitle(route, title);
|
||||
}
|
||||
|
||||
async function resetTabTitle() {
|
||||
tabbarStore.setUpdateTime();
|
||||
await tabbarStore.resetTabTitle(route);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取操作是否禁用
|
||||
* @param tab
|
||||
*/
|
||||
function getTabDisableState(tab: RouteLocationNormalized = route) {
|
||||
const tabs = tabbarStore.getTabs;
|
||||
const affixTabs = tabbarStore.affixTabs;
|
||||
const index = tabs.findIndex((item) => item.path === tab.path);
|
||||
|
||||
const disabled = tabs.length <= 1;
|
||||
|
||||
const { meta } = tab;
|
||||
const affixTab = meta?.affixTab ?? false;
|
||||
const isCurrentTab = route.path === tab.path;
|
||||
|
||||
// 当前处于最左侧或者减去固定标签页的数量等于0
|
||||
const disabledCloseLeft =
|
||||
index === 0 || index - affixTabs.length <= 0 || !isCurrentTab;
|
||||
|
||||
const disabledCloseRight = !isCurrentTab || index === tabs.length - 1;
|
||||
|
||||
const disabledCloseOther =
|
||||
disabled || !isCurrentTab || tabs.length - affixTabs.length <= 1;
|
||||
return {
|
||||
disabledCloseAll: disabled,
|
||||
disabledCloseCurrent: !!affixTab || disabled,
|
||||
disabledCloseLeft,
|
||||
disabledCloseOther,
|
||||
disabledCloseRight,
|
||||
disabledRefresh: !isCurrentTab,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
closeAllTabs,
|
||||
closeCurrentTab,
|
||||
closeLeftTabs,
|
||||
closeOtherTabs,
|
||||
closeRightTabs,
|
||||
closeTabByKey,
|
||||
getTabDisableState,
|
||||
openTabInNewWindow,
|
||||
pinTab,
|
||||
refreshTab,
|
||||
resetTabTitle,
|
||||
setTabTitle,
|
||||
toggleTabPin,
|
||||
unpinTab,
|
||||
};
|
||||
}
|
84
packages/effects/hooks/src/use-watermark.ts
Normal file
84
packages/effects/hooks/src/use-watermark.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import type { Watermark, WatermarkOptions } from 'watermark-js-plus';
|
||||
|
||||
import { nextTick, onUnmounted, readonly, ref } from 'vue';
|
||||
|
||||
const watermark = ref<Watermark>();
|
||||
const unmountedHooked = ref<boolean>(false);
|
||||
const cachedOptions = ref<Partial<WatermarkOptions>>({
|
||||
advancedStyle: {
|
||||
colorStops: [
|
||||
{
|
||||
color: 'gray',
|
||||
offset: 0,
|
||||
},
|
||||
{
|
||||
color: 'gray',
|
||||
offset: 1,
|
||||
},
|
||||
],
|
||||
type: 'linear',
|
||||
},
|
||||
// fontSize: '20px',
|
||||
content: '',
|
||||
contentType: 'multi-line-text',
|
||||
globalAlpha: 0.25,
|
||||
gridLayoutOptions: {
|
||||
cols: 2,
|
||||
gap: [20, 20],
|
||||
matrix: [
|
||||
[1, 0],
|
||||
[0, 1],
|
||||
],
|
||||
rows: 2,
|
||||
},
|
||||
height: 200,
|
||||
layout: 'grid',
|
||||
rotate: 30,
|
||||
width: 160,
|
||||
});
|
||||
|
||||
export function useWatermark() {
|
||||
async function initWatermark(options: Partial<WatermarkOptions>) {
|
||||
const { Watermark } = await import('watermark-js-plus');
|
||||
|
||||
cachedOptions.value = {
|
||||
...cachedOptions.value,
|
||||
...options,
|
||||
};
|
||||
watermark.value = new Watermark(cachedOptions.value);
|
||||
await watermark.value?.create();
|
||||
}
|
||||
|
||||
async function updateWatermark(options: Partial<WatermarkOptions>) {
|
||||
if (watermark.value) {
|
||||
await nextTick();
|
||||
await watermark.value?.changeOptions({
|
||||
...cachedOptions.value,
|
||||
...options,
|
||||
});
|
||||
} else {
|
||||
await initWatermark(options);
|
||||
}
|
||||
}
|
||||
|
||||
function destroyWatermark() {
|
||||
if (watermark.value) {
|
||||
watermark.value.destroy();
|
||||
watermark.value = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// 只在第一次调用时注册卸载钩子,防止重复注册以致于在路由切换时销毁了水印
|
||||
if (!unmountedHooked.value) {
|
||||
unmountedHooked.value = true;
|
||||
onUnmounted(() => {
|
||||
destroyWatermark();
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
destroyWatermark,
|
||||
updateWatermark,
|
||||
watermark: readonly(watermark),
|
||||
};
|
||||
}
|
9
packages/effects/hooks/tsconfig.json
Normal file
9
packages/effects/hooks/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/tsconfig/web.json",
|
||||
"compilerOptions": {
|
||||
"types": ["vite/client", "@vben/types/global"]
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
Reference in New Issue
Block a user