纵向表格高度根据最大高度自适应
前段时间项目中的一个商品原料对比页面需要优化一下,因为一些原料、价格、规格等一些参数可能不止一个,某些参数最多支持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} ${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} ${e.content}%` : e.elementChineseName
const inciString = e.content ? `${e.inci} ${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,
}
}
最终效果(带小圆点的)
评论
0 评论