diff --git a/src/tabbar/README.md b/src/tabbar/README.md index 7fca5e380..d38fe1a86 100644 --- a/src/tabbar/README.md +++ b/src/tabbar/README.md @@ -160,6 +160,7 @@ export default { | route | Whether to enable route mode | _boolean_ | `false` | | placeholder `v2.6.0` | Whether to generage a placeholder element when fixed | _boolean_ | `false` | | safe-area-inset-bottom | Whether to enable bottom safe area adaptation | _boolean_ | `false` | +| before-change `v2.10.4` | Callback function before changing tabs,return `false` to prevent change,support return Promise | _(name) => boolean \| Promise_ | - | ### Tabbar Events diff --git a/src/tabbar/README.zh-CN.md b/src/tabbar/README.zh-CN.md index 634a66137..2578c0109 100644 --- a/src/tabbar/README.zh-CN.md +++ b/src/tabbar/README.zh-CN.md @@ -168,6 +168,7 @@ export default { | route | 是否开启路由模式 | _boolean_ | `false` | | placeholder `v2.6.0` | 固定在底部时,是否在标签位置生成一个等高的占位元素 | _boolean_ | `false` | | safe-area-inset-bottom | 是否开启[底部安全区适配](#/zh-CN/quickstart#di-bu-an-quan-qu-gua-pei),设置 fixed 时默认开启 | _boolean_ | `false` | +| before-change `v2.10.4` | 切换标签前的回调函数,返回 `false` 可阻止切换,支持返回 Promise | _(name) => boolean \| Promise_ | - | ### Tabbar Events diff --git a/src/tabbar/index.js b/src/tabbar/index.js index 27b5d445b..b1c4af25e 100644 --- a/src/tabbar/index.js +++ b/src/tabbar/index.js @@ -1,6 +1,7 @@ import { createNamespace } from '../utils'; -import { ParentMixin } from '../mixins/relation'; import { BORDER_TOP_BOTTOM } from '../utils/constant'; +import { callInterceptor } from '../utils/interceptor'; +import { ParentMixin } from '../mixins/relation'; const [createComponent, bem] = createNamespace('tabbar'); @@ -12,6 +13,7 @@ export default createComponent({ zIndex: [Number, String], placeholder: Boolean, activeColor: String, + beforeChange: Function, inactiveColor: String, value: { type: [Number, String], @@ -67,8 +69,14 @@ export default createComponent({ onChange(active) { if (active !== this.value) { - this.$emit('input', active); - this.$emit('change', active); + callInterceptor({ + interceptor: this.beforeChange, + args: [active], + done: () => { + this.$emit('input', active); + this.$emit('change', active); + }, + }); } }, diff --git a/src/utils/interceptor.ts b/src/utils/interceptor.ts new file mode 100644 index 000000000..96cb63481 --- /dev/null +++ b/src/utils/interceptor.ts @@ -0,0 +1,27 @@ +import { isPromise, noop } from '.'; + +export function callInterceptor(options: { + interceptor?: (...args: any[]) => Promise | boolean; + done: () => void; + args: any[]; +}) { + const { interceptor, args, done } = options; + + if (interceptor) { + const returnVal = interceptor(...args); + + if (isPromise(returnVal)) { + returnVal + .then((value) => { + if (value) { + done(); + } + }) + .catch(noop); + } else if (returnVal) { + done(); + } + } else { + done(); + } +} diff --git a/src/utils/test/interceptor.spec.js b/src/utils/test/interceptor.spec.js new file mode 100644 index 000000000..cd7c92552 --- /dev/null +++ b/src/utils/test/interceptor.spec.js @@ -0,0 +1,50 @@ +import { later } from '../../../test'; +import { callInterceptor } from '../interceptor'; + +test('#callInterceptor', async () => { + const done = jest.fn(); + callInterceptor({ done }); + expect(done).toHaveBeenCalledTimes(1); + + callInterceptor({ + interceptor: () => false, + done, + }); + expect(done).toHaveBeenCalledTimes(1); + + callInterceptor({ + interceptor: () => true, + done, + }); + expect(done).toHaveBeenCalledTimes(2); + + callInterceptor({ + interceptor: () => Promise.resolve(false), + done, + }); + await later(); + expect(done).toHaveBeenCalledTimes(2); + + callInterceptor({ + interceptor: () => Promise.resolve(true), + done, + }); + await later(); + expect(done).toHaveBeenCalledTimes(3); + + callInterceptor({ + interceptor: () => Promise.reject(), + done, + }); + await later(); + expect(done).toHaveBeenCalledTimes(3); + + callInterceptor({ + interceptor: (...args) => { + expect(args).toEqual(['foo']); + }, + args: ['foo'], + done, + }); + expect(done).toHaveBeenCalledTimes(3); +});