From 39c4150b1e12d74ea6d4ef64f784ab4194dfe040 Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Tue, 31 Mar 2026 10:45:27 +0800 Subject: [PATCH 1/4] release: v4.1.0 --- package.json | 2 +- publish/nutui-taro/package.json | 2 +- publish/nutui/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 73b2a65916..d5866a37f3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@nutui/nutui", - "version": "4.3.15", + "version": "4.3.16", "description": "京东风格的轻量级移动端 Vue2、Vue3 组件库(支持小程序开发)", "main": "dist/nutui.umd.js", "module": "dist/nutui.es.js", diff --git a/publish/nutui-taro/package.json b/publish/nutui-taro/package.json index 24a571a67d..f86c415af6 100644 --- a/publish/nutui-taro/package.json +++ b/publish/nutui-taro/package.json @@ -1,6 +1,6 @@ { "name": "@nutui/nutui-taro", - "version": "4.3.15", + "version": "4.3.16", "description": "京东风格的轻量级移动端 Vue2、Vue3 组件库(支持小程序开发)", "main": "dist/nutui.umd.js", "module": "dist/nutui.es.js", diff --git a/publish/nutui/package.json b/publish/nutui/package.json index 6a1f8e368f..ac985dc3f7 100644 --- a/publish/nutui/package.json +++ b/publish/nutui/package.json @@ -1,6 +1,6 @@ { "name": "@nutui/nutui", - "version": "4.3.15", + "version": "4.3.16", "description": "京东风格的轻量级移动端 Vue2、Vue3 组件库(支持小程序开发)", "main": "dist/nutui.umd.js", "module": "dist/nutui.es.js", From 1126238e85f77583f054bef7de04ffc88bd5203c Mon Sep 17 00:00:00 2001 From: huangjianwu Date: Tue, 31 Mar 2026 21:06:57 +0800 Subject: [PATCH 2/4] fix(toast): prevent duplicate close events during closing animation --- src/packages/__VUE/toast/index.vue | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/packages/__VUE/toast/index.vue b/src/packages/__VUE/toast/index.vue index 8e6b52c27e..f3a0b457b8 100644 --- a/src/packages/__VUE/toast/index.vue +++ b/src/packages/__VUE/toast/index.vue @@ -93,10 +93,11 @@ export default create({ } }, emits: ['close'], - setup(props, { emit }) { + setup(props) { let timer: null | number | undefined const state = reactive({ - mounted: false + mounted: false, + closing: false }) onMounted(() => { state.mounted = true @@ -109,9 +110,11 @@ export default create({ } const hide = () => { state.mounted = false + state.closing = true } const show = () => { clearTimer() + state.closing = false if (props.duration) { timer = window.setTimeout(() => { hide() @@ -119,10 +122,8 @@ export default create({ } } const clickCover = () => { - if (props.closeOnClickOverlay) { - hide() - emit('close') - } + if (!props.closeOnClickOverlay || state.closing) return // 点击遮罩时如果正在关闭中,则不触发关闭事件,避免重复调用close事件 + hide() } if (props.duration) { From 672f1ec9102894db6ea3bf433b678c3bb74ab911 Mon Sep 17 00:00:00 2001 From: huangjianwu Date: Tue, 31 Mar 2026 21:43:42 +0800 Subject: [PATCH 3/4] fix(toast): enhance close behavior and auto-hide functionality in tests --- .../__VUE/toast/__tests__/index.spec.ts | 90 ++++++++++++++++++- 1 file changed, 86 insertions(+), 4 deletions(-) diff --git a/src/packages/__VUE/toast/__tests__/index.spec.ts b/src/packages/__VUE/toast/__tests__/index.spec.ts index d367fe8954..5ad6cbdf11 100644 --- a/src/packages/__VUE/toast/__tests__/index.spec.ts +++ b/src/packages/__VUE/toast/__tests__/index.spec.ts @@ -1,4 +1,5 @@ import { mount } from '@vue/test-utils' +import { vi } from 'vitest' import { nextTick } from 'vue' import { Toast } from '@nutui/nutui' @@ -40,6 +41,7 @@ describe('component toast', () => { const toastCover: any = wrapper.find('.nut-toast-cover') expect(toastCover.element.style.backgroundColor).toEqual('black') }) + test('should close Toast when using closeOnClickOverlay prop and clicked', async () => { const wrapper = mount(Toast, { props: { @@ -54,14 +56,94 @@ describe('component toast', () => { await nextTick() expect(toast.element.style.display).toEqual('none') }) - test('should render customClass when using customClass prop ', async () => { + test('should not close Toast when closeOnClickOverlay is false and clicked', async () => { const wrapper = mount(Toast, { props: { - customClass: 'custom' + cover: true, + closeOnClickOverlay: false, + duration: 0 } }) await nextTick() - const toast: any = wrapper.find('.custom') - expect(toast.exists()).toBe(true) + const toast: any = wrapper.find('.nut-toast') + await toast.trigger('click') + await nextTick() + expect(toast.element.style.display).not.toEqual('none') + }) + + test('should auto-hide after duration', async () => { + vi.useFakeTimers() + const wrapper = mount(Toast, { + props: { msg: 'msg', duration: 100 } + }) + await nextTick() + const toast: any = wrapper.find('.nut-toast') + expect(toast.element.style.display).not.toEqual('none') + + vi.advanceTimersByTime(100) + await nextTick() + expect(toast.element.style.display).toEqual('none') + vi.useRealTimers() + }) + + test('should not auto-hide when duration is 0', async () => { + vi.useFakeTimers() + const wrapper = mount(Toast, { + props: { msg: 'msg', duration: 0 } + }) + await nextTick() + const toast: any = wrapper.find('.nut-toast') + vi.advanceTimersByTime(5000) + await nextTick() + expect(toast.element.style.display).not.toEqual('none') + vi.useRealTimers() + }) + test('should set state.closing to true after clicking overlay', async () => { + const wrapper = mount(Toast, { + props: { cover: true, closeOnClickOverlay: true, duration: 0 } + }) + await nextTick() + const toast = wrapper.find('.nut-toast') + await toast.trigger('click') + await nextTick() + expect((wrapper.vm as any).state.closing).toBe(true) + expect(toast.element.style.display).toEqual('none') + }) + + test('should not re-trigger hide when clicking overlay during closing animation', async () => { + const wrapper = mount(Toast, { + props: { cover: true, closeOnClickOverlay: true, duration: 0 } + }) + await nextTick() + const toast = wrapper.find('.nut-toast') + + // 第一次点击:state.mounted = false,state.closing = true + await toast.trigger('click') + await nextTick() + expect((wrapper.vm as any).state.closing).toBe(true) + expect(toast.element.style.display).toEqual('none') + + // 第二次点击:closing = true,clickCover 提前 return,state 保持不变 + await toast.trigger('click') + await nextTick() + expect((wrapper.vm as any).state.closing).toBe(true) + expect((wrapper.vm as any).state.mounted).toBe(false) + }) + + test('should reset closing state when show() is called again', async () => { + const wrapper = mount(Toast, { + props: { cover: true, closeOnClickOverlay: true, duration: 0 } + }) + await nextTick() + const toast = wrapper.find('.nut-toast') + + await toast.trigger('click') + await nextTick() + expect((wrapper.vm as any).state.closing).toBe(true) + + // 更新 duration 触发 watch → show() → closing 重置 + await wrapper.setProps({ duration: 100 }) + await nextTick() + expect((wrapper.vm as any).state.closing).toBe(false) }) }) From 3b0aeaac6efa40f96a2bb2be780bf42aa42f9cc0 Mon Sep 17 00:00:00 2001 From: huangjianwu Date: Tue, 31 Mar 2026 22:44:47 +0800 Subject: [PATCH 4/4] fix(toast-taro): prevent closing toast multiple times during animation --- src/packages/__VUE/toast/index.taro.vue | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/packages/__VUE/toast/index.taro.vue b/src/packages/__VUE/toast/index.taro.vue index 084d3e99fa..88f2b4af67 100644 --- a/src/packages/__VUE/toast/index.taro.vue +++ b/src/packages/__VUE/toast/index.taro.vue @@ -33,7 +33,7 @@