page.vue 2.6 KB

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