浏览代码

Merge branch 'main' into fix

Jin Mao 4 月之前
父节点
当前提交
b9224fc379

+ 9 - 3
apps/web-antd/src/adapter/component/index.ts

@@ -17,6 +17,7 @@ import type { BaseFormComponentType } from '@vben/common-ui';
 import type { Recordable } from '@vben/types';
 
 import {
+  computed,
   defineAsyncComponent,
   defineComponent,
   h,
@@ -383,12 +384,17 @@ const withPreviewUpload = () => {
         attrs?.fileList || attrs?.['file-list'] || [],
       );
 
+      const maxSize = computed(() => attrs?.maxSize ?? attrs?.['max-size']);
+      const aspectRatio = computed(
+        () => attrs?.aspectRatio ?? attrs?.['aspect-ratio'],
+      );
+
       const handleBeforeUpload = async (
         file: UploadFile,
         originFileList: Array<File>,
       ) => {
-        if (attrs.maxSize && (file.size || 0) / 1024 / 1024 > attrs.maxSize) {
-          message.error($t('ui.formRules.sizeLimit', [attrs.maxSize]));
+        if (maxSize.value && (file.size || 0) / 1024 / 1024 > maxSize.value) {
+          message.error($t('ui.formRules.sizeLimit', [maxSize.value]));
           file.status = 'removed';
           return false;
         }
@@ -401,7 +407,7 @@ const withPreviewUpload = () => {
         ) {
           file.status = 'removed';
           // antd Upload组件问题 file参数获取的是UploadFile类型对象无法取到File类型 所以通过originFileList[0]获取
-          const blob = await cropImage(originFileList[0], attrs.aspectRatio);
+          const blob = await cropImage(originFileList[0], aspectRatio.value);
           return new Promise((resolve, reject) => {
             if (!blob) {
               return reject(new Error($t('ui.crop.errorTip')));

+ 12 - 0
packages/@core/base/shared/src/utils/dom.ts

@@ -41,6 +41,18 @@ export function getElementVisibleRect(
   const left = Math.max(rect.left, 0);
   const right = Math.min(rect.right, viewWidth);
 
+  // 如果元素完全不可见,则返回一个空的矩形
+  if (top >= viewHeight || bottom <= 0 || left >= viewWidth || right <= 0) {
+    return {
+      bottom: 0,
+      height: 0,
+      left: 0,
+      right: 0,
+      top: 0,
+      width: 0,
+    };
+  }
+
   return {
     bottom,
     height: Math.max(0, bottom - top),

+ 8 - 1
packages/@core/ui-kit/popup-ui/src/modal/use-modal.ts

@@ -41,6 +41,7 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
             // 不能用 Object.assign,会丢失 api 的原型函数
             Object.setPrototypeOf(extendedApi, api);
           },
+          consumed: false,
           options,
           async reCreateModal() {
             isModalReady.value = false;
@@ -73,7 +74,13 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
     return [Modal, extendedApi as ExtendedModalApi] as const;
   }
 
-  const injectData = inject<any>(USER_MODAL_INJECT_KEY, {});
+  let injectData = inject<any>(USER_MODAL_INJECT_KEY, {});
+  // 这个数据已经被使用了,说明这个弹窗是嵌套的弹窗,不应该merge上层的配置
+  if (injectData.consumed) {
+    injectData = {};
+  } else {
+    injectData.consumed = true;
+  }
 
   const mergedOptions = {
     ...DEFAULT_MODAL_PROPS,

+ 3 - 3
packages/@core/ui-kit/shadcn-ui/src/components/spinner/loading.vue

@@ -32,19 +32,19 @@ const props = withDefaults(defineProps<Props>(), {
 // const startTime = ref(0);
 const showSpinner = ref(false);
 const renderSpinner = ref(false);
-const timer = ref<ReturnType<typeof setTimeout>>();
+let timer: ReturnType<typeof setTimeout> | undefined;
 
 watch(
   () => props.spinning,
   (show) => {
     if (!show) {
       showSpinner.value = false;
-      clearTimeout(timer.value);
+      timer && clearTimeout(timer);
       return;
     }
 
     // startTime.value = performance.now();
-    timer.value = setTimeout(() => {
+    timer = setTimeout(() => {
       // const loadingTime = performance.now() - startTime.value;
 
       showSpinner.value = true;

+ 33 - 1
packages/effects/plugins/src/echarts/use-echarts.ts

@@ -92,7 +92,8 @@ function useEcharts(chartRef: Ref<EchartsUIType>) {
           return;
         }
         useTimeoutFn(() => {
-          if (!chartInstance) {
+          if (!chartInstance || chartInstance?.getDom() !== el) {
+            chartInstance?.dispose();
             const instance = initCharts();
             if (!instance) return;
           }
@@ -104,6 +105,36 @@ function useEcharts(chartRef: Ref<EchartsUIType>) {
     });
   };
 
+  const updateDate = (
+    option: EChartsOption,
+    notMerge = false, // false = 合并(保留动画),true = 完全替换
+    lazyUpdate = false, // true 时不立即重绘,适合短时间内多次调用
+  ): Promise<echarts.ECharts | null> => {
+    return new Promise((resolve) => {
+      nextTick(() => {
+        if (!chartInstance) {
+          // 还没初始化 → 当作首次渲染
+          renderEcharts(option).then(resolve);
+          return;
+        }
+
+        // 合并你原有的全局配置(比如 backgroundColor)
+        const finalOption = {
+          ...option,
+          ...getOptions.value,
+        };
+
+        chartInstance.setOption(finalOption, {
+          notMerge,
+          lazyUpdate,
+          // silent: true,     // 如果追求极致性能可开启(关闭所有事件)
+        });
+
+        resolve(chartInstance);
+      });
+    });
+  };
+
   function resize() {
     const el = getChartEl();
     if (isElHidden(el)) {
@@ -139,6 +170,7 @@ function useEcharts(chartRef: Ref<EchartsUIType>) {
   return {
     renderEcharts,
     resize,
+    updateDate,
     getChartInstance: () => chartInstance,
   };
 }

+ 9 - 3
playground/src/adapter/component/index.ts

@@ -17,6 +17,7 @@ import type { BaseFormComponentType } from '@vben/common-ui';
 import type { Recordable } from '@vben/types';
 
 import {
+  computed,
   defineAsyncComponent,
   defineComponent,
   h,
@@ -383,12 +384,17 @@ const withPreviewUpload = () => {
         attrs?.fileList || attrs?.['file-list'] || [],
       );
 
+      const maxSize = computed(() => attrs?.maxSize ?? attrs?.['max-size']);
+      const aspectRatio = computed(
+        () => attrs?.aspectRatio ?? attrs?.['aspect-ratio'],
+      );
+
       const handleBeforeUpload = async (
         file: UploadFile,
         originFileList: Array<File>,
       ) => {
-        if (attrs.maxSize && (file.size || 0) / 1024 / 1024 > attrs.maxSize) {
-          message.error($t('ui.formRules.sizeLimit', [attrs.maxSize]));
+        if (maxSize.value && (file.size || 0) / 1024 / 1024 > maxSize.value) {
+          message.error($t('ui.formRules.sizeLimit', [maxSize.value]));
           file.status = 'removed';
           return false;
         }
@@ -401,7 +407,7 @@ const withPreviewUpload = () => {
         ) {
           file.status = 'removed';
           // antd Upload组件问题 file参数获取的是UploadFile类型对象无法取到File类型 所以通过originFileList[0]获取
-          const blob = await cropImage(originFileList[0], attrs.aspectRatio);
+          const blob = await cropImage(originFileList[0], aspectRatio.value);
           return new Promise((resolve, reject) => {
             if (!blob) {
               return reject(new Error($t('ui.crop.errorTip')));

+ 10 - 3
playground/src/store/auth.ts

@@ -78,15 +78,22 @@ export const useAuthStore = defineStore('auth', () => {
     };
   }
 
+  const isLoggingOut = ref(false); // 正在 logout 标识, 防止 /logout 死循环.
+
   async function logout(redirect: boolean = true) {
+    if (isLoggingOut.value) return; // 正在登出中, 说明已进入循环, 直接返回.
+    isLoggingOut.value = true; // 设置 标识
+
     try {
       await logoutApi();
     } catch {
       // 不做任何处理
-    }
+    } finally {
+      isLoggingOut.value = false; // 重置 标识
 
-    resetAllStores();
-    accessStore.setLoginExpired(false);
+      resetAllStores();
+      accessStore.setLoginExpired(false);
+    }
 
     // 回登录页带上当前路由地址
     await router.replace({