perf(DataTable): implement lazy wrapping and optimize initialization
This commit is contained in:
parent
0bae1edceb
commit
3f19fb3a83
44
dist/datatable.js
vendored
44
dist/datatable.js
vendored
@ -1,4 +1,4 @@
|
|||||||
import { Component, NewState, Util, RefreshState } from "@web/state";
|
import { Component, NewState, Util } from "@web/state";
|
||||||
import { VirtualScroll } from "@web/base";
|
import { VirtualScroll } from "@web/base";
|
||||||
Component.register("DataTable", (container) => {
|
Component.register("DataTable", (container) => {
|
||||||
const vs = VirtualScroll();
|
const vs = VirtualScroll();
|
||||||
@ -21,19 +21,24 @@ Component.register("DataTable", (container) => {
|
|||||||
const scrollEl = container.querySelector(".dt-body");
|
const scrollEl = container.querySelector(".dt-body");
|
||||||
if (!scrollEl) return;
|
if (!scrollEl) return;
|
||||||
const res = vs.calc(scrollEl, state.list);
|
const res = vs.calc(scrollEl, state.list);
|
||||||
if (res) Object.assign(state, {
|
if (res) {
|
||||||
|
res.renderedList.forEach((item, i) => {
|
||||||
|
if (!item.__watch) {
|
||||||
|
const wrapped = NewState(item);
|
||||||
|
res.renderedList[i] = wrapped;
|
||||||
|
state.list[res.listStartIndex + i] = wrapped;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.assign(state, {
|
||||||
prevHeight: res.prevHeight,
|
prevHeight: res.prevHeight,
|
||||||
postHeight: res.postHeight,
|
postHeight: res.postHeight,
|
||||||
_listStartIndex: res.listStartIndex,
|
_listStartIndex: res.listStartIndex,
|
||||||
renderedList: res.renderedList
|
renderedList: res.renderedList
|
||||||
});
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
container.refresh = refresh;
|
container.refresh = refresh;
|
||||||
state.__watch("list", (list) => {
|
state.__watch("list", (list) => {
|
||||||
if (list && list.length > 0 && !list[0].__watch) {
|
|
||||||
state.list = list.map((item) => NewState(item));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
state._listStartIndex = 0;
|
state._listStartIndex = 0;
|
||||||
const scrollEl = container.querySelector(".dt-body");
|
const scrollEl = container.querySelector(".dt-body");
|
||||||
state.renderedList = vs.reset(list, scrollEl || { clientHeight: 800 }) || [];
|
state.renderedList = vs.reset(list, scrollEl || { clientHeight: 800 }) || [];
|
||||||
@ -56,8 +61,10 @@ Component.register("DataTable", (container) => {
|
|||||||
};
|
};
|
||||||
container.clearAllActive = (keepSelection = false) => {
|
container.clearAllActive = (keepSelection = false) => {
|
||||||
state.list.forEach((row) => {
|
state.list.forEach((row) => {
|
||||||
|
if (row && row.__watch) {
|
||||||
if (row._editingF !== null) row._editingF = null;
|
if (row._editingF !== null) row._editingF = null;
|
||||||
if (row._activeF !== null) row._activeF = null;
|
if (row._activeF !== null) row._activeF = null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if (!keepSelection) {
|
if (!keepSelection) {
|
||||||
state.selStartR = -1;
|
state.selStartR = -1;
|
||||||
@ -85,13 +92,14 @@ Component.register("DataTable", (container) => {
|
|||||||
state.selStartF = state.selEndF = f;
|
state.selStartF = state.selEndF = f;
|
||||||
}
|
}
|
||||||
state.isSelecting = true;
|
state.isSelecting = true;
|
||||||
state.list[r]._activeF = f;
|
if (state.list[r] && state.list[r].__watch) state.list[r]._activeF = f;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
container.updateSelect = (r, f) => state.isSelecting && (state.selEndR = r, state.selEndF = f);
|
container.updateSelect = (r, f) => state.isSelecting && (state.selEndR = r, state.selEndF = f);
|
||||||
container.endSelect = () => state.isSelecting = false;
|
container.endSelect = () => state.isSelecting = false;
|
||||||
container.editCell = (row, f, fIdx) => {
|
container.editCell = (row, f, fIdx) => {
|
||||||
var _a;
|
var _a;
|
||||||
|
if (!row.__watch) return;
|
||||||
const rMin = Math.min(state.selStartR, state.selEndR), rMax = Math.max(state.selStartR, state.selEndR);
|
const rMin = Math.min(state.selStartR, state.selEndR), rMax = Math.max(state.selStartR, state.selEndR);
|
||||||
const fMin = Math.min(state.selStartF, state.selEndF), fMax = Math.max(state.selStartF, state.selEndF);
|
const fMin = Math.min(state.selStartF, state.selEndF), fMax = Math.max(state.selStartF, state.selEndF);
|
||||||
const rIdx = state.list.indexOf(row);
|
const rIdx = state.list.indexOf(row);
|
||||||
@ -106,7 +114,12 @@ Component.register("DataTable", (container) => {
|
|||||||
if (count > 1) {
|
if (count > 1) {
|
||||||
const unwatch = row.__watch(f.id, (val) => {
|
const unwatch = row.__watch(f.id, (val) => {
|
||||||
for (let r = rMin; r <= rMax; r++) {
|
for (let r = rMin; r <= rMax; r++) {
|
||||||
if (state.list[r] !== row) state.list[r][f.id] = val;
|
const cur = state.list[r];
|
||||||
|
if (cur !== row) {
|
||||||
|
const wrapped = cur.__watch ? cur : NewState(cur);
|
||||||
|
state.list[r] = wrapped;
|
||||||
|
wrapped[f.id] = val;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
unwatch();
|
unwatch();
|
||||||
});
|
});
|
||||||
@ -160,8 +173,13 @@ Component.register("DataTable", (container) => {
|
|||||||
const fStart = Math.min(state.selStartF, state.selEndF);
|
const fStart = Math.min(state.selStartF, state.selEndF);
|
||||||
if (rStart === -1) return;
|
if (rStart === -1) return;
|
||||||
rows.forEach((rowData, i) => {
|
rows.forEach((rowData, i) => {
|
||||||
const row = state.list[rStart + i];
|
let row = state.list[rStart + i];
|
||||||
if (row) rowData.forEach((val, j) => {
|
if (row) {
|
||||||
|
if (!row.__watch) {
|
||||||
|
row = NewState(row);
|
||||||
|
state.list[rStart + i] = row;
|
||||||
|
}
|
||||||
|
rowData.forEach((val, j) => {
|
||||||
const field = state.fields[fStart + j];
|
const field = state.fields[fStart + j];
|
||||||
if (field) {
|
if (field) {
|
||||||
if (typeof row[field.id] === "boolean") row[field.id] = val.toLowerCase() === "true";
|
if (typeof row[field.id] === "boolean") row[field.id] = val.toLowerCase() === "true";
|
||||||
@ -169,6 +187,7 @@ Component.register("DataTable", (container) => {
|
|||||||
else row[field.id] = val;
|
else row[field.id] = val;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
container.addEventListener("keydown", (e) => {
|
container.addEventListener("keydown", (e) => {
|
||||||
@ -242,8 +261,3 @@ Component.register("DataTable", (container) => {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
));
|
));
|
||||||
if (typeof document !== "undefined") {
|
|
||||||
const initDataTable = () => RefreshState(document.documentElement);
|
|
||||||
if (document.readyState !== "loading") initDataTable();
|
|
||||||
else document.addEventListener("DOMContentLoaded", initDataTable, true);
|
|
||||||
}
|
|
||||||
|
|||||||
2
dist/datatable.min.js
vendored
2
dist/datatable.min.js
vendored
File diff suppressed because one or more lines are too long
46
src/index.js
46
src/index.js
@ -17,20 +17,28 @@ Component.register('DataTable', container => {
|
|||||||
const scrollEl = container.querySelector('.dt-body')
|
const scrollEl = container.querySelector('.dt-body')
|
||||||
if (!scrollEl) return
|
if (!scrollEl) return
|
||||||
const res = vs.calc(scrollEl, state.list)
|
const res = vs.calc(scrollEl, state.list)
|
||||||
if (res) Object.assign(state, {
|
if (res) {
|
||||||
|
// 仅对渲染列表中的项进行响应式包装(如果尚未包装)
|
||||||
|
res.renderedList.forEach((item, i) => {
|
||||||
|
if (!item.__watch) {
|
||||||
|
const wrapped = NewState(item)
|
||||||
|
res.renderedList[i] = wrapped
|
||||||
|
// 同步回原列表
|
||||||
|
state.list[res.listStartIndex + i] = wrapped
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Object.assign(state, {
|
||||||
prevHeight: res.prevHeight, postHeight: res.postHeight,
|
prevHeight: res.prevHeight, postHeight: res.postHeight,
|
||||||
_listStartIndex: res.listStartIndex, renderedList: res.renderedList
|
_listStartIndex: res.listStartIndex, renderedList: res.renderedList
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
container.refresh = refresh
|
container.refresh = refresh
|
||||||
|
|
||||||
state.__watch('list', list => {
|
state.__watch('list', list => {
|
||||||
if (list && list.length > 0 && !list[0].__watch) {
|
|
||||||
state.list = list.map(item => NewState(item))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
state._listStartIndex = 0
|
state._listStartIndex = 0
|
||||||
const scrollEl = container.querySelector('.dt-body')
|
const scrollEl = container.querySelector('.dt-body')
|
||||||
|
// 初始预览,不全量包装
|
||||||
state.renderedList = vs.reset(list, scrollEl || { clientHeight: 800 }) || []
|
state.renderedList = vs.reset(list, scrollEl || { clientHeight: 800 }) || []
|
||||||
if (scrollEl) {
|
if (scrollEl) {
|
||||||
vs.init(list, refresh)
|
vs.init(list, refresh)
|
||||||
@ -55,8 +63,10 @@ Component.register('DataTable', container => {
|
|||||||
|
|
||||||
container.clearAllActive = (keepSelection = false) => {
|
container.clearAllActive = (keepSelection = false) => {
|
||||||
state.list.forEach(row => {
|
state.list.forEach(row => {
|
||||||
|
if (row && row.__watch) {
|
||||||
if (row._editingF !== null) row._editingF = null
|
if (row._editingF !== null) row._editingF = null
|
||||||
if (row._activeF !== null) row._activeF = null
|
if (row._activeF !== null) row._activeF = null
|
||||||
|
}
|
||||||
})
|
})
|
||||||
if (!keepSelection) {
|
if (!keepSelection) {
|
||||||
state.selStartR = -1; state.multiSelections = []
|
state.selStartR = -1; state.multiSelections = []
|
||||||
@ -82,7 +92,7 @@ Component.register('DataTable', container => {
|
|||||||
state.selStartF = state.selEndF = f
|
state.selStartF = state.selEndF = f
|
||||||
}
|
}
|
||||||
state.isSelecting = true
|
state.isSelecting = true
|
||||||
state.list[r]._activeF = f
|
if (state.list[r] && state.list[r].__watch) state.list[r]._activeF = f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,6 +100,7 @@ Component.register('DataTable', container => {
|
|||||||
container.endSelect = () => state.isSelecting = false
|
container.endSelect = () => state.isSelecting = false
|
||||||
|
|
||||||
container.editCell = (row, f, fIdx) => {
|
container.editCell = (row, f, fIdx) => {
|
||||||
|
if (!row.__watch) return; // 理论上双击的行一定已经包装过
|
||||||
const rMin = Math.min(state.selStartR, state.selEndR), rMax = Math.max(state.selStartR, state.selEndR)
|
const rMin = Math.min(state.selStartR, state.selEndR), rMax = Math.max(state.selStartR, state.selEndR)
|
||||||
const fMin = Math.min(state.selStartF, state.selEndF), fMax = Math.max(state.selStartF, state.selEndF)
|
const fMin = Math.min(state.selStartF, state.selEndF), fMax = Math.max(state.selStartF, state.selEndF)
|
||||||
const rIdx = state.list.indexOf(row)
|
const rIdx = state.list.indexOf(row)
|
||||||
@ -107,7 +118,12 @@ Component.register('DataTable', container => {
|
|||||||
if (count > 1) {
|
if (count > 1) {
|
||||||
const unwatch = row.__watch(f.id, (val) => {
|
const unwatch = row.__watch(f.id, (val) => {
|
||||||
for (let r = rMin; r <= rMax; r++) {
|
for (let r = rMin; r <= rMax; r++) {
|
||||||
if (state.list[r] !== row) state.list[r][f.id] = val
|
const cur = state.list[r]
|
||||||
|
if (cur !== row) {
|
||||||
|
const wrapped = cur.__watch ? cur : NewState(cur)
|
||||||
|
state.list[r] = wrapped
|
||||||
|
wrapped[f.id] = val
|
||||||
|
}
|
||||||
}
|
}
|
||||||
unwatch()
|
unwatch()
|
||||||
})
|
})
|
||||||
@ -156,8 +172,13 @@ Component.register('DataTable', container => {
|
|||||||
const fStart = Math.min(state.selStartF, state.selEndF)
|
const fStart = Math.min(state.selStartF, state.selEndF)
|
||||||
if (rStart === -1) return
|
if (rStart === -1) return
|
||||||
rows.forEach((rowData, i) => {
|
rows.forEach((rowData, i) => {
|
||||||
const row = state.list[rStart + i]
|
let row = state.list[rStart + i]
|
||||||
if (row) rowData.forEach((val, j) => {
|
if (row) {
|
||||||
|
if (!row.__watch) {
|
||||||
|
row = NewState(row)
|
||||||
|
state.list[rStart + i] = row
|
||||||
|
}
|
||||||
|
rowData.forEach((val, j) => {
|
||||||
const field = state.fields[fStart + j]
|
const field = state.fields[fStart + j]
|
||||||
if (field) {
|
if (field) {
|
||||||
if (typeof row[field.id] === 'boolean') row[field.id] = val.toLowerCase() === 'true'
|
if (typeof row[field.id] === 'boolean') row[field.id] = val.toLowerCase() === 'true'
|
||||||
@ -165,6 +186,7 @@ Component.register('DataTable', container => {
|
|||||||
else row[field.id] = val
|
else row[field.id] = val
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,9 +254,3 @@ Component.register('DataTable', container => {
|
|||||||
</style>
|
</style>
|
||||||
</div>
|
</div>
|
||||||
`))
|
`))
|
||||||
|
|
||||||
if (typeof document !== 'undefined') {
|
|
||||||
const initDataTable = () => RefreshState(document.documentElement)
|
|
||||||
if (document.readyState !== 'loading') initDataTable()
|
|
||||||
else document.addEventListener('DOMContentLoaded', initDataTable, true)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -34,6 +34,7 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="bg-light">
|
<body class="bg-light">
|
||||||
|
<div id="debug-log" style="position:fixed; top:0; right:0; background:rgba(0,0,0,0.8); color:white; z-index:9999; font-family:monospace; padding:10px; max-height:200px; overflow:auto;"></div>
|
||||||
<div class="demo-container">
|
<div class="demo-container">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
<h4 class="mb-0 text-primary fw-bold">DataTable Professional</h4>
|
<h4 class="mb-0 text-primary fw-bold">DataTable Professional</h4>
|
||||||
@ -49,6 +50,15 @@
|
|||||||
import '@web/base'
|
import '@web/base'
|
||||||
import '../src/index.js'
|
import '../src/index.js'
|
||||||
|
|
||||||
|
const logEl = document.getElementById('debug-log');
|
||||||
|
const log = (msg) => {
|
||||||
|
console.log(msg);
|
||||||
|
logEl.innerText += msg + '\n';
|
||||||
|
};
|
||||||
|
|
||||||
|
log('Script starting after imports...');
|
||||||
|
|
||||||
|
try {
|
||||||
const table = document.getElementById('myTable')
|
const table = document.getElementById('myTable')
|
||||||
|
|
||||||
const fields = [
|
const fields = [
|
||||||
@ -81,6 +91,13 @@
|
|||||||
|
|
||||||
// 数据初始化逻辑
|
// 数据初始化逻辑
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
try {
|
||||||
|
log('Triggering RefreshState before init...');
|
||||||
|
RefreshState(table);
|
||||||
|
|
||||||
|
log('Initializing table state...');
|
||||||
|
if (!table.state) table.state = NewState({});
|
||||||
|
|
||||||
Object.assign(table.state, {
|
Object.assign(table.state, {
|
||||||
fields: fields,
|
fields: fields,
|
||||||
list: data
|
list: data
|
||||||
@ -88,9 +105,15 @@
|
|||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.testStatus = 'passed'
|
window.testStatus = 'passed'
|
||||||
console.log('DataTable initialized in test page')
|
log('DataTable initialized in test page: PASSED');
|
||||||
}, 500)
|
}, 500)
|
||||||
|
} catch (e) {
|
||||||
|
log('ERROR in state init: ' + e.message + '\n' + e.stack);
|
||||||
|
}
|
||||||
}, 200)
|
}, 200)
|
||||||
|
} catch (e) {
|
||||||
|
log('ERROR during startup: ' + e.message);
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user