From 3f19fb3a83e3eaedefa0dc4ad6a6bc90c66cb0b8 Mon Sep 17 00:00:00 2001 From: AI Engineer Date: Sun, 17 May 2026 18:50:30 +0800 Subject: [PATCH] perf(DataTable): implement lazy wrapping and optimize initialization --- dist/datatable.js | 70 ++++++++++++++++++------------- dist/datatable.min.js | 2 +- src/index.js | 68 ++++++++++++++++++------------ test/index.html | 97 ++++++++++++++++++++++++++----------------- 4 files changed, 145 insertions(+), 92 deletions(-) diff --git a/dist/datatable.js b/dist/datatable.js index e210ae5..4d779a0 100644 --- a/dist/datatable.js +++ b/dist/datatable.js @@ -1,4 +1,4 @@ -import { Component, NewState, Util, RefreshState } from "@web/state"; +import { Component, NewState, Util } from "@web/state"; import { VirtualScroll } from "@web/base"; Component.register("DataTable", (container) => { const vs = VirtualScroll(); @@ -21,19 +21,24 @@ Component.register("DataTable", (container) => { const scrollEl = container.querySelector(".dt-body"); if (!scrollEl) return; const res = vs.calc(scrollEl, state.list); - if (res) Object.assign(state, { - prevHeight: res.prevHeight, - postHeight: res.postHeight, - _listStartIndex: res.listStartIndex, - renderedList: res.renderedList - }); + 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, + _listStartIndex: res.listStartIndex, + renderedList: res.renderedList + }); + } }; container.refresh = refresh; state.__watch("list", (list) => { - if (list && list.length > 0 && !list[0].__watch) { - state.list = list.map((item) => NewState(item)); - return; - } state._listStartIndex = 0; const scrollEl = container.querySelector(".dt-body"); state.renderedList = vs.reset(list, scrollEl || { clientHeight: 800 }) || []; @@ -56,8 +61,10 @@ Component.register("DataTable", (container) => { }; container.clearAllActive = (keepSelection = false) => { state.list.forEach((row) => { - if (row._editingF !== null) row._editingF = null; - if (row._activeF !== null) row._activeF = null; + if (row && row.__watch) { + if (row._editingF !== null) row._editingF = null; + if (row._activeF !== null) row._activeF = null; + } }); if (!keepSelection) { state.selStartR = -1; @@ -85,13 +92,14 @@ Component.register("DataTable", (container) => { state.selStartF = state.selEndF = f; } 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.endSelect = () => state.isSelecting = false; container.editCell = (row, f, fIdx) => { var _a; + if (!row.__watch) return; 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 rIdx = state.list.indexOf(row); @@ -106,7 +114,12 @@ Component.register("DataTable", (container) => { if (count > 1) { const unwatch = row.__watch(f.id, (val) => { 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(); }); @@ -160,15 +173,21 @@ Component.register("DataTable", (container) => { const fStart = Math.min(state.selStartF, state.selEndF); if (rStart === -1) return; rows.forEach((rowData, i) => { - const row = state.list[rStart + i]; - if (row) rowData.forEach((val, j) => { - const field = state.fields[fStart + j]; - if (field) { - if (typeof row[field.id] === "boolean") row[field.id] = val.toLowerCase() === "true"; - else if (typeof row[field.id] === "number") row[field.id] = Number(val); - else row[field.id] = val; + let row = state.list[rStart + i]; + if (row) { + if (!row.__watch) { + row = NewState(row); + state.list[rStart + i] = row; } - }); + rowData.forEach((val, j) => { + const field = state.fields[fStart + j]; + if (field) { + if (typeof row[field.id] === "boolean") row[field.id] = val.toLowerCase() === "true"; + else if (typeof row[field.id] === "number") row[field.id] = Number(val); + else row[field.id] = val; + } + }); + } }); }; container.addEventListener("keydown", (e) => { @@ -242,8 +261,3 @@ Component.register("DataTable", (container) => { ` )); -if (typeof document !== "undefined") { - const initDataTable = () => RefreshState(document.documentElement); - if (document.readyState !== "loading") initDataTable(); - else document.addEventListener("DOMContentLoaded", initDataTable, true); -} diff --git a/dist/datatable.min.js b/dist/datatable.min.js index 0b70780..3831b2e 100644 --- a/dist/datatable.min.js +++ b/dist/datatable.min.js @@ -1 +1 @@ -import{Component as t,NewState as e,Util as i,RefreshState as n}from"@web/state";import{VirtualScroll as s}from"@web/base";if(t.register("DataTable",t=>{const i=s(),n=t.state;Object.assign(n,{list:[],fields:[],renderedList:[],prevHeight:0,postHeight:0,_listStartIndex:0,selStartR:-1,selStartF:-1,selEndR:-1,selEndF:-1,multiSelections:[],isSelecting:!1});const d=()=>{const e=t.querySelector(".dt-body");if(!e)return;const s=i.calc(e,n.list);s&&Object.assign(n,{prevHeight:s.prevHeight,postHeight:s.postHeight,_listStartIndex:s.listStartIndex,renderedList:s.renderedList})};t.refresh=d,n.__watch("list",s=>{if(s&&s.length>0&&!s[0].__watch)return void(n.list=s.map(t=>e(t)));n._listStartIndex=0;const r=t.querySelector(".dt-body");n.renderedList=i.reset(s,r||{clientHeight:800})||[],r&&(i.init(s,d),requestAnimationFrame(()=>d()))}),t.onItemUpdate=(t,e)=>i.update(t+n._listStartIndex,e),t.getOffset=(t,e,i)=>{const n=t.slice(0,e).filter(t=>"left"===t.pinned),s=t.slice(e+1).filter(t=>"right"===t.pinned);return("left"===i?n:s).reduce((t,e)=>t+(e.width||150),0)},t.isCellSelected=(t,e)=>{const i=Math.min(n.selStartR,n.selEndR),s=Math.max(n.selStartR,n.selEndR),d=Math.min(n.selStartF,n.selEndF),r=Math.max(n.selStartF,n.selEndF);return t>=i&&t<=s&&e>=d&&e<=r||n.multiSelections.some(i=>t>=i.r1&&t<=i.r2&&e>=i.f1&&e<=i.f2)},t.clearAllActive=(t=!1)=>{n.list.forEach(t=>{null!==t._editingF&&(t._editingF=null),null!==t._activeF&&(t._activeF=null)}),t||(n.selStartR=-1,n.multiSelections=[])},t.startSelect=(e,i,s)=>{const d=t.isCellSelected(e,i);n.editingCell&&(n.editingCell=null),s.shiftKey&&-1!==n.selStartR?(n.selEndR=e,n.selEndF=i):(d||(s.ctrlKey||s.metaKey?-1!==n.selStartR&&n.multiSelections.push({r1:Math.min(n.selStartR,n.selEndR),r2:Math.max(n.selStartR,n.selEndR),f1:Math.min(n.selStartF,n.selEndF),f2:Math.max(n.selStartF,n.selEndF)}):t.clearAllActive(),n.selStartR=n.selEndR=e,n.selStartF=n.selEndF=i),n.isSelecting=!0,n.list[e]._activeF=i)},t.updateSelect=(t,e)=>n.isSelecting&&(n.selEndR=t,n.selEndF=e),t.endSelect=()=>n.isSelecting=!1,t.editCell=(e,i,s)=>{var d;const r=Math.min(n.selStartR,n.selEndR),l=Math.max(n.selStartR,n.selEndR),a=Math.min(n.selStartF,n.selEndF),o=Math.max(n.selStartF,n.selEndF),c=n.list.indexOf(e),h=-1!==n.selStartR&&c>=r&&c<=l&&s>=a&&s<=o?l-r+1:0;if(h>1&&(null==(d=globalThis.UI)?void 0:d.toast)&&UI.toast(`{#Bulk Editing {num} rows... || ${h}#}`),t.clearAllActive(!0),e._editingF=i.id,e._activeF=s,h>1){const t=e.__watch(i.id,s=>{for(let t=r;t<=l;t++)n.list[t]!==e&&(n.list[t][i.id]=s);t()})}};t.copy=async()=>{const t=Math.min(n.selStartR,n.selEndR),e=Math.max(n.selStartR,n.selEndR),i=Math.min(n.selStartF,n.selEndF),s=Math.max(n.selStartF,n.selEndF);if(-1===t)return;const d=n.list.slice(t,e+1).map(t=>n.fields.slice(i,s+1).map(e=>(t=>{const e=String(t??"");return e.includes("\t")||e.includes("\n")||e.includes('"')?'"'+e.replace(/"/g,'""')+'"':e})(t[e.id])).join("\t")).join("\n");await navigator.clipboard.writeText(d)},t.paste=async()=>{const t=(t=>{const e=[];let i=[],n="",s=!1;for(let d=0;d{const d=n.list[e+s];d&&t.forEach((t,e)=>{const s=n.fields[i+e];s&&("boolean"==typeof d[s.id]?d[s.id]="true"===t.toLowerCase():"number"==typeof d[s.id]?d[s.id]=Number(t):d[s.id]=t)})})},t.addEventListener("keydown",e=>{(e.ctrlKey||e.metaKey)&&("c"===e.key&&(e.preventDefault(),t.copy()),"v"===e.key&&(e.preventDefault(),t.paste()))});const r=e=>!t.contains(e.target)&&t.clearAllActive();document.addEventListener("mousedown",r),window.addEventListener("mouseup",t.endSelect),t._onUnload=()=>{document.removeEventListener("mousedown",r),window.removeEventListener("mouseup",t.endSelect)}},i.makeDom("\n
\n\t
\n\t\t
\n\t\t\t\n\t\t\t\n\t\t
\n\t
\n\t
this.refresh())\" style=\"overflow-anchor:none\">\n\t\t
0\" $style=\"'height:' + this.state.prevHeight + 'px;'\">
\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t
\n\t\t
\n\t\t
0\" $style=\"'height:' + this.state.postHeight + 'px;'\">
\n\t
\n\t\n
\n")),"undefined"!=typeof document){const t=()=>n(document.documentElement);"loading"!==document.readyState?t():document.addEventListener("DOMContentLoaded",t,!0)} +import{Component as t,NewState as e,Util as i}from"@web/state";import{VirtualScroll as n}from"@web/base";t.register("DataTable",t=>{const i=n(),s=t.state;Object.assign(s,{list:[],fields:[],renderedList:[],prevHeight:0,postHeight:0,_listStartIndex:0,selStartR:-1,selStartF:-1,selEndR:-1,selEndF:-1,multiSelections:[],isSelecting:!1});const r=()=>{const n=t.querySelector(".dt-body");if(!n)return;const r=i.calc(n,s.list);r&&(r.renderedList.forEach((t,i)=>{if(!t.__watch){const n=e(t);r.renderedList[i]=n,s.list[r.listStartIndex+i]=n}}),Object.assign(s,{prevHeight:r.prevHeight,postHeight:r.postHeight,_listStartIndex:r.listStartIndex,renderedList:r.renderedList}))};t.refresh=r,s.__watch("list",e=>{s._listStartIndex=0;const n=t.querySelector(".dt-body");s.renderedList=i.reset(e,n||{clientHeight:800})||[],n&&(i.init(e,r),requestAnimationFrame(()=>r()))}),t.onItemUpdate=(t,e)=>i.update(t+s._listStartIndex,e),t.getOffset=(t,e,i)=>{const n=t.slice(0,e).filter(t=>"left"===t.pinned),s=t.slice(e+1).filter(t=>"right"===t.pinned);return("left"===i?n:s).reduce((t,e)=>t+(e.width||150),0)},t.isCellSelected=(t,e)=>{const i=Math.min(s.selStartR,s.selEndR),n=Math.max(s.selStartR,s.selEndR),r=Math.min(s.selStartF,s.selEndF),d=Math.max(s.selStartF,s.selEndF);return t>=i&&t<=n&&e>=r&&e<=d||s.multiSelections.some(i=>t>=i.r1&&t<=i.r2&&e>=i.f1&&e<=i.f2)},t.clearAllActive=(t=!1)=>{s.list.forEach(t=>{t&&t.__watch&&(null!==t._editingF&&(t._editingF=null),null!==t._activeF&&(t._activeF=null))}),t||(s.selStartR=-1,s.multiSelections=[])},t.startSelect=(e,i,n)=>{const r=t.isCellSelected(e,i);s.editingCell&&(s.editingCell=null),n.shiftKey&&-1!==s.selStartR?(s.selEndR=e,s.selEndF=i):(r||(n.ctrlKey||n.metaKey?-1!==s.selStartR&&s.multiSelections.push({r1:Math.min(s.selStartR,s.selEndR),r2:Math.max(s.selStartR,s.selEndR),f1:Math.min(s.selStartF,s.selEndF),f2:Math.max(s.selStartF,s.selEndF)}):t.clearAllActive(),s.selStartR=s.selEndR=e,s.selStartF=s.selEndF=i),s.isSelecting=!0,s.list[e]&&s.list[e].__watch&&(s.list[e]._activeF=i))},t.updateSelect=(t,e)=>s.isSelecting&&(s.selEndR=t,s.selEndF=e),t.endSelect=()=>s.isSelecting=!1,t.editCell=(i,n,r)=>{var d;if(!i.__watch)return;const l=Math.min(s.selStartR,s.selEndR),a=Math.max(s.selStartR,s.selEndR),o=Math.min(s.selStartF,s.selEndF),c=Math.max(s.selStartF,s.selEndF),h=s.list.indexOf(i),f=-1!==s.selStartR&&h>=l&&h<=a&&r>=o&&r<=c?a-l+1:0;if(f>1&&(null==(d=globalThis.UI)?void 0:d.toast)&&UI.toast(`{#Bulk Editing {num} rows... || ${f}#}`),t.clearAllActive(!0),i._editingF=n.id,i._activeF=r,f>1){const t=i.__watch(n.id,r=>{for(let t=l;t<=a;t++){const d=s.list[t];if(d!==i){const i=d.__watch?d:e(d);s.list[t]=i,i[n.id]=r}}t()})}};t.copy=async()=>{const t=Math.min(s.selStartR,s.selEndR),e=Math.max(s.selStartR,s.selEndR),i=Math.min(s.selStartF,s.selEndF),n=Math.max(s.selStartF,s.selEndF);if(-1===t)return;const r=s.list.slice(t,e+1).map(t=>s.fields.slice(i,n+1).map(e=>(t=>{const e=String(t??"");return e.includes("\t")||e.includes("\n")||e.includes('"')?'"'+e.replace(/"/g,'""')+'"':e})(t[e.id])).join("\t")).join("\n");await navigator.clipboard.writeText(r)},t.paste=async()=>{const t=(t=>{const e=[];let i=[],n="",s=!1;for(let r=0;r{let d=s.list[i+r];d&&(d.__watch||(d=e(d),s.list[i+r]=d),t.forEach((t,e)=>{const i=s.fields[n+e];i&&("boolean"==typeof d[i.id]?d[i.id]="true"===t.toLowerCase():"number"==typeof d[i.id]?d[i.id]=Number(t):d[i.id]=t)}))})},t.addEventListener("keydown",e=>{(e.ctrlKey||e.metaKey)&&("c"===e.key&&(e.preventDefault(),t.copy()),"v"===e.key&&(e.preventDefault(),t.paste()))});const d=e=>!t.contains(e.target)&&t.clearAllActive();document.addEventListener("mousedown",d),window.addEventListener("mouseup",t.endSelect),t._onUnload=()=>{document.removeEventListener("mousedown",d),window.removeEventListener("mouseup",t.endSelect)}},i.makeDom("\n
\n\t
\n\t\t
\n\t\t\t\n\t\t\t\n\t\t
\n\t
\n\t
this.refresh())\" style=\"overflow-anchor:none\">\n\t\t
0\" $style=\"'height:' + this.state.prevHeight + 'px;'\">
\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t
\n\t\t
\n\t\t
0\" $style=\"'height:' + this.state.postHeight + 'px;'\">
\n\t
\n\t\n
\n")); diff --git a/src/index.js b/src/index.js index a72fbc2..ec47c75 100644 --- a/src/index.js +++ b/src/index.js @@ -17,20 +17,28 @@ Component.register('DataTable', container => { const scrollEl = container.querySelector('.dt-body') if (!scrollEl) return const res = vs.calc(scrollEl, state.list) - if (res) Object.assign(state, { - prevHeight: res.prevHeight, postHeight: res.postHeight, - _listStartIndex: res.listStartIndex, renderedList: res.renderedList - }) + 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, + _listStartIndex: res.listStartIndex, renderedList: res.renderedList + }) + } } container.refresh = refresh state.__watch('list', list => { - if (list && list.length > 0 && !list[0].__watch) { - state.list = list.map(item => NewState(item)) - return - } state._listStartIndex = 0 const scrollEl = container.querySelector('.dt-body') + // 初始预览,不全量包装 state.renderedList = vs.reset(list, scrollEl || { clientHeight: 800 }) || [] if (scrollEl) { vs.init(list, refresh) @@ -55,8 +63,10 @@ Component.register('DataTable', container => { container.clearAllActive = (keepSelection = false) => { state.list.forEach(row => { - if (row._editingF !== null) row._editingF = null - if (row._activeF !== null) row._activeF = null + if (row && row.__watch) { + if (row._editingF !== null) row._editingF = null + if (row._activeF !== null) row._activeF = null + } }) if (!keepSelection) { state.selStartR = -1; state.multiSelections = [] @@ -82,7 +92,7 @@ Component.register('DataTable', container => { state.selStartF = state.selEndF = f } 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.editCell = (row, f, fIdx) => { + if (!row.__watch) return; // 理论上双击的行一定已经包装过 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 rIdx = state.list.indexOf(row) @@ -107,7 +118,12 @@ Component.register('DataTable', container => { if (count > 1) { const unwatch = row.__watch(f.id, (val) => { 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() }) @@ -156,15 +172,21 @@ Component.register('DataTable', container => { const fStart = Math.min(state.selStartF, state.selEndF) if (rStart === -1) return rows.forEach((rowData, i) => { - const row = state.list[rStart + i] - if (row) rowData.forEach((val, j) => { - const field = state.fields[fStart + j] - if (field) { - if (typeof row[field.id] === 'boolean') row[field.id] = val.toLowerCase() === 'true' - else if (typeof row[field.id] === 'number') row[field.id] = Number(val) - else row[field.id] = val + let row = state.list[rStart + i] + if (row) { + if (!row.__watch) { + row = NewState(row) + state.list[rStart + i] = row } - }) + rowData.forEach((val, j) => { + const field = state.fields[fStart + j] + if (field) { + if (typeof row[field.id] === 'boolean') row[field.id] = val.toLowerCase() === 'true' + else if (typeof row[field.id] === 'number') row[field.id] = Number(val) + else row[field.id] = val + } + }) + } }) } @@ -232,9 +254,3 @@ Component.register('DataTable', container => { `)) - -if (typeof document !== 'undefined') { - const initDataTable = () => RefreshState(document.documentElement) - if (document.readyState !== 'loading') initDataTable() - else document.addEventListener('DOMContentLoaded', initDataTable, true) -} diff --git a/test/index.html b/test/index.html index 94ff050..e4ed62d 100644 --- a/test/index.html +++ b/test/index.html @@ -34,6 +34,7 @@ +

DataTable Professional

@@ -49,50 +50,72 @@ import '@web/base' import '../src/index.js' - const table = document.getElementById('myTable') + const logEl = document.getElementById('debug-log'); + const log = (msg) => { + console.log(msg); + logEl.innerText += msg + '\n'; + }; - const fields = [ - { id: 'id', name: 'ID', width: 60, type: 'text', pinned: 'left' }, - { id: 'name', name: 'User Name', width: 150, type: 'text', pinned: 'left' }, - { id: 'role', name: 'Role', width: 120, type: 'select', options: ['Admin', 'Editor', 'Viewer'] }, - { id: 'active', name: 'Active', width: 80, type: 'switch' }, - { id: 'bio', name: 'Bio', width: 250, type: 'textarea' }, - { id: 'gender', name: 'Gender', width: 120, type: 'radio', options: ['Male', 'Female', 'Other'] }, - { id: 'tags', name: 'Tags', width: 150, type: 'TagsInput' }, - { id: 'score', name: 'Score', width: 100, type: 'number' }, - { id: 'created', name: 'Created At', width: 150, type: 'date' }, - { id: 'actions', name: 'Actions', width: 100, type: 'text', pinned: 'right' } - ] + log('Script starting after imports...'); + + try { + const table = document.getElementById('myTable') - const generateData = (count) => Array.from({ length: count }, (_, i) => ({ - id: i + 1, - name: 'User ' + (i + 1), - role: ['Admin', 'Editor', 'Viewer'][i % 3], - active: i % 2 === 0, - bio: 'This is the bio for user ' + (i + 1) + '. It might be a long text that needs a textarea for editing.', - gender: ['Male', 'Female', 'Other'][i % 3], - tags: ['State.js', 'Vite', 'Playwright'].slice(0, (i % 3) + 1), - score: Math.floor(Math.random() * 100), - created: new Date().toISOString().split('T')[0], - actions: '...' - })) + const fields = [ + { id: 'id', name: 'ID', width: 60, type: 'text', pinned: 'left' }, + { id: 'name', name: 'User Name', width: 150, type: 'text', pinned: 'left' }, + { id: 'role', name: 'Role', width: 120, type: 'select', options: ['Admin', 'Editor', 'Viewer'] }, + { id: 'active', name: 'Active', width: 80, type: 'switch' }, + { id: 'bio', name: 'Bio', width: 250, type: 'textarea' }, + { id: 'gender', name: 'Gender', width: 120, type: 'radio', options: ['Male', 'Female', 'Other'] }, + { id: 'tags', name: 'Tags', width: 150, type: 'TagsInput' }, + { id: 'score', name: 'Score', width: 100, type: 'number' }, + { id: 'created', name: 'Created At', width: 150, type: 'date' }, + { id: 'actions', name: 'Actions', width: 100, type: 'text', pinned: 'right' } + ] - const data = generateData(500) + const generateData = (count) => Array.from({ length: count }, (_, i) => ({ + id: i + 1, + name: 'User ' + (i + 1), + role: ['Admin', 'Editor', 'Viewer'][i % 3], + active: i % 2 === 0, + bio: 'This is the bio for user ' + (i + 1) + '. It might be a long text that needs a textarea for editing.', + gender: ['Male', 'Female', 'Other'][i % 3], + tags: ['State.js', 'Vite', 'Playwright'].slice(0, (i % 3) + 1), + score: Math.floor(Math.random() * 100), + created: new Date().toISOString().split('T')[0], + actions: '...' + })) - // 数据初始化逻辑 - setTimeout(() => { - Object.assign(table.state, { - fields: fields, - list: data - }) + const data = generateData(500) + // 数据初始化逻辑 setTimeout(() => { - window.testStatus = 'passed' - console.log('DataTable initialized in test page') - }, 500) - }, 200) + try { + log('Triggering RefreshState before init...'); + RefreshState(table); + + log('Initializing table state...'); + if (!table.state) table.state = NewState({}); + + Object.assign(table.state, { + fields: fields, + list: data + }) + + setTimeout(() => { + window.testStatus = 'passed' + log('DataTable initialized in test page: PASSED'); + }, 500) + } catch (e) { + log('ERROR in state init: ' + e.message + '\n' + e.stack); + } + }, 200) + } catch (e) { + log('ERROR during startup: ' + e.message); + } - \ No newline at end of file +