BasicModal.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. <template>
  2. <div class="vent-modal">
  3. <Modal v-bind="getBindValue" @cancel="handleCancel">
  4. <template #closeIcon v-if="!$slots.closeIcon">
  5. <ModalClose
  6. :canFullscreen="getProps.canFullscreen"
  7. :fullScreen="fullScreenRef"
  8. :commentSpan="commentSpan"
  9. :enableComment="getProps.enableComment"
  10. @comment="handleComment"
  11. @cancel="handleCancel"
  12. @fullscreen="handleFullScreen"
  13. />
  14. </template>
  15. <template #title v-if="!$slots.title">
  16. <ModalHeader :helpMessage="getProps.helpMessage" :title="getMergeProps.title" @dblclick="handleTitleDbClick" />
  17. </template>
  18. <template #title v-if="!isNoTitle">
  19. <ModalHeader :helpMessage="getProps.helpMessage" :title="getMergeProps.title" @dblclick="handleTitleDbClick" />
  20. </template>
  21. <template #footer v-if="!$slots.footer">
  22. <ModalFooter v-bind="getBindValue" @ok="handleOk" @cancel="handleCancel">
  23. <template #[item]="data" v-for="item in Object.keys($slots)">
  24. <slot :name="item" v-bind="data || {}"></slot>
  25. </template>
  26. </ModalFooter>
  27. </template>
  28. <!-- update-begin-author:taoyan date:2022-7-18 for: modal弹窗 支持评论 slot -->
  29. <a-row v-if="getProps.enableComment" class="jeecg-modal-wrapper">
  30. <a-col :span="24 - commentSpan" class="jeecg-modal-content">
  31. <ModalWrapper
  32. :useWrapper="getProps.useWrapper"
  33. :footerOffset="wrapperFooterOffset"
  34. :fullScreen="fullScreenRef"
  35. ref="modalWrapperRef"
  36. :loading="getProps.loading"
  37. :loading-tip="getProps.loadingTip"
  38. :minHeight="getProps.minHeight"
  39. :height="getWrapperHeight"
  40. :visible="visibleRef"
  41. :modalFooterHeight="footer !== undefined && !footer ? 0 : undefined"
  42. v-bind="omit(getProps.wrapperProps, 'visible', 'height', 'modalFooterHeight')"
  43. @ext-height="handleExtHeight"
  44. @height-change="handleHeightChange"
  45. >
  46. <slot></slot>
  47. </ModalWrapper>
  48. </a-col>
  49. </a-row>
  50. <!-- update-begin-author:taoyan date:2022-7-18 for: modal弹窗 支持评论 slot -->
  51. <a-row class="jeecg-modal-wrapper">
  52. <a-col :span="24 - commentSpan" class="jeecg-modal-content">
  53. <ModalWrapper
  54. :useWrapper="getProps.useWrapper"
  55. :footerOffset="wrapperFooterOffset"
  56. :fullScreen="fullScreenRef"
  57. ref="modalWrapperRef"
  58. :loading="getProps.loading"
  59. :loading-tip="getProps.loadingTip"
  60. :minHeight="getProps.minHeight"
  61. :height="getWrapperHeight"
  62. :visible="visibleRef"
  63. :modalFooterHeight="footer !== undefined && !footer ? 0 : undefined"
  64. v-bind="omit(getProps.wrapperProps, 'visible', 'height', 'modalFooterHeight')"
  65. @ext-height="handleExtHeight"
  66. @height-change="handleHeightChange"
  67. >
  68. <slot></slot>
  69. </ModalWrapper>
  70. </a-col>
  71. <a-col :span="commentSpan" class="jeecg-comment-outer">
  72. <slot name="comment"></slot>
  73. </a-col>
  74. </a-row>
  75. <!-- update-end-author:taoyan date:2022-7-18 for: modal弹窗 支持评论 slot -->
  76. <template #[item]="data" v-for="item in Object.keys(omit($slots, 'default'))">
  77. <slot :name="item" v-bind="data || {}"></slot>
  78. </template>
  79. </Modal>
  80. </div>
  81. </template>
  82. <script lang="ts">
  83. import type { ModalProps, ModalMethods } from './typing';
  84. import { defineComponent, computed, ref, watch, unref, watchEffect, toRef, getCurrentInstance, nextTick } from 'vue';
  85. import Modal from './components/Modal';
  86. import ModalWrapper from './components/ModalWrapper.vue';
  87. import ModalClose from './components/ModalClose.vue';
  88. import ModalFooter from './components/ModalFooter.vue';
  89. import ModalHeader from './components/ModalHeader.vue';
  90. import { isFunction } from '/@/utils/is';
  91. import { deepMerge } from '/@/utils';
  92. import { basicProps } from './props';
  93. import { useFullScreen } from './hooks/useModalFullScreen';
  94. import { omit } from 'lodash-es';
  95. import { useDesign } from '/@/hooks/web/useDesign';
  96. export default defineComponent({
  97. name: 'BasicModal',
  98. components: { Modal, ModalWrapper, ModalClose, ModalFooter, ModalHeader },
  99. inheritAttrs: false,
  100. props: basicProps,
  101. emits: ['visible-change', 'height-change', 'cancel', 'ok', 'register', 'update:visible', 'fullScreen'],
  102. setup(props, { emit, attrs, slots }) {
  103. const visibleRef = ref(false);
  104. const propsRef = ref<Partial<ModalProps> | null>(null);
  105. const modalWrapperRef = ref<any>(null);
  106. const { prefixCls } = useDesign('basic-modal');
  107. // modal Bottom and top height
  108. const extHeightRef = ref(0);
  109. const modalMethods: ModalMethods = {
  110. setModalProps,
  111. emitVisible: undefined,
  112. redoModalHeight: () => {
  113. nextTick(() => {
  114. if (unref(modalWrapperRef)) {
  115. (unref(modalWrapperRef) as any).setModalHeight();
  116. }
  117. });
  118. },
  119. };
  120. const instance = getCurrentInstance();
  121. if (instance) {
  122. emit('register', modalMethods, instance.uid);
  123. }
  124. // Custom title component: get title
  125. const getMergeProps = computed((): Recordable => {
  126. return {
  127. ...props,
  128. ...(unref(propsRef) as any),
  129. };
  130. });
  131. //update-begin-author:liusq date:2023-05-25 for:【issues/4856】Modal控件设置 :title = null 无效
  132. //是否未设置标题
  133. const isNoTitle = computed(() => {
  134. //标题为空并且不含有标题插槽
  135. return !unref(getMergeProps).title && !slots.title;
  136. });
  137. //update-end-author:liusq date:2023-05-25 for:【issues/4856】Modal控件设置 :title = null 无效
  138. const { handleFullScreen, getWrapClassName, fullScreenRef } = useFullScreen({
  139. modalWrapperRef,
  140. extHeightRef,
  141. wrapClassName: toRef(getMergeProps.value, 'wrapClassName'),
  142. });
  143. // modal component does not need title and origin buttons
  144. const getProps = computed((): Recordable => {
  145. const opt = {
  146. ...unref(getMergeProps),
  147. visible: unref(visibleRef),
  148. okButtonProps: undefined,
  149. cancelButtonProps: undefined,
  150. title: undefined,
  151. };
  152. return {
  153. ...opt,
  154. wrapClassName: unref(getWrapClassName),
  155. };
  156. });
  157. const getBindValue = computed((): Recordable => {
  158. const attr = {
  159. ...attrs,
  160. ...unref(getMergeProps),
  161. visible: unref(visibleRef),
  162. wrapClassName: unref(getWrapClassName),
  163. };
  164. if (unref(fullScreenRef)) {
  165. return omit(attr, ['height', 'title']);
  166. }
  167. return omit(attr, 'title');
  168. });
  169. const getWrapperHeight = computed(() => {
  170. if (unref(fullScreenRef)) return undefined;
  171. return unref(getProps).height;
  172. });
  173. watchEffect(() => {
  174. fullScreenRef.value = !!props.defaultFullscreen;
  175. });
  176. watchEffect(() => {
  177. visibleRef.value = !!props.visible;
  178. });
  179. watch(
  180. () => unref(visibleRef),
  181. (v) => {
  182. emit('visible-change', v);
  183. emit('update:visible', v);
  184. instance && modalMethods.emitVisible?.(v, instance.uid);
  185. nextTick(() => {
  186. if (props.scrollTop && v && unref(modalWrapperRef)) {
  187. (unref(modalWrapperRef) as any).scrollTop();
  188. }
  189. });
  190. },
  191. {
  192. immediate: false,
  193. }
  194. );
  195. // 取消事件
  196. async function handleCancel(e: Event) {
  197. debugger;
  198. e?.stopPropagation();
  199. // 过滤自定义关闭按钮的空白区域
  200. if ((e.target as HTMLElement)?.classList?.contains(prefixCls + '-close--custom')) return;
  201. if (props.closeFunc && isFunction(props.closeFunc)) {
  202. const isClose: boolean = await props.closeFunc();
  203. visibleRef.value = !isClose;
  204. return;
  205. }
  206. visibleRef.value = false;
  207. emit('cancel', e);
  208. }
  209. /**
  210. * @description: 设置modal参数
  211. */
  212. function setModalProps(props: Partial<ModalProps>): void {
  213. // Keep the last setModalProps
  214. propsRef.value = deepMerge(unref(propsRef) || ({} as any), props);
  215. if (Reflect.has(props, 'visible')) {
  216. visibleRef.value = !!props.visible;
  217. }
  218. if (Reflect.has(props, 'defaultFullscreen')) {
  219. fullScreenRef.value = !!props.defaultFullscreen;
  220. }
  221. }
  222. function handleOk(e: Event) {
  223. emit('ok', e);
  224. }
  225. function handleHeightChange(height: string) {
  226. emit('height-change', height);
  227. }
  228. function handleExtHeight(height: number) {
  229. extHeightRef.value = height;
  230. }
  231. function handleTitleDbClick(e) {
  232. if (!props.canFullscreen) return;
  233. e.stopPropagation();
  234. handleFullScreen(e);
  235. }
  236. //update-begin-author:taoyan date:2022-7-18 for: modal支持评论 slot
  237. const commentSpan = ref(0);
  238. watch(
  239. () => props.enableComment,
  240. (flag) => {
  241. handleComment(flag);
  242. },
  243. { immediate: true }
  244. );
  245. function handleComment(flag) {
  246. if (flag === true) {
  247. commentSpan.value = 6;
  248. } else {
  249. commentSpan.value = 0;
  250. }
  251. }
  252. //update-end-author:taoyan date:2022-7-18 for: modal支持评论 slot
  253. // update-begin--author:liaozhiyang---date:20230804---for:【QQYUN-5866】放大行数自适应
  254. watch(fullScreenRef, (val) => {
  255. emit('fullScreen', val);
  256. });
  257. // update-begin--author:liaozhiyang---date:20230804---for:【QQYUN-5866】放大行数自适应
  258. return {
  259. handleCancel,
  260. getBindValue,
  261. getProps,
  262. handleFullScreen,
  263. fullScreenRef,
  264. getMergeProps,
  265. handleOk,
  266. visibleRef,
  267. omit,
  268. modalWrapperRef,
  269. handleExtHeight,
  270. handleHeightChange,
  271. handleTitleDbClick,
  272. getWrapperHeight,
  273. commentSpan,
  274. handleComment,
  275. isNoTitle,
  276. };
  277. },
  278. });
  279. </script>
  280. <style lang="less">
  281. @ventSpace: zxm;
  282. /*update-begin-author:taoyan date:2022-7-27 for:modal评论区域样式*/
  283. .jeecg-comment-outer {
  284. border-left: 1px solid #f0f0f0;
  285. .@{ventSpace}-tabs-nav-wrap {
  286. /* text-align: center;*/
  287. }
  288. }
  289. .jeecg-modal-content {
  290. > .scroll-container {
  291. padding: 14px;
  292. }
  293. }
  294. /*update-end-author:taoyan date:2022-7-27 for:modal评论区域样式*/
  295. // wrapper设为100%,兼容之前写过的弹窗自定义样式
  296. .jeecg-modal-wrapper,
  297. .jeecg-modal-content {
  298. height: 100%;
  299. }
  300. </style>