index.vue 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. <template>
  2. <div :style="style">
  3. <slot />
  4. </div>
  5. </template>
  6. <script setup lang="ts" name="Grid">
  7. import {
  8. ref,
  9. watch,
  10. useSlots,
  11. computed,
  12. provide,
  13. onBeforeMount,
  14. onMounted,
  15. onUnmounted,
  16. onDeactivated,
  17. onActivated,
  18. VNodeArrayChildren,
  19. VNode
  20. } from "vue";
  21. import type { BreakPoint } from "./interface/index";
  22. type Props = {
  23. cols?: number | Record<BreakPoint, number>;
  24. collapsed?: boolean;
  25. collapsedRows?: number;
  26. gap?: [number, number] | number;
  27. };
  28. const props = withDefaults(defineProps<Props>(), {
  29. cols: () => ({ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }),
  30. collapsed: false,
  31. collapsedRows: 1,
  32. gap: 0
  33. });
  34. onBeforeMount(() => props.collapsed && findIndex());
  35. onMounted(() => {
  36. resize({ target: { innerWidth: window.innerWidth } } as unknown as UIEvent);
  37. window.addEventListener("resize", resize);
  38. });
  39. onActivated(() => {
  40. resize({ target: { innerWidth: window.innerWidth } } as unknown as UIEvent);
  41. window.addEventListener("resize", resize);
  42. });
  43. onUnmounted(() => {
  44. window.removeEventListener("resize", resize);
  45. });
  46. onDeactivated(() => {
  47. window.removeEventListener("resize", resize);
  48. });
  49. // 监听屏幕变化
  50. const resize = (e: UIEvent) => {
  51. let width = (e.target as Window).innerWidth;
  52. switch (!!width) {
  53. case width < 768:
  54. breakPoint.value = "xs";
  55. break;
  56. case width >= 768 && width < 992:
  57. breakPoint.value = "sm";
  58. break;
  59. case width >= 992 && width < 1200:
  60. breakPoint.value = "md";
  61. break;
  62. case width >= 1200 && width < 1920:
  63. breakPoint.value = "lg";
  64. break;
  65. case width >= 1920:
  66. breakPoint.value = "xl";
  67. break;
  68. }
  69. };
  70. // 注入 gap 间距
  71. provide("gap", Array.isArray(props.gap) ? props.gap[0] : props.gap);
  72. // 注入响应式断点
  73. let breakPoint = ref<BreakPoint>("xl");
  74. provide("breakPoint", breakPoint);
  75. // 注入要开始折叠的 index
  76. const hiddenIndex = ref(-1);
  77. provide("shouldHiddenIndex", hiddenIndex);
  78. // 注入 cols
  79. const gridCols = computed(() => {
  80. if (typeof props.cols === "object") return props.cols[breakPoint.value] ?? props.cols;
  81. return props.cols;
  82. });
  83. provide("cols", gridCols);
  84. // 寻找需要开始折叠的字段 index
  85. const slots = useSlots().default?.({});
  86. const findIndex = () => {
  87. let fields: VNodeArrayChildren = [];
  88. let suffix: VNode | null = null;
  89. slots?.forEach((slot: any) => {
  90. // suffix
  91. if (typeof slot.type === "object" && slot.type.name === "GridItem" && slot.props?.suffix !== undefined) suffix = slot;
  92. // slot children
  93. if (typeof slot.type === "symbol" && Array.isArray(slot.children)) fields.push(...slot.children);
  94. });
  95. // 计算 suffix 所占用的列
  96. let suffixCols = 0;
  97. if (suffix) {
  98. suffixCols =
  99. ((suffix as VNode).props![breakPoint.value]?.span ?? (suffix as VNode).props?.span ?? 1) +
  100. ((suffix as VNode).props![breakPoint.value]?.offset ?? (suffix as VNode).props?.offset ?? 0);
  101. }
  102. try {
  103. let find = false;
  104. fields.reduce((prev = 0, current, index) => {
  105. prev +=
  106. ((current as VNode)!.props![breakPoint.value]?.span ?? (current as VNode)!.props?.span ?? 1) +
  107. ((current as VNode)!.props![breakPoint.value]?.offset ?? (current as VNode)!.props?.offset ?? 0);
  108. if (Number(prev) > props.collapsedRows * gridCols.value - suffixCols) {
  109. hiddenIndex.value = index;
  110. find = true;
  111. throw "find it";
  112. }
  113. return prev;
  114. }, 0);
  115. if (!find) hiddenIndex.value = -1;
  116. } catch (e) {
  117. // console.warn(e);
  118. }
  119. };
  120. // 断点变化时执行 findIndex
  121. watch(
  122. () => breakPoint.value,
  123. () => {
  124. if (props.collapsed) findIndex();
  125. }
  126. );
  127. // 监听 collapsed
  128. watch(
  129. () => props.collapsed,
  130. value => {
  131. if (value) return findIndex();
  132. hiddenIndex.value = -1;
  133. }
  134. );
  135. // 设置间距
  136. const gridGap = computed(() => {
  137. if (typeof props.gap === "number") return `${props.gap}px`;
  138. if (Array.isArray(props.gap)) return `${props.gap[1]}px ${props.gap[0]}px`;
  139. return "unset";
  140. });
  141. // 设置 style
  142. const style = computed(() => {
  143. return {
  144. display: "grid",
  145. gridGap: gridGap.value,
  146. gridTemplateColumns: `repeat(${gridCols.value}, minmax(0, 1fr))`
  147. };
  148. });
  149. defineExpose({ breakPoint });
  150. </script>