From 48ed7970554af522dae847de7b6a92bb26d1cb0c Mon Sep 17 00:00:00 2001 From: eric Date: Fri, 9 Jan 2026 22:38:11 +0800 Subject: [PATCH 01/10] =?UTF-8?q?fix:=20=E9=98=B2=E6=AD=A2=20/logout=20?= =?UTF-8?q?=E6=AD=BB=E5=BE=AA=E7=8E=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- playground/src/store/auth.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/playground/src/store/auth.ts b/playground/src/store/auth.ts index 4adeb76e..9d461d15 100644 --- a/playground/src/store/auth.ts +++ b/playground/src/store/auth.ts @@ -78,11 +78,17 @@ export const useAuthStore = defineStore('auth', () => { }; } + let isLoggingOut = false; // 正在 logout 标识, 防止 /logout 死循环. + async function logout(redirect: boolean = true) { + if (isLoggingOut) return; + isLoggingOut = true; try { await logoutApi(); } catch { // 不做任何处理 + } finally { + isLoggingOut = false; } resetAllStores(); From 13c8318adc5aa28cb90cc55d1de9eb7ba1ae14cc Mon Sep 17 00:00:00 2001 From: eric Date: Fri, 9 Jan 2026 23:05:05 +0800 Subject: [PATCH 02/10] =?UTF-8?q?refactor:=201.=20=E7=94=A8=20ref=20?= =?UTF-8?q?=E5=8C=85=E8=A3=85=20flag;=202.=20=E5=9C=A8=E6=9C=80=E5=90=8E?= =?UTF-8?q?=20=E6=B8=85=E7=90=86=20flag;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- playground/src/store/auth.ts | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/playground/src/store/auth.ts b/playground/src/store/auth.ts index 9d461d15..1fec5c04 100644 --- a/playground/src/store/auth.ts +++ b/playground/src/store/auth.ts @@ -78,31 +78,32 @@ export const useAuthStore = defineStore('auth', () => { }; } - let isLoggingOut = false; // 正在 logout 标识, 防止 /logout 死循环. + const isLoggingOut = ref(false); // 正在 logout 标识, 防止 /logout 死循环. async function logout(redirect: boolean = true) { - if (isLoggingOut) return; - isLoggingOut = true; + if (isLoggingOut.value) return; // 正在登出中, 说明已进入循环, 直接返回. + isLoggingOut.value = true; // 设置 标识 + try { await logoutApi(); + + resetAllStores(); + accessStore.setLoginExpired(false); + + // 回登录页带上当前路由地址 + await router.replace({ + path: LOGIN_PATH, + query: redirect + ? { + redirect: encodeURIComponent(router.currentRoute.value.fullPath), + } + : {}, + }); } catch { // 不做任何处理 } finally { - isLoggingOut = false; + isLoggingOut.value = false; // 重置 标识 } - - resetAllStores(); - accessStore.setLoginExpired(false); - - // 回登录页带上当前路由地址 - await router.replace({ - path: LOGIN_PATH, - query: redirect - ? { - redirect: encodeURIComponent(router.currentRoute.value.fullPath), - } - : {}, - }); } async function fetchUserInfo() { From 1cb53e943ea99acb655100dcfaf743b01de1a09a Mon Sep 17 00:00:00 2001 From: eric Date: Fri, 9 Jan 2026 23:13:06 +0800 Subject: [PATCH 03/10] =?UTF-8?q?refactor:=20=E5=B0=86=E8=B7=B3=E8=BD=AC?= =?UTF-8?q?=E6=94=BE=E5=88=B0=E6=9C=80=E5=90=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- playground/src/store/auth.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/playground/src/store/auth.ts b/playground/src/store/auth.ts index 1fec5c04..14100727 100644 --- a/playground/src/store/auth.ts +++ b/playground/src/store/auth.ts @@ -89,21 +89,21 @@ export const useAuthStore = defineStore('auth', () => { resetAllStores(); accessStore.setLoginExpired(false); - - // 回登录页带上当前路由地址 - await router.replace({ - path: LOGIN_PATH, - query: redirect - ? { - redirect: encodeURIComponent(router.currentRoute.value.fullPath), - } - : {}, - }); } catch { // 不做任何处理 } finally { isLoggingOut.value = false; // 重置 标识 } + + // 回登录页带上当前路由地址 + await router.replace({ + path: LOGIN_PATH, + query: redirect + ? { + redirect: encodeURIComponent(router.currentRoute.value.fullPath), + } + : {}, + }); } async function fetchUserInfo() { From 694396dcfb187595888100b7df676582b11a02e7 Mon Sep 17 00:00:00 2001 From: eric Date: Fri, 9 Jan 2026 23:22:49 +0800 Subject: [PATCH 04/10] refactor: move cleanup to finally block --- playground/src/store/auth.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/playground/src/store/auth.ts b/playground/src/store/auth.ts index 14100727..b50ee772 100644 --- a/playground/src/store/auth.ts +++ b/playground/src/store/auth.ts @@ -86,13 +86,13 @@ export const useAuthStore = defineStore('auth', () => { try { await logoutApi(); - - resetAllStores(); - accessStore.setLoginExpired(false); } catch { // 不做任何处理 } finally { isLoggingOut.value = false; // 重置 标识 + + resetAllStores(); + accessStore.setLoginExpired(false); } // 回登录页带上当前路由地址 From 3862942e9fdaab0edc87cf7f94e5b2a23a30d8db Mon Sep 17 00:00:00 2001 From: Qiu <57087356+Child-qjj@users.noreply.github.com> Date: Wed, 21 Jan 2026 11:47:01 +0800 Subject: [PATCH 05/10] fix: chart instance disposal condition dom has been disposed in vue3 v-if,but chartInstance exist --- packages/effects/plugins/src/echarts/use-echarts.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/effects/plugins/src/echarts/use-echarts.ts b/packages/effects/plugins/src/echarts/use-echarts.ts index 1a28fb12..332b3a0d 100644 --- a/packages/effects/plugins/src/echarts/use-echarts.ts +++ b/packages/effects/plugins/src/echarts/use-echarts.ts @@ -92,7 +92,8 @@ function useEcharts(chartRef: Ref) { return; } useTimeoutFn(() => { - if (!chartInstance) { + if (!chartInstance || chartInstance?.getDom() !== el) { + chartInstance?.dispose(); const instance = initCharts(); if (!instance) return; } From 6c8c49966a9d0b7751ab1cbf1b51dc72225d75e8 Mon Sep 17 00:00:00 2001 From: JyQAQ <45193678+jyqwq@users.noreply.github.com> Date: Wed, 21 Jan 2026 17:20:53 +0800 Subject: [PATCH 06/10] =?UTF-8?q?Perf:=20=E4=BC=98=E5=8C=96antd=20upload?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E5=8F=82=E6=95=B0=E8=8E=B7=E5=8F=96=20(#7114?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf(antd upload params): 优化组件参数取值 确保不同调用场景配置参数可用 * perf(antd upload params): 优化组件参数取值 确保不同调用场景配置参数可用 * perf(antd upload params): 优化组件参数取值 确保不同调用场景配置参数可用 * perf(antd upload params): 优化组件参数取值 确保不同调用场景配置参数可用 --- apps/web-antd/src/adapter/component/index.ts | 12 +++++++++--- playground/src/adapter/component/index.ts | 12 +++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/apps/web-antd/src/adapter/component/index.ts b/apps/web-antd/src/adapter/component/index.ts index 7fdb1434..80d990b7 100644 --- a/apps/web-antd/src/adapter/component/index.ts +++ b/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, ) => { - 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'))); diff --git a/playground/src/adapter/component/index.ts b/playground/src/adapter/component/index.ts index 7fdb1434..80d990b7 100644 --- a/playground/src/adapter/component/index.ts +++ b/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, ) => { - 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'))); From 203ee9b6234d8f7ddcdae6eee9c6584563b8da2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A9=99=E5=AD=90?= <126167116+caodachen@users.noreply.github.com> Date: Thu, 22 Jan 2026 11:37:01 +0800 Subject: [PATCH 07/10] fix(@vben-core/shared): element outside viewport, the element visible rect each prop expect 0 (#7120) * fix(@vben-core/shared): element outside viewport * fix(@vben-core/shared): element outside viewport --- packages/@core/base/shared/src/utils/dom.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/@core/base/shared/src/utils/dom.ts b/packages/@core/base/shared/src/utils/dom.ts index 69617176..35a7e5ff 100644 --- a/packages/@core/base/shared/src/utils/dom.ts +++ b/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), From 74381aa8c1ba043b56d97227397fc95fa17bf6b0 Mon Sep 17 00:00:00 2001 From: yuhengshen Date: Thu, 22 Jan 2026 20:07:13 +0800 Subject: [PATCH 08/10] =?UTF-8?q?fix:=20=E5=B5=8C=E5=A5=97=E5=BC=B9?= =?UTF-8?q?=E7=AA=97=EF=BC=8C=E9=94=99=E8=AF=AF=20merge=20options=20(#7126?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/@core/ui-kit/popup-ui/src/modal/use-modal.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/@core/ui-kit/popup-ui/src/modal/use-modal.ts b/packages/@core/ui-kit/popup-ui/src/modal/use-modal.ts index 0af48476..5385b215 100644 --- a/packages/@core/ui-kit/popup-ui/src/modal/use-modal.ts +++ b/packages/@core/ui-kit/popup-ui/src/modal/use-modal.ts @@ -41,6 +41,7 @@ export function useVbenModal( // 不能用 Object.assign,会丢失 api 的原型函数 Object.setPrototypeOf(extendedApi, api); }, + consumed: false, options, async reCreateModal() { isModalReady.value = false; @@ -73,7 +74,13 @@ export function useVbenModal( return [Modal, extendedApi as ExtendedModalApi] as const; } - const injectData = inject(USER_MODAL_INJECT_KEY, {}); + let injectData = inject(USER_MODAL_INJECT_KEY, {}); + // 这个数据已经被使用了,说明这个弹窗是嵌套的弹窗,不应该merge上层的配置 + if (injectData.consumed) { + injectData = {}; + } else { + injectData.consumed = true; + } const mergedOptions = { ...DEFAULT_MODAL_PROPS, From d5d4a5c5916257d088c0164fe769d2c6906a2ccd Mon Sep 17 00:00:00 2001 From: Sun Date: Fri, 23 Jan 2026 11:06:45 +0800 Subject: [PATCH 09/10] =?UTF-8?q?feat(effects-plugins):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=20echarts=20=E5=9B=BE=E8=A1=A8=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 updateDate 方法用于更新 echarts 图表选项,支持合并配置、 完全替换和延迟更新等模式。该方法会在组件未初始化时自动执 行首次渲染,并能够合并全局配置如 backgroundColor 等选项。 --- .../plugins/src/echarts/use-echarts.ts | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/effects/plugins/src/echarts/use-echarts.ts b/packages/effects/plugins/src/echarts/use-echarts.ts index 1a28fb12..b3c38285 100644 --- a/packages/effects/plugins/src/echarts/use-echarts.ts +++ b/packages/effects/plugins/src/echarts/use-echarts.ts @@ -104,6 +104,36 @@ function useEcharts(chartRef: Ref) { }); }; + const updateDate = ( + option: EChartsOption, + notMerge = false, // false = 合并(保留动画),true = 完全替换 + lazyUpdate = false, // true 时不立即重绘,适合短时间内多次调用 + ): Promise => { + 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 +169,7 @@ function useEcharts(chartRef: Ref) { return { renderEcharts, resize, + updateDate, getChartInstance: () => chartInstance, }; } From 44f8aed06d4cd238ff7f922e081bb30085e9249b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A9=99=E5=AD=90?= <126167116+caodachen@users.noreply.github.com> Date: Fri, 23 Jan 2026 13:16:09 +0800 Subject: [PATCH 10/10] fix: timer not need reactivity (#7128) --- .../ui-kit/shadcn-ui/src/components/spinner/loading.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/@core/ui-kit/shadcn-ui/src/components/spinner/loading.vue b/packages/@core/ui-kit/shadcn-ui/src/components/spinner/loading.vue index 268da932..1544f34d 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/components/spinner/loading.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/components/spinner/loading.vue @@ -32,19 +32,19 @@ const props = withDefaults(defineProps(), { // const startTime = ref(0); const showSpinner = ref(false); const renderSpinner = ref(false); -const timer = ref>(); +let timer: ReturnType | 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;