BasicModal.vue 10 KB

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