Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save xinglongjizi/29b61ff5f13075833056dbd4dc0f72f9 to your computer and use it in GitHub Desktop.

Select an option

Save xinglongjizi/29b61ff5f13075833056dbd4dc0f72f9 to your computer and use it in GitHub Desktop.
/*
背景el-table在渲染超过1000个td的场景下会出现性能问题:
1. 初始渲染时间加长
2. 更改某个数据,vue内部进行大量的js计算,阻塞渲染主线程,造成浏览器几秒卡死,不可交互
这是由于vue的虚拟dom渲染原理,即使一棵虚拟dom树的某个部分从未改变,还是会在每次重渲染时创建新的 vnode,带来了大量不必要的内存压力和性能问题
解决方案,只能上技巧:
让el-table中的表格内容尽量简单,只做简单的静态数据渲染,交互都脱离el-table
比如表格中的数据编辑:点击某个td,触发在td编辑数据
技巧:以编辑数据,单独谈一个编辑modal为启发,可以做一个蒙层,在这个蒙层中做编辑的ui,根据点击的td的位置信息,将编辑ui定位到td位置重合
为了不因为改了一个数据,又触发el-table重新渲染,直接使用dom操作更改数据
并且,传入el-table的数据都是经过Object.freeze的
*/
<template>
<Teleport to="body">
<div class="input-lay" v-show="showInputLay">
<form
class="number-input-wrapper"
:style="inputPosition"
@submit="numberSubmit"
>
<input
ref="numInput"
type="number"
min="0"
max="99999999"
step="1"
@keydown="numberKeyDown"
@change="numberChange"
@blur="numberBlur"
/>
</form>
</div>
</Teleport>
<el-table
class="hi-common-el-table"
:data="data"
row-key="id"
border
:max-height="maxHeight"
default-expand-all
@cell-click="cellClick"
show-summary
:summary-method="getSummaries"
>
<el-table-column
prop="name"
label="子系统/模块"
min-width="200"
fixed
show-overflow-tooltip
resizable
>
</el-table-column>
<el-table-column
v-for="col in cols"
:key="col.weekDate"
:prop="col.weekDate"
width="110"
align="center"
:resizable="false"
>
<template #header>
<div class="col-name">
<span>{{ col.week }}</span>
<span>{{ col.weekDate }}</span>
</div>
</template>
<template #default="{ row }">
<span class="is-modified" v-if="row[col.weekDate + isModifiedKey]"></span>
<span :class="calcClass(row, col.weekDate)">{{ row[col.weekDate] }}</span>
</template>
</el-table-column>
</el-table>
</template>
<script lang="ts" setup>
import { ref, reactive, computed, nextTick, h } from 'vue'
import type { VNode } from 'vue'
import { useWindowSize } from '@vueuse/core'
import type { TableColumnCtx } from 'element-plus'
import { ElMessage } from 'element-plus'
import {
httpEditPredictionData,
} from '@/api/issueTrendPrediction/editTrendPredictionDataModal'
import type {
PureObj,
} from '../types'
import { storeToRefs } from 'pinia'
import { LoginStore } from '@/store/modules/login'
import { useIssueTrendPredictionPageInfoStore } from '../store/modules/pageInfo'
const loginStore = LoginStore()
const issueTrendPredictionPageInfoStore = useIssueTrendPredictionPageInfoStore()
const { planCode } = storeToRefs(issueTrendPredictionPageInfoStore)
const { height } = useWindowSize()
const isModifiedKey = '_isInput'
const props = defineProps<{
cols: PureObj[]
data: PureObj[]
}>()
const data = computed(() => {
return Object.freeze(props.data)
})
const cols = computed(() => {
return Object.freeze(props.cols)
})
const maxHeight = computed(() => {
return height.value - 50 - 25 - 50 - 50 - 50
})
const showInputLay = ref<boolean>(false)
const numInput = ref<HTMLInputElement>()
const editRow = ref<PureObj | null>(null)
const editColProp = ref<string>('')
const changeDataMap = new Map()
interface StylePotions {
top: string
left: string
}
const inputPosition: StylePotions = reactive({
top: '0px',
left: '0px',
})
const calcClass = (row: PureObj, weekDate: string) => {
if (row.level) {
return 'can-edit'
} else {
return 'sys-span' + row.code + weekDate
}
}
let $editSpan: HTMLSpanElement
let editOldValue: number
async function cellClick(row: PureObj, column: PureObj, cell: HTMLTableCellElement) {
if (column.property === 'name' || row.level === 0) {
return
}
showInputLay.value = true
editRow.value = row
editColProp.value = column.property
const rect = cell.getBoundingClientRect()
const $input = numInput.value as HTMLInputElement
inputPosition.top = rect.top + 'px'
inputPosition.left = rect.left + 'px'
await nextTick()
$input.focus()
$editSpan = cell.querySelector('.can-edit') as HTMLSpanElement
$input.value = $editSpan.textContent ?? ''
editOldValue = $editSpan.textContent ? Number($editSpan.textContent) : 0
}
const numberKeyDown = async (e: KeyboardEvent) => {
const pattern = /^[^\d]$/
if (pattern.test(e.key)) {
e.preventDefault()
}
}
const numberSubmit = (e: Event) => {
e.preventDefault()
showInputLay.value = false
}
const numberBlur = () => {
showInputLay.value = false
}
const numberChange = (e: Event) => {
const $this = e.target as HTMLInputElement
if ($this.value) {
$this.stepUp()
$this.stepDown()
}
calcResult()
}
const calcResult = () => {
const $this = numInput.value as HTMLInputElement
$editSpan.textContent = $this.value
const editValue = $this.value ? Number($this.value) : 0
const editChangedValue = editValue - editOldValue
const $sysSpan = document.querySelector(`.sys-span${editRow.value?.parentCode}${editColProp.value}`) as HTMLSpanElement
const sysOldVal = $sysSpan.textContent ? Number($sysSpan.textContent) : 0
$sysSpan.textContent = sysOldVal + editChangedValue + ''
const $summarySpan = document.querySelector(`.summary-cell${editColProp.value}`) as HTMLSpanElement
const summaryOldVal = $summarySpan.textContent ? Number($summarySpan.textContent) : 0
$summarySpan.textContent = summaryOldVal + editChangedValue + ''
const mapKey = editRow.value?.parentCode + editRow.value?.code + editColProp.value
const mapVal = {
projectSelfCode: loginStore.projectSelfCode,
groupCode: planCode.value,
subSystem: editRow.value?.parentCode,
module: editRow.value?.code,
weekTime: editColProp.value,
inputData: $this.value ? Number($this.value) : null,
}
changeDataMap.set(mapKey, mapVal)
}
const saveData = async () => {
if (changeDataMap.size === 0) {
return
}
const res = await httpEditPredictionData([...changeDataMap.values()])
const { data } = res
if (data?.success) {
ElMessage.success('success')
} else {
ElMessage.error('出错了,请重试,或联系支撑人员')
}
}
defineExpose({
saveData,
})
interface SummaryMethodProps<T = PureObj> {
columns: TableColumnCtx<T>[]
data: T[]
}
const getSummaries = (param: SummaryMethodProps) => {
const { columns, data } = param
const sums: (number | string | VNode)[] = []
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = '总计'
return
}
const values = data.map((item: PureObj) => {
let sum = 0
item.children.forEach((child: PureObj) => {
sum += Number(child[column.property] || 0)
})
item[column.property] = sum
return sum
})
if (!values.every((value) => Number.isNaN(value))) {
sums[index] = values.reduce((prev, curr) => {
const value = Number(curr)
if (!Number.isNaN(value)) {
return prev + curr
} else {
return prev
}
}, 0)
} else {
sums[index] = 'N/A'
}
})
return sums.map((val, index) => {
if (index > 0) {
return h('span', { class: 'summary-cell' + columns[index].property }, val)
} else {
return val
}
})
}
</script>
<style lang="scss" scoped>
.input-lay {
position: fixed;
z-index: 99999;
inset: 0;
.number-input-wrapper {
position: absolute;
top: 0;
left: 0;
display: inline-flex;
align-items: center;
width: 100px;
height: 30px;
padding: 0 10px;
font-size: 12px;
}
input[type=number] {
width: 100%;
border: 1px dashed #00aaf2;
border-radius: 2px;
outline: none;
height: 24px;
padding: 0 10px;
text-align: center;
}
}
.el-table {
.col-name {
display: flex;
flex-direction: column;
line-height: normal;
}
:deep(.el-table__cell:has(.is-modified)) {
position: relative;
}
.is-modified {
position: absolute;
top: 0;
right: 0;
width: 14px;
height: 10px;
background-color: #00aaf2;
clip-path: polygon(0 0, 100% 0, 100% 100%);
pointer-events: none;
}
:deep(.el-table__cell:has(.can-edit)),
:deep(.el-table__cell:has(.can-edit) .cell) {
padding: 0;
}
:deep(.el-table__cell:has(.can-edit)) {
cursor: pointer;
&:hover .can-edit {
border-color: #00aaf2;
}
}
.can-edit {
display: flex;
align-items: center;
justify-content: center;
height: 24px;
margin: 0 10px;
border: 1px dashed transparent;
border-radius: 2px;
}
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment