use-modal.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import type { ExtendedModalApi, ModalApiOptions, ModalProps } from './modal';
  2. import {
  3. defineComponent,
  4. h,
  5. inject,
  6. nextTick,
  7. onDeactivated,
  8. provide,
  9. reactive,
  10. ref,
  11. } from 'vue';
  12. import { useStore } from '@vben-core/shared/store';
  13. import { ModalApi } from './modal-api';
  14. import VbenModal from './modal.vue';
  15. const USER_MODAL_INJECT_KEY = Symbol('VBEN_MODAL_INJECT');
  16. const DEFAULT_MODAL_PROPS: Partial<ModalProps> = {};
  17. export function setDefaultModalProps(props: Partial<ModalProps>) {
  18. Object.assign(DEFAULT_MODAL_PROPS, props);
  19. }
  20. export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
  21. options: ModalApiOptions = {},
  22. ) {
  23. // Modal一般会抽离出来,所以如果有传入 connectedComponent,则表示为外部调用,与内部组件进行连接
  24. // 外部的Modal通过provide/inject传递api
  25. const { connectedComponent } = options;
  26. if (connectedComponent) {
  27. const extendedApi = reactive({});
  28. const isModalReady = ref(true);
  29. const Modal = defineComponent(
  30. (props: TParentModalProps, { attrs, slots }) => {
  31. provide(USER_MODAL_INJECT_KEY, {
  32. extendApi(api: ExtendedModalApi) {
  33. // 不能直接给 reactive 赋值,会丢失响应
  34. // 不能用 Object.assign,会丢失 api 的原型函数
  35. Object.setPrototypeOf(extendedApi, api);
  36. },
  37. options,
  38. async reCreateModal() {
  39. isModalReady.value = false;
  40. await nextTick();
  41. isModalReady.value = true;
  42. },
  43. });
  44. checkProps(extendedApi as ExtendedModalApi, {
  45. ...props,
  46. ...attrs,
  47. ...slots,
  48. });
  49. return () =>
  50. h(
  51. isModalReady.value ? connectedComponent : 'div',
  52. {
  53. ...props,
  54. ...attrs,
  55. },
  56. slots,
  57. );
  58. },
  59. // eslint-disable-next-line vue/one-component-per-file
  60. {
  61. name: 'VbenParentModal',
  62. inheritAttrs: false,
  63. },
  64. );
  65. /**
  66. * 在开启keepAlive情况下 直接通过浏览器按钮/手势等返回 不会关闭弹窗
  67. */
  68. onDeactivated(() => {
  69. (extendedApi as ExtendedModalApi)?.close?.();
  70. });
  71. return [Modal, extendedApi as ExtendedModalApi] as const;
  72. }
  73. const injectData = inject<any>(USER_MODAL_INJECT_KEY, {});
  74. const mergedOptions = {
  75. ...DEFAULT_MODAL_PROPS,
  76. ...injectData.options,
  77. ...options,
  78. } as ModalApiOptions;
  79. mergedOptions.onOpenChange = (isOpen: boolean) => {
  80. options.onOpenChange?.(isOpen);
  81. injectData.options?.onOpenChange?.(isOpen);
  82. };
  83. mergedOptions.onClosed = () => {
  84. options.onClosed?.();
  85. if (mergedOptions.destroyOnClose) {
  86. injectData.reCreateModal?.();
  87. }
  88. };
  89. const api = new ModalApi(mergedOptions);
  90. const extendedApi: ExtendedModalApi = api as never;
  91. extendedApi.useStore = (selector) => {
  92. return useStore(api.store, selector);
  93. };
  94. const Modal = defineComponent(
  95. (props: ModalProps, { attrs, slots }) => {
  96. return () =>
  97. h(
  98. VbenModal,
  99. {
  100. ...props,
  101. ...attrs,
  102. modalApi: extendedApi,
  103. },
  104. slots,
  105. );
  106. },
  107. // eslint-disable-next-line vue/one-component-per-file
  108. {
  109. name: 'VbenModal',
  110. inheritAttrs: false,
  111. },
  112. );
  113. injectData.extendApi?.(extendedApi);
  114. return [Modal, extendedApi] as const;
  115. }
  116. async function checkProps(api: ExtendedModalApi, attrs: Record<string, any>) {
  117. if (!attrs || Object.keys(attrs).length === 0) {
  118. return;
  119. }
  120. await nextTick();
  121. const state = api?.store?.state;
  122. if (!state) {
  123. return;
  124. }
  125. const stateKeys = new Set(Object.keys(state));
  126. for (const attr of Object.keys(attrs)) {
  127. if (stateKeys.has(attr) && !['class'].includes(attr)) {
  128. // connectedComponent存在时,不要传入Modal的props,会造成复杂度提升,如果你需要修改Modal的props,请使用 useModal 或者api
  129. console.warn(
  130. `[Vben Modal]: When 'connectedComponent' exists, do not set props or slots '${attr}', which will increase complexity. If you need to modify the props of Modal, please use useVbenModal or api.`,
  131. );
  132. }
  133. }
  134. }