page.vue 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  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 { preferences } from '@vben-core/preferences';
  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. /** 头部固定 */
  21. fixedHeader?: boolean;
  22. headerClass?: string;
  23. footerClass?: string;
  24. }
  25. defineOptions({
  26. name: 'Page',
  27. });
  28. const {
  29. contentClass = '',
  30. description = '',
  31. autoContentHeight = false,
  32. title = '',
  33. fixedHeader = false,
  34. } = defineProps<Props>();
  35. const headerHeight = ref(0);
  36. const footerHeight = ref(0);
  37. const shouldAutoHeight = ref(false);
  38. const headerRef = useTemplateRef<HTMLDivElement>('headerRef');
  39. const footerRef = useTemplateRef<HTMLDivElement>('footerRef');
  40. const headerStyle = computed<StyleValue>(() => {
  41. return fixedHeader
  42. ? {
  43. position: 'sticky',
  44. zIndex: 200,
  45. top:
  46. preferences.header.mode === 'fixed' ? 'var(--vben-header-height)' : 0,
  47. }
  48. : undefined;
  49. });
  50. const contentStyle = computed(() => {
  51. if (autoContentHeight) {
  52. return {
  53. height: shouldAutoHeight.value
  54. ? `calc(var(--vben-content-height) - ${headerHeight.value}px - ${footerHeight.value}px)`
  55. : '0',
  56. // 'overflow-y': shouldAutoHeight.value?'auto':'unset',
  57. };
  58. }
  59. return {};
  60. });
  61. async function calcContentHeight() {
  62. if (!autoContentHeight) {
  63. return;
  64. }
  65. await nextTick();
  66. headerHeight.value = headerRef.value?.offsetHeight || 0;
  67. footerHeight.value = footerRef.value?.offsetHeight || 0;
  68. setTimeout(() => {
  69. shouldAutoHeight.value = true;
  70. }, 30);
  71. }
  72. onMounted(() => {
  73. calcContentHeight();
  74. });
  75. </script>
  76. <template>
  77. <div class="relative">
  78. <div
  79. v-if="
  80. description ||
  81. $slots.description ||
  82. title ||
  83. $slots.title ||
  84. $slots.extra
  85. "
  86. ref="headerRef"
  87. :class="
  88. cn(
  89. 'bg-card relative px-6 py-4',
  90. headerClass,
  91. fixedHeader ? 'border-border border-b' : '',
  92. )
  93. "
  94. :style="headerStyle"
  95. >
  96. <slot name="title">
  97. <div v-if="title" class="mb-2 flex text-lg font-semibold">
  98. {{ title }}
  99. </div>
  100. </slot>
  101. <slot name="description">
  102. <p v-if="description" class="text-muted-foreground">
  103. {{ description }}
  104. </p>
  105. </slot>
  106. <div v-if="$slots.extra" class="absolute bottom-4 right-4">
  107. <slot name="extra"></slot>
  108. </div>
  109. </div>
  110. <div :class="contentClass" :style="contentStyle" class="h-full p-4">
  111. <slot></slot>
  112. </div>
  113. <div
  114. v-if="$slots.footer"
  115. ref="footerRef"
  116. :class="
  117. cn(
  118. footerClass,
  119. 'bg-card align-center absolute bottom-0 left-0 right-0 flex px-6 py-4',
  120. )
  121. "
  122. >
  123. <slot name="footer"></slot>
  124. </div>
  125. </div>
  126. </template>