mirror of
https://gitee.com/dapppp/ruoyi-plus-vben5.git
synced 2026-03-08 07:31:09 +08:00
Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import { loadScript } from '../resources';
|
||||
|
||||
const testJsPath =
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js';
|
||||
|
||||
describe('loadScript', () => {
|
||||
beforeEach(() => {
|
||||
// 每个测试前清空 head,保证环境干净
|
||||
document.head.innerHTML = '';
|
||||
});
|
||||
|
||||
it('should resolve when the script loads successfully', async () => {
|
||||
const promise = loadScript(testJsPath);
|
||||
|
||||
// 此时脚本元素已被创建并插入
|
||||
const script = document.querySelector(
|
||||
`script[src="${testJsPath}"]`,
|
||||
) as HTMLScriptElement;
|
||||
expect(script).toBeTruthy();
|
||||
|
||||
// 模拟加载成功
|
||||
script.dispatchEvent(new Event('load'));
|
||||
|
||||
// 等待 promise resolve
|
||||
await expect(promise).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not insert duplicate script and resolve immediately if already loaded', async () => {
|
||||
// 先手动插入一个相同 src 的 script
|
||||
const existing = document.createElement('script');
|
||||
existing.src = 'bar.js';
|
||||
document.head.append(existing);
|
||||
|
||||
// 再次调用
|
||||
const promise = loadScript('bar.js');
|
||||
|
||||
// 立即 resolve
|
||||
await expect(promise).resolves.toBeUndefined();
|
||||
|
||||
// head 中只保留一个
|
||||
const scripts = document.head.querySelectorAll('script[src="bar.js"]');
|
||||
expect(scripts).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should reject when the script fails to load', async () => {
|
||||
const promise = loadScript('error.js');
|
||||
|
||||
const script = document.querySelector(
|
||||
'script[src="error.js"]',
|
||||
) as HTMLScriptElement;
|
||||
expect(script).toBeTruthy();
|
||||
|
||||
// 模拟加载失败
|
||||
script.dispatchEvent(new Event('error'));
|
||||
|
||||
await expect(promise).rejects.toThrow('Failed to load script: error.js');
|
||||
});
|
||||
|
||||
it('should handle multiple concurrent calls and only insert one script tag', async () => {
|
||||
const p1 = loadScript(testJsPath);
|
||||
const p2 = loadScript(testJsPath);
|
||||
|
||||
const script = document.querySelector(
|
||||
`script[src="${testJsPath}"]`,
|
||||
) as HTMLScriptElement;
|
||||
expect(script).toBeTruthy();
|
||||
|
||||
// 触发一次 load,两个 promise 都应该 resolve
|
||||
script.dispatchEvent(new Event('load'));
|
||||
|
||||
await expect(p1).resolves.toBeUndefined();
|
||||
await expect(p2).resolves.toBeUndefined();
|
||||
|
||||
// 只插入一次
|
||||
const scripts = document.head.querySelectorAll(
|
||||
`script[src="${testJsPath}"]`,
|
||||
);
|
||||
expect(scripts).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@@ -7,6 +7,7 @@ export * from './inference';
|
||||
export * from './letter';
|
||||
export * from './merge';
|
||||
export * from './nprogress';
|
||||
export * from './resources';
|
||||
export * from './state-handler';
|
||||
export * from './to';
|
||||
export * from './tree';
|
||||
|
||||
21
packages/@core/base/shared/src/utils/resources.ts
Normal file
21
packages/@core/base/shared/src/utils/resources.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* 加载js文件
|
||||
* @param src js文件地址
|
||||
*/
|
||||
function loadScript(src: string) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (document.querySelector(`script[src="${src}"]`)) {
|
||||
// 如果已经加载过,直接 resolve
|
||||
return resolve();
|
||||
}
|
||||
const script = document.createElement('script');
|
||||
script.src = src;
|
||||
script.addEventListener('load', () => resolve());
|
||||
script.addEventListener('error', () =>
|
||||
reject(new Error(`Failed to load script: ${src}`)),
|
||||
);
|
||||
document.head.append(script);
|
||||
});
|
||||
}
|
||||
|
||||
export { loadScript };
|
||||
@@ -59,6 +59,7 @@ export class ModalApi {
|
||||
showCancelButton: true,
|
||||
showConfirmButton: true,
|
||||
title: '',
|
||||
animationType: 'slide',
|
||||
};
|
||||
|
||||
this.store = new Store<ModalState>(
|
||||
|
||||
@@ -5,6 +5,11 @@ import type { MaybePromise } from '@vben-core/typings';
|
||||
import type { ModalApi } from './modal-api';
|
||||
|
||||
export interface ModalProps {
|
||||
/**
|
||||
* 动画类型
|
||||
* @default 'slide'
|
||||
*/
|
||||
animationType?: 'scale' | 'slide';
|
||||
/**
|
||||
* 是否要挂载到内容区域
|
||||
* @default false
|
||||
|
||||
@@ -94,12 +94,11 @@ const {
|
||||
submitting,
|
||||
title,
|
||||
titleTooltip,
|
||||
animationType,
|
||||
zIndex,
|
||||
} = usePriorityValues(props, state);
|
||||
|
||||
const shouldFullscreen = computed(
|
||||
() => (fullscreen.value && header.value) || isMobile.value,
|
||||
);
|
||||
const shouldFullscreen = computed(() => fullscreen.value || isMobile.value);
|
||||
|
||||
const shouldDraggable = computed(
|
||||
() => draggable.value && !shouldFullscreen.value && header.value,
|
||||
@@ -244,6 +243,7 @@ function handleClosed() {
|
||||
:modal="modal"
|
||||
:open="state?.isOpen"
|
||||
:show-close="closable"
|
||||
:animation-type="animationType"
|
||||
:z-index="zIndex"
|
||||
:overlay-blur="overlayBlur"
|
||||
close-class="top-3"
|
||||
|
||||
@@ -20,6 +20,7 @@ import DialogOverlay from './DialogOverlay.vue';
|
||||
const props = withDefaults(
|
||||
defineProps<
|
||||
DialogContentProps & {
|
||||
animationType?: 'scale' | 'slide';
|
||||
appendTo?: HTMLElement | string;
|
||||
class?: ClassType;
|
||||
closeClass?: ClassType;
|
||||
@@ -31,7 +32,12 @@ const props = withDefaults(
|
||||
zIndex?: number;
|
||||
}
|
||||
>(),
|
||||
{ appendTo: 'body', closeDisabled: false, showClose: true },
|
||||
{
|
||||
appendTo: 'body',
|
||||
animationType: 'slide',
|
||||
closeDisabled: false,
|
||||
showClose: true,
|
||||
},
|
||||
);
|
||||
const emits = defineEmits<
|
||||
DialogContentEmits & { close: []; closed: []; opened: [] }
|
||||
@@ -43,6 +49,7 @@ const delegatedProps = computed(() => {
|
||||
modal: _modal,
|
||||
open: _open,
|
||||
showClose: __,
|
||||
animationType: ___,
|
||||
...delegated
|
||||
} = props;
|
||||
|
||||
@@ -100,7 +107,11 @@ defineExpose({
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'z-popup bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-top-[48%] w-full p-6 shadow-lg outline-none sm:rounded-xl',
|
||||
'z-popup bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 w-full p-6 shadow-lg outline-none sm:rounded-xl',
|
||||
{
|
||||
'data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-top-[48%]':
|
||||
animationType === 'slide',
|
||||
},
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
|
||||
Reference in New Issue
Block a user