🔱: [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:
GitHub Actions Bot
2025-03-03 19:24:51 +00:00
parent de26ee9383
commit 335d175d57
649 changed files with 36984 additions and 826 deletions
@@ -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)
};
}