page.vue 2.4 KB

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