🔱: [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,48 @@
import type { SortableOptions } from 'sortablejs';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { useSortable } from '../use-sortable';
describe('useSortable', () => {
beforeEach(() => {
vi.mock('sortablejs/modular/sortable.complete.esm.js', () => ({
default: {
create: vi.fn(),
},
}));
});
it('should call Sortable.create with the correct options', async () => {
// Create a mock element
const mockElement = document.createElement('div') as HTMLDivElement;
// Define custom options
const customOptions: SortableOptions = {
group: 'test-group',
sort: false,
};
// Use the useSortable function
const { initializeSortable } = useSortable(mockElement, customOptions);
// Initialize sortable
await initializeSortable();
// Import sortablejs to access the mocked create function
const Sortable = await import(
'sortablejs/modular/sortable.complete.esm.js'
);
// Verify that Sortable.create was called with the correct parameters
expect(Sortable.default.create).toHaveBeenCalledTimes(1);
expect(Sortable.default.create).toHaveBeenCalledWith(
mockElement,
expect.objectContaining({
animation: 300,
delay: 400,
delayOnTouchOnly: true,
...customOptions,
}),
);
});
});
@@ -0,0 +1,8 @@
export * from "./use-is-mobile";
export * from "./use-layout-style";
export * from "./use-namespace";
export * from "./use-priority-value";
export * from "./use-scroll-lock";
export * from "./use-simple-locale";
export * from "./use-sortable";
export { useEmitAsProps, useForwardExpose, useForwardProps, useForwardPropsEmits } from "radix-vue";
@@ -0,0 +1,7 @@
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core";
export function useIsMobile() {
const breakpoints = useBreakpoints(breakpointsTailwind);
const isMobile = breakpoints.smaller("md");
return { isMobile };
}
@@ -0,0 +1,79 @@
import type { CSSProperties } from "vue";
import type { VisibleDomRect } from "/@/vben/shared/utils";
import { computed, onMounted, onUnmounted, ref } from "vue";
import { CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT, CSS_VARIABLE_LAYOUT_CONTENT_WIDTH, CSS_VARIABLE_LAYOUT_FOOTER_HEIGHT, CSS_VARIABLE_LAYOUT_HEADER_HEIGHT } from "/@/vben/shared/constants";
import { getElementVisibleRect } from "/@/vben/shared/utils";
import { useCssVar, useDebounceFn } from "@vueuse/core";
/**
* @zh_CN content style
*/
export function useLayoutContentStyle() {
let resizeObserver: null | ResizeObserver = null;
const contentElement = ref<HTMLDivElement | null>(null);
const visibleDomRect = ref<null | VisibleDomRect>(null);
const contentHeight = useCssVar(CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT);
const contentWidth = useCssVar(CSS_VARIABLE_LAYOUT_CONTENT_WIDTH);
const overlayStyle = computed((): CSSProperties => {
const { height, left, top, width } = visibleDomRect.value ?? {};
return {
height: `${height}px`,
left: `${left}px`,
position: "fixed",
top: `${top}px`,
width: `${width}px`,
zIndex: 150
};
});
const debouncedCalcHeight = useDebounceFn((_entries: ResizeObserverEntry[]) => {
visibleDomRect.value = getElementVisibleRect(contentElement.value);
contentHeight.value = `${visibleDomRect.value.height}px`;
contentWidth.value = `${visibleDomRect.value.width}px`;
}, 16);
onMounted(() => {
if (contentElement.value && !resizeObserver) {
resizeObserver = new ResizeObserver(debouncedCalcHeight);
resizeObserver.observe(contentElement.value);
}
});
onUnmounted(() => {
resizeObserver?.disconnect();
resizeObserver = null;
});
return { contentElement, overlayStyle, visibleDomRect };
}
export function useLayoutHeaderStyle() {
const headerHeight = useCssVar(CSS_VARIABLE_LAYOUT_HEADER_HEIGHT);
return {
getLayoutHeaderHeight: () => {
return Number.parseInt(`${headerHeight.value}`, 10);
},
setLayoutHeaderHeight: (height: number) => {
headerHeight.value = `${height}px`;
}
};
}
export function useLayoutFooterStyle() {
const footerHeight = useCssVar(CSS_VARIABLE_LAYOUT_FOOTER_HEIGHT);
return {
getLayoutFooterHeight: () => {
return Number.parseInt(`${footerHeight.value}`, 10);
},
setLayoutFooterHeight: (height: number) => {
footerHeight.value = `${height}px`;
}
};
}
@@ -0,0 +1,88 @@
import { DEFAULT_NAMESPACE } from "../shared/constants";
/**
* @see copy https://github.com/element-plus/element-plus/blob/dev/packages/hooks/use-namespace/index.ts
*/
const statePrefix = "is-";
const _bem = (namespace: string, block: string, blockSuffix: string, element: string, modifier: string) => {
let cls = `${namespace}-${block}`;
if (blockSuffix) {
cls += `-${blockSuffix}`;
}
if (element) {
cls += `__${element}`;
}
if (modifier) {
cls += `--${modifier}`;
}
return cls;
};
const is: {
(name: string): string;
// eslint-disable-next-line @typescript-eslint/unified-signatures
(name: string, state: boolean | undefined): string;
} = (name: string, ...args: [] | [boolean | undefined]) => {
const state = args.length > 0 ? args[0] : true;
return name && state ? `${statePrefix}${name}` : "";
};
const useNamespace = (block: string) => {
const namespace = DEFAULT_NAMESPACE;
const b = (blockSuffix = "") => _bem(namespace, block, blockSuffix, "", "");
const e = (element?: string) => (element ? _bem(namespace, block, "", element, "") : "");
const m = (modifier?: string) => (modifier ? _bem(namespace, block, "", "", modifier) : "");
const be = (blockSuffix?: string, element?: string) => (blockSuffix && element ? _bem(namespace, block, blockSuffix, element, "") : "");
const em = (element?: string, modifier?: string) => (element && modifier ? _bem(namespace, block, "", element, modifier) : "");
const bm = (blockSuffix?: string, modifier?: string) => (blockSuffix && modifier ? _bem(namespace, block, blockSuffix, "", modifier) : "");
const bem = (blockSuffix?: string, element?: string, modifier?: string) => (blockSuffix && element && modifier ? _bem(namespace, block, blockSuffix, element, modifier) : "");
// for css var
// --el-xxx: value;
const cssVar = (object: Record<string, string>) => {
const styles: Record<string, string> = {};
for (const key in object) {
if (object[key]) {
styles[`--${namespace}-${key}`] = object[key];
}
}
return styles;
};
// with block
const cssVarBlock = (object: Record<string, string>) => {
const styles: Record<string, string> = {};
for (const key in object) {
if (object[key]) {
styles[`--${namespace}-${block}-${key}`] = object[key];
}
}
return styles;
};
const cssVarName = (name: string) => `--${namespace}-${name}`;
const cssVarBlockName = (name: string) => `--${namespace}-${block}-${name}`;
return {
b,
be,
bem,
bm,
// css
cssVar,
cssVarBlock,
cssVarBlockName,
cssVarName,
e,
em,
is,
m,
namespace
};
};
type UseNamespaceReturn = ReturnType<typeof useNamespace>;
export type { UseNamespaceReturn };
export { useNamespace };
@@ -0,0 +1,71 @@
import type { ComputedRef, Ref } from "vue";
import { computed, getCurrentInstance, unref, useAttrs, useSlots } from "vue";
import { getFirstNonNullOrUndefined, kebabToCamelCase } from "../shared/utils";
/**
* 依次从插槽、attrs、props、state 中获取值
* @param key
* @param props
* @param state
*/
export function usePriorityValue<T extends Record<string, any>, S extends Record<string, any>, K extends keyof T = keyof T>(key: K, props: T, state: Readonly<Ref<NoInfer<S>>> | undefined) {
const instance = getCurrentInstance();
const slots = useSlots();
const attrs = useAttrs() as T;
const value = computed((): T[K] => {
// props不管有没有传,都会有默认值,会影响这里的顺序,
// 通过判断原始props是否有值来判断是否传入
const rawProps = (instance?.vnode?.props || {}) as T;
const standardRawProps = {} as T;
for (const [key, value] of Object.entries(rawProps)) {
standardRawProps[kebabToCamelCase(key) as K] = value;
}
const propsKey = standardRawProps?.[key] === undefined ? undefined : props[key];
// slot可以关闭
return getFirstNonNullOrUndefined(slots[key as string], attrs[key], propsKey, state?.value?.[key as keyof S]) as T[K];
});
return value;
}
/**
* 批量获取state中的值(每个值都是ref)
* @param props
* @param state
*/
export function usePriorityValues<T extends Record<string, any>, S extends Ref<Record<string, any>> = Readonly<Ref<NoInfer<T>, NoInfer<T>>>>(props: T, state: S | undefined) {
const result: { [K in keyof T]: ComputedRef<T[K]> } = {} as never;
(Object.keys(props) as (keyof T)[]).forEach((key) => {
result[key] = usePriorityValue(key as keyof typeof props, props, state);
});
return result;
}
/**
* 批量获取state中的值(集中在一个computed,用于透传)
* @param props
* @param state
*/
export function useForwardPriorityValues<T extends Record<string, any>, S extends Ref<Record<string, any>> = Readonly<Ref<NoInfer<T>, NoInfer<T>>>>(props: T, state: S | undefined) {
const computedResult: { [K in keyof T]: ComputedRef<T[K]> } = {} as never;
(Object.keys(props) as (keyof T)[]).forEach((key) => {
computedResult[key] = usePriorityValue(key as keyof typeof props, props, state);
});
return computed(() => {
const unwrapResult: Record<string, any> = {};
Object.keys(props).forEach((key) => {
unwrapResult[key] = unref(computedResult[key]);
});
return unwrapResult as { [K in keyof T]: T[K] };
});
}
@@ -0,0 +1,46 @@
import { getScrollbarWidth, needsScrollbar } from "../shared/utils";
import { useScrollLock as _useScrollLock, tryOnBeforeUnmount, tryOnMounted } from "@vueuse/core";
export const SCROLL_FIXED_CLASS = `_scroll__fixed_`;
export function useScrollLock() {
const isLocked = _useScrollLock(document.body);
const scrollbarWidth = getScrollbarWidth();
tryOnMounted(() => {
if (!needsScrollbar()) {
return;
}
document.body.style.paddingRight = `${scrollbarWidth}px`;
const layoutFixedNodes = document.querySelectorAll<HTMLElement>(`.${SCROLL_FIXED_CLASS}`);
const nodes = [...layoutFixedNodes];
if (nodes.length > 0) {
nodes.forEach((node) => {
node.dataset.transition = node.style.transition;
node.style.transition = "none";
node.style.paddingRight = `${scrollbarWidth}px`;
});
}
isLocked.value = true;
});
tryOnBeforeUnmount(() => {
if (!needsScrollbar()) {
return;
}
isLocked.value = false;
const layoutFixedNodes = document.querySelectorAll<HTMLElement>(`.${SCROLL_FIXED_CLASS}`);
const nodes = [...layoutFixedNodes];
if (nodes.length > 0) {
nodes.forEach((node) => {
node.style.paddingRight = "";
requestAnimationFrame(() => {
node.style.transition = node.dataset.transition || "";
});
});
}
document.body.style.paddingRight = "";
});
}
@@ -0,0 +1,3 @@
# Simple i18n
Simple i18 implementation
@@ -0,0 +1,27 @@
import type { Locale } from "./messages";
import { computed, ref } from "vue";
import { createSharedComposable } from "@vueuse/core";
import { getMessages } from "./messages";
export const useSimpleLocale = createSharedComposable(() => {
const currentLocale = ref<Locale>("zh-CN");
const setSimpleLocale = (locale: Locale) => {
currentLocale.value = locale;
};
const $t = computed(() => {
const localeMessages = getMessages(currentLocale.value);
return (key: string) => {
return localeMessages[key] || key;
};
});
return {
$t,
currentLocale,
setSimpleLocale
};
});
@@ -0,0 +1,22 @@
export type Locale = "en-US" | "zh-CN";
export const messages: Record<Locale, Record<string, string>> = {
"en-US": {
cancel: "Cancel",
collapse: "Collapse",
confirm: "Confirm",
expand: "Expand",
reset: "Reset",
submit: "Submit"
},
"zh-CN": {
cancel: "取消",
collapse: "收起",
confirm: "确认",
expand: "展开",
reset: "重置",
submit: "提交"
}
};
export const getMessages = (locale: Locale) => messages[locale];
@@ -0,0 +1,26 @@
import type { SortableOptions } from "sortablejs";
import type Sortable from "sortablejs";
function useSortable<T extends HTMLElement>(sortableContainer: T, options: SortableOptions = {}) {
const initializeSortable = async () => {
const Sortable = await import(
// @ts-expect-error - This is a dynamic import
"sortablejs/modular/sortable.complete.esm.js"
);
const sortable = Sortable?.default?.create?.(sortableContainer, {
animation: 300,
delay: 400,
delayOnTouchOnly: true,
...options
});
return sortable as Sortable;
};
return {
initializeSortable
};
}
export { useSortable };
export type { Sortable };