page.vue 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  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
  92. ? 'border-border border-b transition-all duration-200'
  93. : '',
  94. )
  95. "
  96. :style="headerStyle"
  97. >
  98. <slot name="title">
  99. <div v-if="title" class="mb-2 flex text-lg font-semibold">
  100. {{ title }}
  101. </div>
  102. </slot>
  103. <slot name="description">
  104. <p v-if="description" class="text-muted-foreground">
  105. {{ description }}
  106. </p>
  107. </slot>
  108. <div v-if="$slots.extra" class="absolute bottom-4 right-4">
  109. <slot name="extra"></slot>
  110. </div>
  111. </div>
  112. <div :class="contentClass" :style="contentStyle" class="h-full p-4">
  113. <slot></slot>
  114. </div>
  115. <div
  116. v-if="$slots.footer"
  117. ref="footerRef"
  118. :class="
  119. cn(
  120. footerClass,
  121. 'bg-card align-center absolute bottom-0 left-0 right-0 flex px-6 py-4',
  122. )
  123. "
  124. >
  125. <slot name="footer"></slot>
  126. </div>
  127. </div>
  128. </template>