feat: add dashboard page
This commit is contained in:
@@ -22,6 +22,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/vue": "^4.1.2",
|
||||
"vue": "^3.4.30"
|
||||
"vue": "^3.4.31"
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ import { Icon } from '@iconify/vue';
|
||||
|
||||
function createIcon(name: string) {
|
||||
return defineComponent({
|
||||
name: `SvgIcon-${name}`,
|
||||
setup(props, { attrs }) {
|
||||
return () => h(Icon, { icon: name, ...props, ...attrs });
|
||||
},
|
||||
|
@@ -36,7 +36,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/shared": "^3.4.30",
|
||||
"@vue/shared": "^3.4.31",
|
||||
"clsx": "2.1.1",
|
||||
"defu": "^6.1.4",
|
||||
"nprogress": "^0.2.0",
|
||||
|
140
packages/@core/shared/toolkit/src/dom.test.ts
Normal file
140
packages/@core/shared/toolkit/src/dom.test.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { getElementVisibleHeight } from './dom'; // 假设函数位于 utils.ts 中
|
||||
|
||||
describe('getElementVisibleHeight', () => {
|
||||
// Mocking the getBoundingClientRect method
|
||||
const mockGetBoundingClientRect = vi.fn();
|
||||
const originalGetBoundingClientRect = Element.prototype.getBoundingClientRect;
|
||||
|
||||
beforeAll(() => {
|
||||
// Mock getBoundingClientRect method
|
||||
Element.prototype.getBoundingClientRect = mockGetBoundingClientRect;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// Restore original getBoundingClientRect method
|
||||
Element.prototype.getBoundingClientRect = originalGetBoundingClientRect;
|
||||
});
|
||||
|
||||
it('should return 0 if the element is null or undefined', () => {
|
||||
expect(getElementVisibleHeight(null)).toBe(0);
|
||||
expect(getElementVisibleHeight()).toBe(0);
|
||||
});
|
||||
|
||||
it('should return the visible height of the element', () => {
|
||||
// Mock the getBoundingClientRect return value
|
||||
mockGetBoundingClientRect.mockReturnValue({
|
||||
bottom: 500,
|
||||
height: 400,
|
||||
left: 0,
|
||||
right: 0,
|
||||
toJSON: () => ({}),
|
||||
top: 100,
|
||||
width: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
});
|
||||
|
||||
const mockElement = document.createElement('div');
|
||||
document.body.append(mockElement);
|
||||
|
||||
// Mocking window.innerHeight and document.documentElement.clientHeight
|
||||
const originalInnerHeight = window.innerHeight;
|
||||
const originalClientHeight = document.documentElement.clientHeight;
|
||||
|
||||
Object.defineProperty(window, 'innerHeight', {
|
||||
value: 600,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
Object.defineProperty(document.documentElement, 'clientHeight', {
|
||||
value: 600,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
expect(getElementVisibleHeight(mockElement)).toBe(400);
|
||||
|
||||
// Restore original values
|
||||
Object.defineProperty(window, 'innerHeight', {
|
||||
value: originalInnerHeight,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
Object.defineProperty(document.documentElement, 'clientHeight', {
|
||||
value: originalClientHeight,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
mockElement.remove();
|
||||
});
|
||||
|
||||
it('should return the visible height when element is partially out of viewport', () => {
|
||||
// Mock the getBoundingClientRect return value
|
||||
mockGetBoundingClientRect.mockReturnValue({
|
||||
bottom: 300,
|
||||
height: 400,
|
||||
left: 0,
|
||||
right: 0,
|
||||
toJSON: () => ({}),
|
||||
top: -100,
|
||||
width: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
});
|
||||
|
||||
const mockElement = document.createElement('div');
|
||||
document.body.append(mockElement);
|
||||
|
||||
// Mocking window.innerHeight and document.documentElement.clientHeight
|
||||
const originalInnerHeight = window.innerHeight;
|
||||
const originalClientHeight = document.documentElement.clientHeight;
|
||||
|
||||
Object.defineProperty(window, 'innerHeight', {
|
||||
value: 600,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
Object.defineProperty(document.documentElement, 'clientHeight', {
|
||||
value: 600,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
expect(getElementVisibleHeight(mockElement)).toBe(300);
|
||||
|
||||
// Restore original values
|
||||
Object.defineProperty(window, 'innerHeight', {
|
||||
value: originalInnerHeight,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
Object.defineProperty(document.documentElement, 'clientHeight', {
|
||||
value: originalClientHeight,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
mockElement.remove();
|
||||
});
|
||||
|
||||
it('should return 0 if the element is completely out of viewport', () => {
|
||||
// Mock the getBoundingClientRect return value
|
||||
mockGetBoundingClientRect.mockReturnValue({
|
||||
bottom: -100,
|
||||
height: 400,
|
||||
left: 0,
|
||||
right: 0,
|
||||
toJSON: () => ({}),
|
||||
top: -500,
|
||||
width: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
});
|
||||
|
||||
const mockElement = document.createElement('div');
|
||||
document.body.append(mockElement);
|
||||
|
||||
expect(getElementVisibleHeight(mockElement)).toBe(0);
|
||||
|
||||
mockElement.remove();
|
||||
});
|
||||
});
|
24
packages/@core/shared/toolkit/src/dom.ts
Normal file
24
packages/@core/shared/toolkit/src/dom.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 获取元素可见高度
|
||||
* @param element
|
||||
* @returns
|
||||
*/
|
||||
function getElementVisibleHeight(
|
||||
element?: HTMLElement | null | undefined,
|
||||
): number {
|
||||
if (!element) {
|
||||
return 0;
|
||||
}
|
||||
const rect = element.getBoundingClientRect();
|
||||
const viewHeight = Math.max(
|
||||
document.documentElement.clientHeight,
|
||||
window.innerHeight,
|
||||
);
|
||||
|
||||
const top = Math.max(rect.top, 0);
|
||||
const bottom = Math.min(rect.bottom, viewHeight);
|
||||
|
||||
return Math.max(0, bottom - top);
|
||||
}
|
||||
|
||||
export { getElementVisibleHeight };
|
@@ -1,5 +1,6 @@
|
||||
export * from './cn';
|
||||
export * from './diff';
|
||||
export * from './dom';
|
||||
export * from './hash';
|
||||
export * from './inference';
|
||||
export * from './letter';
|
||||
@@ -7,5 +8,6 @@ export * from './merge';
|
||||
export * from './namespace';
|
||||
export * from './nprogress';
|
||||
export * from './tree';
|
||||
export * from './unique';
|
||||
export * from './update-css-variables';
|
||||
export * from './window';
|
||||
|
@@ -97,11 +97,20 @@ function isWindowsOs(): boolean {
|
||||
return windowsRegex.test(navigator.userAgent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查传入的值是否为数字
|
||||
* @param value
|
||||
*/
|
||||
function isNumber(value: any): value is number {
|
||||
return typeof value === 'number' && Number.isFinite(value);
|
||||
}
|
||||
|
||||
export {
|
||||
isEmpty,
|
||||
isFunction,
|
||||
isHttpUrl,
|
||||
isMacOs,
|
||||
isNumber,
|
||||
isObject,
|
||||
isString,
|
||||
isUndefined,
|
||||
|
61
packages/@core/shared/toolkit/src/unique.test.ts
Normal file
61
packages/@core/shared/toolkit/src/unique.test.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { uniqueByField } from './unique';
|
||||
|
||||
describe('uniqueByField', () => {
|
||||
it('should return an array with unique items based on id field', () => {
|
||||
const items = [
|
||||
{ id: 1, name: 'Item 1' },
|
||||
{ id: 2, name: 'Item 2' },
|
||||
{ id: 3, name: 'Item 3' },
|
||||
{ id: 1, name: 'Duplicate Item' },
|
||||
];
|
||||
|
||||
const uniqueItems = uniqueByField(items, 'id');
|
||||
|
||||
// Assert expected results
|
||||
expect(uniqueItems).toHaveLength(3); // After deduplication, there should be three objects left
|
||||
expect(uniqueItems).toEqual([
|
||||
{ id: 1, name: 'Item 1' },
|
||||
{ id: 2, name: 'Item 2' },
|
||||
{ id: 3, name: 'Item 3' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return an empty array when input array is empty', () => {
|
||||
const items: any[] = []; // Empty array
|
||||
|
||||
const uniqueItems = uniqueByField(items, 'id');
|
||||
|
||||
// Assert expected results
|
||||
expect(uniqueItems).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle arrays with only one item correctly', () => {
|
||||
const items = [{ id: 1, name: 'Item 1' }];
|
||||
|
||||
const uniqueItems = uniqueByField(items, 'id');
|
||||
|
||||
// Assert expected results
|
||||
expect(uniqueItems).toHaveLength(1);
|
||||
expect(uniqueItems).toEqual([{ id: 1, name: 'Item 1' }]);
|
||||
});
|
||||
|
||||
it('should preserve the order of the first occurrence of each item', () => {
|
||||
const items = [
|
||||
{ id: 2, name: 'Item 2' },
|
||||
{ id: 1, name: 'Item 1' },
|
||||
{ id: 3, name: 'Item 3' },
|
||||
{ id: 1, name: 'Duplicate Item' },
|
||||
];
|
||||
|
||||
const uniqueItems = uniqueByField(items, 'id');
|
||||
|
||||
// Assert expected results (order of first occurrences preserved)
|
||||
expect(uniqueItems).toEqual([
|
||||
{ id: 2, name: 'Item 2' },
|
||||
{ id: 1, name: 'Item 1' },
|
||||
{ id: 3, name: 'Item 3' },
|
||||
]);
|
||||
});
|
||||
});
|
15
packages/@core/shared/toolkit/src/unique.ts
Normal file
15
packages/@core/shared/toolkit/src/unique.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 根据指定字段对对象数组进行去重
|
||||
* @param arr 要去重的对象数组
|
||||
* @param key 去重依据的字段名
|
||||
* @returns 去重后的对象数组
|
||||
*/
|
||||
function uniqueByField<T>(arr: T[], key: keyof T): T[] {
|
||||
const seen = new Map<any, T>();
|
||||
return arr.filter((item) => {
|
||||
const value = item[key];
|
||||
return seen.has(value) ? false : (seen.set(value, item), true);
|
||||
});
|
||||
}
|
||||
|
||||
export { uniqueByField };
|
@@ -39,7 +39,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.4.30",
|
||||
"vue": "^3.4.31",
|
||||
"vue-router": "^4.4.0"
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user