page.vue 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. <script setup lang="ts">
  2. import type { StyleValue } from 'vue';
  3. import type { PageProps } from './types';
  4. import { computed, nextTick, onMounted, ref, useTemplateRef } from 'vue';
  5. import { CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT } from '@vben-core/shared/constants';
  6. import { cn } from '@vben-core/shared/utils';
  7. defineOptions({
  8. name: 'Page',
  9. });
  10. const { autoContentHeight = false, heightOffset = 0, footerFixed = false } =
  11. defineProps<PageProps>();
  12. const headerHeight = ref(0);
  13. const footerHeight = ref(0);
  14. const shouldAutoHeight = ref(false);
  15. const headerRef = useTemplateRef<HTMLDivElement>('headerRef');
  16. const footerRef = useTemplateRef<HTMLDivElement>('footerRef');
  17. const contentStyle = computed<StyleValue>(() => {
  18. if (autoContentHeight) {
  19. return {
  20. height: `calc(var(${CSS_VARIABLE_LAYOUT_CONTENT_HEIGHT}) - ${headerHeight.value}px - ${footerHeight.value}px - ${typeof heightOffset === 'number' ? `${heightOffset}px` : heightOffset})`,
  21. overflowY: shouldAutoHeight.value ? 'auto' : 'unset',
  22. };
  23. }
  24. return {};
  25. });
  26. async function calcContentHeight() {
  27. if (!autoContentHeight) {
  28. return;
  29. }
  30. shouldAutoHeight.value = false;
  31. await nextTick();
  32. headerHeight.value = headerRef.value?.offsetHeight || 0;
  33. footerHeight.value = footerFixed ? 0 : (footerRef.value?.offsetHeight || 0);
  34. setTimeout(() => {
  35. shouldAutoHeight.value = true;
  36. }, 30);
  37. }
  38. onMounted(() => {
  39. calcContentHeight();
  40. });
  41. </script>
  42. <template>
  43. <div class="relative flex min-h-full flex-col">
  44. <div
  45. v-if="
  46. description ||
  47. $slots.description ||
  48. title ||
  49. $slots.title ||
  50. $slots.extra
  51. "
  52. ref="headerRef"
  53. :class="
  54. cn(
  55. 'relative flex items-end border-b border-border bg-card px-6 py-4',
  56. headerClass,
  57. )
  58. "
  59. >
  60. <div class="flex-auto">
  61. <slot name="title">
  62. <div v-if="title" class="mb-2 flex text-lg font-semibold">
  63. {{ title }}
  64. </div>
  65. </slot>
  66. <slot name="description">
  67. <p v-if="description" class="text-muted-foreground">
  68. {{ description }}
  69. </p>
  70. </slot>
  71. </div>
  72. <div v-if="$slots.extra">
  73. <slot name="extra"></slot>
  74. </div>
  75. </div>
  76. <div :class="cn('h-full p-4', contentClass)" :style="contentStyle">
  77. <slot></slot>
  78. </div>
  79. <div
  80. v-if="$slots.footer"
  81. ref="footerRef"
  82. :class="cn('align-center flex bg-card px-6 py-4', footerClass)"
  83. >
  84. <slot name="footer"></slot>
  85. </div>
  86. </div>
  87. </template>