纵向表格高度根据最大高度自适应

前段时间项目中的一个商品原料对比页面需要优化一下,因为一些原料、价格、规格等一些参数可能不止一个,某些参数最多支持20个,我们需要将这些材料进行展示对比,初版时做的样子是不管它有多少个,只显示第一个,其余的利用饿了么组件库中的 Tooltip来显示,经过一段客户使用后,反馈说体验非常不友好,想要改成和市场上大部分对比一样,无论有多少的参数,全部都显示在页面上,并且在每一个前面加上一个小圆点,不需要鼠标放上面后再来显示所有的,这样看起来直观一点,因为当时做这个项目时时间比较紧,于是选择了纯手撸页面,没有用其他插件,具体就是将表头和每一列都分开,表头是始终固定在左侧,对比的列表是用的数组中的 slice方法来分割出来每一个添加的对比元素,使用v-for进行遍历每一个元素,那么,废话不多说,直接上代码吧

父组件

<div ref="tableRef" class="table-title">
                <div class="table-style table-fixed">
                     <!-- 表头 -->
                  <div
                    v-for="(h, i) in headers" :key="i" class="table-header" 
                    :style="setHeaderStyle(h)">
                    {{ h }}
                  </div>
                </div>
                <template v-if="!isEmpty(demandStore.contrastList)">
                    <!-- 子组件—对比的列表 -->
                  <comparison-list
                    v-for="item in slicedList"
                    ref="comparisonListRef"
                    :key="item.element.id"
                    v-model:single-height="singleHeight"
                    :item="item"
                    :remove="remove"
                  />
                </template>
              </div>
              <div style="position: fixed;top:0;z-index: 100">
                <fixed-header v-show="isFixed" class="fixed-top" :scroll-left="scrollLeft" />
              </div>
<script setup lang="ts">
 // 因为涉及表格数据,这里使用x1,x2等代替表头
 const headers = ref<string[]>([
  "x1",
  "x2",
  "x3",
  "x4",
  "x5",
  ……
])
// 容器高度
const singleHeight = reactive<any>({
  chineseHeight: '',
  inciHeight: '',
  unitHeight: '',
  specHeight: '',
  contentHeight: '',
})
const isFixed = ref<boolean>(false)
const tableRef = ref()
const scrollLeft = ref<number>(0)
function setHeaderStyle(header: string) {
    // 同步表头的高度
  if (header === 'x1') {
    return {
      height: !isEmpty(demandStore.contrastList) ? `${singleHeight.chineseHeight}` : '45px',
      lineHeight: !isEmpty(demandStore.contrastList) ? `${singleHeight.chineseHeight}` : '45px',
    }
  }
  else if (header === 'x2') {
    return {
      height: !isEmpty(demandStore.contrastList) ? `${singleHeight.inciHeight}` : '45px',
      lineHeight: !isEmpty(demandStore.contrastList) ? `${singleHeight.inciHeight}` : '45px',
    }
  }
  else if (header === 'x3' || header === 'x4') {
    return {
      height: !isEmpty(demandStore.contrastList) ? `${singleHeight.specHeight}` : '45px',
      lineHeight: !isEmpty(demandStore.contrastList) ? `${singleHeight.specHeight}` : '45px',
    }
  }
  else if (header === 'x5') {
    return {
      height: !isEmpty(demandStore.contrastList) ? `${singleHeight.contentHeight}` : '45px',
      lineHeight: !isEmpty(demandStore.contrastList) ? `${singleHeight.contentHeight}` : '45px',
    }
  }
  return {}
}
// 分割出来对应的数据——(0,1)(1,2)
const slicedList: ComputedRef<Contrast[]> = computed(() => {
  const list = demandStore.contrastList
  const contrastList = [] as any
  list.forEach((_, index) => {
    contrastList.push(list.slice(index, index + 1)[0])
  })
  return contrastList
})
onMounted(() => {
  // 超过一定高度时,将它固定到顶部
  window.addEventListener('scroll', () => {
    isFixed.value = document.documentElement.scrollTop >= 300
  })
  if (unref(tableRef)) {
    unref(tableRef).addEventListener('scroll', () => {
      scrollLeft.value = unref(tableRef).scrollLeft
    })
  }
})
</script>

子组件ComparisonList

子组件中我就拿其中一行的数据展示

<div class="table-content" :style="{ height: singleHeight.chineseHeight }">
        <div v-if="!isEmpty(compositionList)" style="margin-left:10px;">
          <div class="all-content">
            <p v-for="(e, index) in compositionList" :key="e.id" class="list-style-type" :style="{ height: `${heightInfo.totalEleCnHeight[index] || minHeight}px` }">
              {{ e.content ? `${e.elementChineseName}&nbsp;&nbsp;${e.content}%`
                : e.elementChineseName }}
            </p>
          </div>
        </div>
      </div>
