PageWrapper.vue 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. <template>
  2. <div :class="getClass" ref="wrapperRef">
  3. <PageHeader
  4. :ghost="ghost"
  5. :title="title"
  6. v-bind="omit($attrs, 'class')"
  7. :style="getHeaderStyle"
  8. ref="headerRef"
  9. v-if="getShowHeader"
  10. >
  11. <template #default>
  12. <template v-if="content">
  13. {{ content }}
  14. </template>
  15. <slot name="headerContent" v-else></slot>
  16. </template>
  17. <template #[item]="data" v-for="item in getHeaderSlots">
  18. <slot :name="item" v-bind="data || {}"></slot>
  19. </template>
  20. </PageHeader>
  21. <div class="overflow-hidden" :class="getContentClass" :style="getContentStyle" ref="contentRef">
  22. <slot></slot>
  23. </div>
  24. <PageFooter v-if="getShowFooter" ref="footerRef">
  25. <template #left>
  26. <slot name="leftFooter"></slot>
  27. </template>
  28. <template #right>
  29. <slot name="rightFooter"></slot>
  30. </template>
  31. </PageFooter>
  32. </div>
  33. </template>
  34. <script lang="ts" setup>
  35. import { PageWrapperFixedHeightKey } from '@/enums/pageEnum';
  36. import { useContentHeight } from '@/hooks/web/useContentHeight';
  37. import { useDesign } from '@/hooks/web/useDesign';
  38. import { propTypes } from '@/utils/propTypes';
  39. import { PageHeader } from 'ant-design-vue';
  40. import { omit } from 'lodash-es';
  41. import {
  42. CSSProperties,
  43. PropType,
  44. computed,
  45. provide,
  46. ref,
  47. unref,
  48. useAttrs,
  49. useSlots,
  50. watch,
  51. } from 'vue';
  52. import PageFooter from './PageFooter.vue';
  53. defineOptions({
  54. name: 'PageWrapper',
  55. inheritAttrs: false,
  56. });
  57. const props = defineProps({
  58. title: propTypes.string,
  59. dense: propTypes.bool,
  60. ghost: propTypes.bool,
  61. headerSticky: propTypes.bool,
  62. headerStyle: Object as PropType<CSSProperties>,
  63. content: propTypes.string,
  64. contentStyle: {
  65. type: Object as PropType<CSSProperties>,
  66. },
  67. contentBackground: propTypes.bool,
  68. contentFullHeight: propTypes.bool.def(false),
  69. contentClass: propTypes.string,
  70. fixedHeight: propTypes.bool,
  71. upwardSpace: propTypes.oneOfType([propTypes.number, propTypes.string]).def(0),
  72. });
  73. const attrs = useAttrs();
  74. const slots = useSlots();
  75. const wrapperRef = ref(null);
  76. const headerRef = ref(null);
  77. const contentRef = ref(null);
  78. const footerRef = ref(null);
  79. const { prefixCls } = useDesign('page-wrapper');
  80. provide(
  81. PageWrapperFixedHeightKey,
  82. computed(() => props.fixedHeight),
  83. );
  84. const getIsContentFullHeight = computed(() => {
  85. return props.contentFullHeight;
  86. });
  87. const getUpwardSpace = computed(() => props.upwardSpace);
  88. const { redoHeight, setCompensation, contentHeight } = useContentHeight(
  89. getIsContentFullHeight,
  90. wrapperRef,
  91. [headerRef, footerRef],
  92. [contentRef],
  93. getUpwardSpace,
  94. );
  95. setCompensation({ useLayoutFooter: true, elements: [footerRef] });
  96. const getClass = computed(() => {
  97. return [
  98. prefixCls,
  99. {
  100. [`${prefixCls}--dense`]: props.dense,
  101. },
  102. attrs.class ?? {},
  103. ];
  104. });
  105. const getHeaderStyle = computed((): CSSProperties => {
  106. const { headerSticky } = props;
  107. if (!headerSticky) {
  108. return {};
  109. }
  110. return {
  111. position: 'sticky',
  112. top: 0,
  113. zIndex: 99,
  114. ...props.headerStyle,
  115. };
  116. });
  117. const getShowHeader = computed(
  118. () => props.content || slots?.headerContent || props.title || getHeaderSlots.value.length,
  119. );
  120. const getShowFooter = computed(() => slots?.leftFooter || slots?.rightFooter);
  121. const getHeaderSlots = computed(() => {
  122. return Object.keys(omit(slots, 'default', 'leftFooter', 'rightFooter', 'headerContent'));
  123. });
  124. const getContentStyle = computed((): CSSProperties => {
  125. const { contentFullHeight, contentStyle, fixedHeight } = props;
  126. if (!contentFullHeight) {
  127. return { ...contentStyle };
  128. }
  129. const height = `${unref(contentHeight)}px`;
  130. return {
  131. ...contentStyle,
  132. minHeight: height,
  133. ...(fixedHeight ? { height } : {}),
  134. };
  135. });
  136. const getContentClass = computed(() => {
  137. const { contentBackground, contentClass } = props;
  138. return [
  139. `${prefixCls}-content`,
  140. contentClass,
  141. {
  142. [`${prefixCls}-content-bg`]: contentBackground,
  143. },
  144. ];
  145. });
  146. watch(
  147. () => [getShowFooter.value],
  148. () => {
  149. redoHeight();
  150. },
  151. {
  152. flush: 'post',
  153. immediate: true,
  154. },
  155. );
  156. </script>
  157. <style lang="less">
  158. @prefix-cls: ~'@{namespace}-page-wrapper';
  159. .@{prefix-cls} {
  160. position: relative;
  161. .@{prefix-cls}-content {
  162. margin: 16px;
  163. }
  164. .ant-page-header {
  165. &:empty {
  166. padding: 0;
  167. }
  168. }
  169. &-content-bg {
  170. background-color: @component-background;
  171. }
  172. &--dense {
  173. .@{prefix-cls}-content {
  174. margin: 0;
  175. }
  176. }
  177. }
  178. </style>