mirror of
https://github.com/certd/certd.git
synced 2026-05-17 05:37:30 +08:00
🔱: [client] sync upgrade with 7 commits [trident-sync]
chore: Merge branch 'vben' # Conflicts: # package.json perf: antdv示例改成使用vben框架 chore: vben chore: vben chore: vben
This commit is contained in:
@@ -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 "../composables";
|
||||
@@ -0,0 +1,15 @@
|
||||
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 } = config;
|
||||
|
||||
return {
|
||||
apiURL: VITE_GLOB_API_URL
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
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": getCssVariableValue("--destructive"),
|
||||
"--el-color-danger-light-3": getCssVariableValue("--destructive-400"),
|
||||
"--el-color-danger-light-5": getCssVariableValue("--destructive-300"),
|
||||
"--el-color-danger-light-7": getCssVariableValue("--destructive-200"),
|
||||
"--el-color-danger-light-8": isDark.value ? border : getCssVariableValue("--destructive-100"),
|
||||
"--el-color-danger-light-9": isDark.value ? accent : getCssVariableValue("--destructive-50"),
|
||||
|
||||
"--el-color-error": getCssVariableValue("--destructive-500"),
|
||||
"--el-color-error-dark-2": getCssVariableValue("--destructive"),
|
||||
"--el-color-error-light-3": getCssVariableValue("--destructive-400"),
|
||||
"--el-color-error-light-5": getCssVariableValue("--destructive-300"),
|
||||
"--el-color-error-light-7": getCssVariableValue("--destructive-200"),
|
||||
"--el-color-error-light-8": isDark.value ? border : getCssVariableValue("--destructive-100"),
|
||||
"--el-color-error-light-9": isDark.value ? accent : getCssVariableValue("--destructive-50"),
|
||||
|
||||
"--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": getCssVariableValue("--primary"),
|
||||
"--el-color-primary-light-3": getCssVariableValue("--primary-400"),
|
||||
"--el-color-primary-light-5": getCssVariableValue("--primary-300"),
|
||||
"--el-color-primary-light-7": isDark.value ? border : getCssVariableValue("--primary-200"),
|
||||
"--el-color-primary-light-8": isDark.value ? border : getCssVariableValue("--primary-100"),
|
||||
"--el-color-primary-light-9": isDark.value ? accent : getCssVariableValue("--primary-50"),
|
||||
|
||||
"--el-color-success": getCssVariableValue("--success-500"),
|
||||
"--el-color-success-dark-2": getCssVariableValue("--success"),
|
||||
"--el-color-success-light-3": getCssVariableValue("--success-400"),
|
||||
"--el-color-success-light-5": getCssVariableValue("--success-300"),
|
||||
"--el-color-success-light-7": getCssVariableValue("--success-200"),
|
||||
"--el-color-success-light-8": isDark.value ? border : getCssVariableValue("--success-100"),
|
||||
"--el-color-success-light-9": isDark.value ? accent : getCssVariableValue("--success-50"),
|
||||
|
||||
"--el-color-warning": getCssVariableValue("--warning-500"),
|
||||
"--el-color-warning-dark-2": getCssVariableValue("--warning"),
|
||||
"--el-color-warning-light-3": getCssVariableValue("--warning-400"),
|
||||
"--el-color-warning-light-5": getCssVariableValue("--warning-300"),
|
||||
"--el-color-warning-light-7": getCssVariableValue("--warning-200"),
|
||||
"--el-color-warning-light-8": isDark.value ? border : getCssVariableValue("--warning-100"),
|
||||
"--el-color-warning-light-9": isDark.value ? accent : 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 }
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
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];
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
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 };
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
import { useTabbarStore } from "../stores";
|
||||
|
||||
export function useRefresh() {
|
||||
const router = useRouter();
|
||||
const tabbarStore = useTabbarStore();
|
||||
|
||||
async function refresh() {
|
||||
await tabbarStore.refresh(router);
|
||||
}
|
||||
|
||||
return {
|
||||
refresh
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import type { RouteLocationNormalized } from "vue-router";
|
||||
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
|
||||
import { useTabbarStore } from "../stores";
|
||||
|
||||
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() {
|
||||
await tabbarStore.refresh(router);
|
||||
}
|
||||
|
||||
async function openTabInNewWindow(tab?: RouteLocationNormalized) {
|
||||
await tabbarStore.openTabInNewWindow(tab || route);
|
||||
}
|
||||
|
||||
async function closeTabByKey(key: string) {
|
||||
await tabbarStore.closeTabByKey(key, router);
|
||||
}
|
||||
|
||||
async function setTabTitle(title: 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: any) => 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
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import type { Watermark, WatermarkOptions } from "watermark-js-plus";
|
||||
|
||||
import { nextTick, onUnmounted, readonly, ref } from "vue";
|
||||
|
||||
import { updatePreferences } from "/@/vben/preferences";
|
||||
|
||||
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);
|
||||
updatePreferences({ app: { watermark: true } });
|
||||
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;
|
||||
}
|
||||
updatePreferences({ app: { watermark: false } });
|
||||
}
|
||||
|
||||
// 只在第一次调用时注册卸载钩子,防止重复注册以致于在路由切换时销毁了水印
|
||||
if (!unmountedHooked.value) {
|
||||
unmountedHooked.value = true;
|
||||
onUnmounted(() => {
|
||||
destroyWatermark();
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
destroyWatermark,
|
||||
updateWatermark,
|
||||
watermark: readonly(watermark)
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user