const props = defineProps({
  singleHeight: {
    type: Object,
    default: () => ({
      chineseHeight: '',
      inciHeight: '',
      unitHeight: '',
      specHeight: '',
      contentHeight: '',
    }),
  },
})
const emits = defineEmits(['update:singleHeight'])
const { minHeight, height, maxHeight, calculateCompositionListHeights } = useCalculateElementHeight()
const heightInfo = reactive<any>({
  totalEleCnHeight: [],
  totalInciHeight: [],
  totalUnitHeight: [],
  totalEleSpecHeight: [],
  totalEleContentHeight: [],
})
const singleHeight = reactive<any>(props.singleHeight)
const containerHeight = computed(() => {
const elements: ElementInContrast[] = demandStore.contrastList.map((item: Contrast) => {
    if (isEmpty(item.element.compositionList)) {
      // 合并 contrastList
      const elementDetail = item.element.elementDetail
      const { elementChineseName, inci, id, elementId, cas } = elementDetail
      // 检查 compositionList 中是否已存在相同的项
      const exists = item.element.compositionList.some(entry => entry.elementChineseName === elementChineseName && entry.inci === inci)
      if (!exists) {
        // 合并 compositionList
        item.element.compositionList.push({ id: `${id}`, elementId: `${elementId}`, elementChineseName, inci, cas })
      }
    }
    if (isEmpty(item.element.priceList)) {
      const elementDetail = item.element.elementDetail
      item.element.priceList.push({
        elementId: elementDetail.elementId,
        id: elementDetail.id,
        packingSpecification: '面议',
        unit: '面议',
        unitPrice: '面议',
      })
    }
    else {
      // 不是面议的将单位转换成对应的中文
      item.element.priceList.forEach((item) => {
        if (item.unit !== '面议')
          unitConversion(item)
      })
    }
    if (isEmpty(item.element.elementIngredientList)) {
      item.element.elementIngredientList.push({
        elementId: `${elementDetail.elementId}`,
        id: `${elementDetail.id}`,
        name: '-',
        percentageComposition: 0,
        percentageCompositionStr: '-',
      })
    }
    return item.element
  })
  const {
    maxEleCnLengths,
    maxInciLengths,
    totalEleCnHeight,
    totalInciHeight,
    maxEleSpeciLengths,
    maxUnitLengths,
    totalUnitHeight,
    totalEleSpecHeight,
    totalEleContentHeight,
    maxContentLengths,
  }
    = calculateCompositionListHeights(elements)
  singleHeight.chineseHeight = `${totalEleCnHeight}px`
  singleHeight.unitHeight = `${totalUnitHeight}px`
  singleHeight.specHeight = `${totalEleSpecHeight}px`
  singleHeight.inciHeight = `${totalInciHeight}px`
  singleHeight.contentHeight = `${totalEleContentHeight}px`
  heightInfo.totalInciHeight = maxInciLengths
  heightInfo.totalEleCnHeight = maxEleCnLengths
  heightInfo.totalUnitHeight = maxUnitLengths
  heightInfo.totalEleSpecHeight = maxEleSpeciLengths
  heightInfo.totalEleContentHeight = maxContentLengths
  return `${totalInciHeight}px`
})
watch(() => singleHeight, (newValue) => {
  emits('update:singleHeight', newValue)
}, {
  immediate: true,
})

抽取的Hooks—useCalculateElementHeight

import type { ElementInContrast, ElementIngredientList, PicList } from '@/views/demand/types'
import type { Composition } from '@/views/element/types'
​
export function useCalculateElementHeight() {
  const minHeight = 20 // 最小高度
  const height = 30 // 正常单列的高度
  const maxHeight = 35 // 最大单列高度
  const cellPadding = 14 // padding 7px 0
  const width = 230 // 固定单列的宽度
  const fontSize = '14px'
  function createTemporaryElement(innerHTML: string): HTMLElement {
    const pElement = document.createElement('p')
    pElement.style.width = `${width}px`
    pElement.style.position = 'absolute'
    pElement.style.visibility = 'hidden'
    pElement.style.whiteSpace = 'break-all'
    pElement.style.fontSize = fontSize
    pElement.innerHTML = innerHTML
    document.body.appendChild(pElement)
    return pElement
  }
​
  function getElementHeight(innerHTML: string) {
    const tempElement = createTemporaryElement(innerHTML)
    const height = tempElement.offsetHeight
    document.body.removeChild(tempElement)
    return height
  }
​
  function calculateCompositionListHeights(data: ElementInContrast[]) {
    const compResults = data.map((item: ElementInContrast) => {
      // compositionList(中文名及inci名称)
      const compositionList = item.compositionList
      // 价格列表
      const priceList = item.priceList
      // 主要有效成分及含量列表
      const contentList = item.elementIngredientList
​
      // 拼接对应的字符串并计算长度
      const concatenatedLengths = compositionList.map((e: Composition) => {
        const eleCnString = e.content ? `${e.elementChineseName}&nbsp;&nbsp;${e.content}%` : e.elementChineseName
        const inciString = e.content ? `${e.inci}&nbsp;&nbsp;${e.content}%` : e.inci
​
        const eleCnHeight = getElementHeight(eleCnString)
        const inciHeight = getElementHeight(inciString)
        // 确保高度至少为 minHeight
        return {
          eleCnLength: Math.max(eleCnHeight, minHeight),
          inciLength: Math.max(inciHeight, minHeight),
        }
      })
​
      const priceConcatenatedLengths = priceList.map((p: PicList) => {
        const specificationStr = p.packingSpecification + p.unit
        const unitPriceStr = `${p.unitPrice}元/${p.unit}`
​
        const eleSpceHeight = getElementHeight(specificationStr)
        const unitHeight = getElementHeight(unitPriceStr)
        // 确保高度至少为 height
        return {
          specificationLength: Math.max(eleSpceHeight, height),
          unitLength: Math.max(unitHeight, height),
        }
      })
​
      const contentLengths = contentList.map((p: ElementIngredientList) => {
        const contentStr = `${p.name}:${p.percentageCompositionStr}%`
​
        const eleContentHeight = getElementHeight(contentStr)
        // 确保高度至少为 height
        return {
          contentLength: Math.max(eleContentHeight, height),
        }
      })
​
      return {
        compositionListLength: compositionList.length,
        concatenatedLengths,
        priceConcatenatedLengths,
        priceList: priceList.length,
        contentLengths,
        contentList: contentList.length,
      }
    })
    // 找到最大的长度
    const maxCompositionListLength = Math.max(...compResults.map(r => r.compositionListLength))
    const maxPriceLength = Math.max(...compResults.map(r => r.priceList))
    const maxContentLength = Math.max(...compResults.map(r => r.contentList))
    // 对比每个 compositionList 中相同索引的元素的字符串长度
    const maxEleCnLengths = []
    const maxInciLengths = []
    let totalEleCnHeight = 0
    let totalInciHeight = 0
    // 对比每个价格中相同索引的元素字符串长度
    const maxEleSpeciLengths = []
    const maxUnitLengths = []
    let totalEleSpecHeight = 0
    let totalUnitHeight = 0
    // 对比主要有效成分及含量中相同索引的元素字符串长度
    const maxContentLengths = []
    let totalEleContentHeight = 0
    // compositionList
    for (let i = 0; i < maxCompositionListLength; i++) {
      let maxEleCnLength = 0
      let maxInciLength = 0
      compResults.forEach((result) => {
        if (result.concatenatedLengths[i] !== undefined) {
          maxEleCnLength = Math.max(maxEleCnLength, result.concatenatedLengths[i].eleCnLength)
          maxInciLength = Math.max(maxInciLength, result.concatenatedLengths[i].inciLength)
        }
      })
      maxEleCnLengths.push(maxEleCnLength)
      maxInciLengths.push(maxInciLength)
      totalEleCnHeight += maxEleCnLength
      totalInciHeight += maxInciLength
    }
    totalInciHeight += maxCompositionListLength * cellPadding
    totalEleCnHeight += maxCompositionListLength * cellPadding
    // 价格
    for (let i = 0; i < maxPriceLength; i++) {
      let maxEleSpeciLength = 0
      let maxUnitLength = 0
      compResults.forEach((result) => {
        if (result.priceConcatenatedLengths[i] !== undefined) {
          maxEleSpeciLength = Math.max(maxEleSpeciLength, result.priceConcatenatedLengths[i].specificationLength)
          maxUnitLength = Math.max(maxUnitLength, result.priceConcatenatedLengths[i].unitLength)
        }
      })
      maxEleSpeciLengths.push(maxEleSpeciLength)
      maxUnitLengths.push(maxUnitLength)
      totalEleSpecHeight += maxEleSpeciLength
      totalUnitHeight += maxUnitLength
    }
    totalUnitHeight += maxPriceLength * cellPadding
    totalEleSpecHeight += maxPriceLength * cellPadding
    // 主要有效成分及含量
    for (let i = 0; i < maxContentLength; i++) {
      let maxEleContentLength = 0
      compResults.forEach((result) => {
        if (result.contentLengths[i] !== undefined)
          maxEleContentLength = Math.max(maxEleContentLength, result.contentLengths[i].contentLength)
      })
      maxContentLengths.push(maxEleContentLength)
      totalEleContentHeight += maxEleContentLength
    }
    totalEleContentHeight += maxContentLength * cellPadding
​
    return {
      compResults,
      maxCompositionListLength,
      maxEleCnLengths,
      maxInciLengths,
      totalEleCnHeight,
      totalInciHeight,
      maxPriceLength,
      maxEleSpeciLengths,
      maxUnitLengths,
      totalUnitHeight,
      totalEleSpecHeight,
      maxContentLengths,
      totalEleContentHeight,
    }
  }
  return {
    minHeight,
    height,
    maxHeight,
    calculateCompositionListHeights,
  }
}
​

最终效果(带小圆点的)


标题:纵向表格高度根据最大高度自适应
作者:mcwu
地址:http://mcongblog.com/articles/2024/08/13/1723518323759.html

    评论
    0 评论
avatar

取消