This commit is contained in:
2025-12-24 10:55:40 +08:00
parent eec9d6045c
commit 752bb23571
25 changed files with 2348 additions and 816 deletions

View File

@@ -285,6 +285,7 @@ let r = lang;
* @property {Function} [onFilterOk] - 列过滤点击 `OK` 时触发的事件
* @property {Function} [onFiltered] - 列过滤后触发的事件
* @property {Function} [onDropExpanded] - 列为下拉框类型时在下拉列表展开时触发的事件
* @property {Function} [onDropCollapsed] - 列为下拉框类型时在下拉列表关闭时触发的事件
* @interface
* @example
* [
@@ -410,6 +411,15 @@ let r = lang;
* @this GridColumnDefinition
* @memberof GridColumnDefinition
*/
/**
* 列为下拉框类型时在下拉列表关闭时触发的事件
* @name onDropCollapsed
* @event
* @param {GridRowItem} item - 行数据对象
* @param {Dropdown} drop - 下拉框对象
* @this GridColumnDefinition
* @memberof GridColumnDefinition
*/
/**
* 判断列是否始终编辑的回调函数
@@ -613,6 +623,12 @@ export class Grid {
* @private
*/
parent: null,
/**
* Grid 包裹元素
* @type {HTMLDivElement}
* @private
*/
container: null,
/**
* Grid 元素 - `div.ui-grid`
* @type {HTMLDivElement}
@@ -818,7 +834,7 @@ export class Grid {
*/
footer: null,
/**
* 加载状态元素引用 - div.ui-grid-loading
* 加载状态元素引用 - div.ui-loading
* @type {HTMLDivElement}
* @private
*/
@@ -862,6 +878,13 @@ export class Grid {
* @ignore
*/
langs = {};
/**
* 区域字符串
* @type {string}
* @default "en"
* @ignore
*/
lgid = 'en';
/**
* 行数大于等于该值则启用虚模式
* @type {number}
@@ -1124,6 +1147,7 @@ export class Grid {
* @param {string} getText.{returns} 返回的多语言
* @property {GridColumnDefinition[]} columns - 列定义的数组
* @property {GridLanguages} [langs] - 多语言资源对象
* @property {string} [lgid=en] - 区域字符串
* @property {number} [virtualCount=100] - 行数大于等于该值则启用虚模式
* @property {boolean} [autoResize=true] - 未设置宽度的列自动调整列宽
* @property {number} [rowHeight=36] - 表格行高,修改后同时需要在 `.ui-grid` 所在父容器重写 `--line-height` 的值以配合显示
@@ -1274,10 +1298,23 @@ export class Grid {
if (!Array.isArray(list)) {
throw new Error('source is not an Array.')
}
list = list.reduce((array, item) => {
array.push({
__level: 0,
values: item
});
if (Array.isArray(item.__children)) {
array.push(...item.__children.map(c => ({
__level: 1,
values: c
})));
}
return array;
}, []);
list = list.map((it, index) => {
return {
__index: index,
values: it
...it
};
});
this._var.source = list;
@@ -1335,9 +1372,11 @@ export class Grid {
if (flag === false) {
this._var.refs.loading.style.visibility = 'hidden';
this._var.refs.loading.style.opacity = 0;
this._var.el.style.overflow = '';
} else {
this._var.refs.loading.style.visibility = 'visible';
this._var.refs.loading.style.opacity = 1;
this._var.el.style.overflow = 'hidden';
}
}
@@ -1402,6 +1441,8 @@ export class Grid {
this._var.parent = container;
this._var.isFirefox = /Firefox\//i.test(navigator.userAgent);
this._var.enabledDict = {};
const c = createElement('div', 'ui-grid-container');
this._var.container = c;
const grid = createElement('div', 'ui-grid');
grid.setAttribute('tabindex', 0);
grid.addEventListener('keydown', e => {
@@ -1506,7 +1547,8 @@ export class Grid {
}
});
}
container.replaceChildren(grid);
c.appendChild(grid);
container.replaceChildren(c);
const sizer = createElement('span', 'ui-grid-sizer');
grid.appendChild(sizer);
this._var.refs.sizer = sizer;
@@ -1525,7 +1567,11 @@ export class Grid {
wrapper.appendChild(table);
// tooltip
if (!this.tooltipDisabled) {
const holder = createElement('div', 'ui-grid-hover-holder');
const holder = createElement('div', 'ui-grid-hover-holder ui-grid-hover ui-tooltip-color',
// createElement('div', 'ui-grid-hover-pointer ui-grid-hover ui-tooltip-color'),
createElement('div', 'ui-grid-hover-curtain ui-grid-hover ui-tooltip-color'),
createElement('div', 'ui-grid-hover-content ui-grid-hover')
);
holder.addEventListener('mousedown', e => {
const holder = e.currentTarget;
const row = Number(holder.dataset.row);
@@ -1542,8 +1588,8 @@ export class Grid {
}
// loading
const loading = createElement('div', 'ui-grid-loading',
createElement('div', null, createIcon('fa-regular', 'spinner-third'))
const loading = createElement('div', 'ui-loading',
createElement('div')
);
this._var.refs.loading = loading;
grid.appendChild(loading);
@@ -1958,7 +2004,8 @@ export class Grid {
const gridWrapper = createElement('div', 'ui-sort-panel-grid');
content.append(buttonWrapper, gridWrapper);
const columnSource = this.columns.filter(c => c.sortable !== false); // ticket 56389, && c.visible !== false
columnSource.sort((a, b) => a.caption > b.caption ? 1 : -1);
const lgid = this.lgid;
columnSource.sort((a, b) => String(a.caption).localeCompare(b.caption, lgid));
grid.columns = [
{
width: 80,
@@ -2421,8 +2468,12 @@ export class Grid {
return new Promise(resolve => {
let working;
let url;
let path = ScriptPath;
if (nullOrEmpty(path) && typeof consts !== 'undefined') {
path = consts.modulePath;
}
if (typeof module === 'string') {
url = `${ScriptPath}${module}`;
url = `${path}${module}`;
} else {
url = URL.createObjectURL(new Blob([`let wasm,WASM_VECTOR_LEN=0,cachegetUint8Memory0=null;function getUint8Memory0(){return null!==cachegetUint8Memory0&&cachegetUint8Memory0.buffer===wasm.memory.buffer||(cachegetUint8Memory0=new Uint8Array(wasm.memory.buffer)),cachegetUint8Memory0}let cachegetInt32Memory0=null;function getInt32Memory0(){return null!==cachegetInt32Memory0&&cachegetInt32Memory0.buffer===wasm.memory.buffer||(cachegetInt32Memory0=new Int32Array(wasm.memory.buffer)),cachegetInt32Memory0}function passArray8ToWasm0(e,t){const a=t(1*e.length);return getUint8Memory0().set(e,a/1),WASM_VECTOR_LEN=e.length,a}function getArrayU8FromWasm0(e,t){return getUint8Memory0().subarray(e/1,e/1+t)}function encode_raw(e,t){var a=passArray8ToWasm0(t,wasm.__wbindgen_malloc),r=WASM_VECTOR_LEN;wasm[e+"_encode_raw"](8,a,r);var s=getInt32Memory0()[2],n=getInt32Memory0()[3],m=getArrayU8FromWasm0(s,n).slice();return wasm.__wbindgen_free(s,1*n),m}self.addEventListener("message",e=>{const t=e.data.type;if("init"===t)if("function"==typeof WebAssembly.instantiateStreaming){const t={},a=fetch(e.data.path+"wasm_flate_bg.wasm");WebAssembly.instantiateStreaming(a,t).then(({instance:e})=>{wasm=e.exports,self.postMessage({type:"init",result:0})}).catch(e=>a.then(t=>{"application/wasm"!==t.headers.get("Content-Type")?self.postMessage({type:"init",error:"\`WebAssembly.instantiateStreaming\` failed because your server does not serve wasm with \`application/wasm\` MIME type. Original error: "+e.message}):self.postMessage({type:"init",error:e.message})}))}else self.postMessage({type:"init",error:"no \`WebAssembly.instantiateStreaming\`"});else if("compress"===t)if(null==wasm)self.postMessage({error:"no \`wasm\` instance"});else{let t=encode_raw("${compressed ?? 'deflate'}",e.data.data);self.postMessage(t,[t.buffer])}});`]));
}
@@ -2469,7 +2520,7 @@ export class Grid {
}
})
working = true;
worker.postMessage({ type: 'init', path: ScriptPath });
worker.postMessage({ type: 'init', path });
});
}
@@ -2515,6 +2566,7 @@ export class Grid {
direction = 1;
}
const editing = col.sortAsText !== true;
const lgid = this.lgid;
const comparer = (a, b) => {
a = this._getItemSortProp(a, editing, col);
b = this._getItemSortProp(b, editing, col);
@@ -2541,8 +2593,9 @@ export class Grid {
b = b.join(', ');
}
if (typeof a === 'string' && typeof b === 'string') {
a = a.toLowerCase();
b = b.toLowerCase();
// a = a.toLowerCase();
// b = b.toLowerCase();
return a.localeCompare(b, lgid);
}
} else {
if (a == null && b != null) {
@@ -2558,8 +2611,9 @@ export class Grid {
b = b.join(', ');
}
if (typeof a === 'string' && typeof b === 'string') {
a = a.toLowerCase();
b = b.toLowerCase();
// a = a.toLowerCase();
// b = b.toLowerCase();
return a.localeCompare(b, lgid);
}
}
return a === b ? 0 : (a > b ? 1 : -1);
@@ -2727,10 +2781,15 @@ export class Grid {
}
th.appendChild(wrapper);
if (!readonly && col.enabled !== false && col.allcheck && alwaysEditing) {
const check = createCheckbox({
switch: col.switch,
onchange: e => this._onColumnAllChecked(col, e.target.checked)
});
let check;
if (typeof type.createEdit === 'function') {
check = type.createEdit(e => this._onColumnAllChecked(col, e.target.checked), col);
} else {
check = createCheckbox({
switch: col.switch,
onchange: e => this._onColumnAllChecked(col, e.target.checked)
});
}
wrapper.appendChild(check);
}
let caption;
@@ -2752,7 +2811,7 @@ export class Grid {
if (col.captionTooltip != null) {
const help = createIcon('fa-solid', 'question-circle');
wrapper.appendChild(help);
setTooltip(help, col.captionTooltip, false, this._var.parent);
setTooltip(help, col.captionTooltip, false, this._var.container);
}
// order arrow
if (col.sortable) {
@@ -3041,6 +3100,12 @@ export class Grid {
} else if (row.classList.contains('selected')) {
row.classList.remove('selected');
}
if (vals.__level !== 0) {
row.classList.add('ui-grid-row-level');
row.classList.add(`level-${vals.__level}`);
} else {
row.classList.remove('ui-grid-row-level');
}
const stateChanged = virtualRow.editing !== selected;
virtualRow.editing = selected;
// data
@@ -3105,7 +3170,7 @@ export class Grid {
if (col.text != null) {
val = col.text;
} else if (typeof col.filter === 'function') {
val = col.filter(item, selected, this._var.refs.body, startIndex + i);
val = col.filter(item, !this.readonly && selected, this._var.refs.body, startIndex + i);
} else {
val = item[col.key];
if (val != null) {
@@ -3604,7 +3669,7 @@ export class Grid {
/**
* @private
* @param {string} key
* @param {("autoResize" | "style" | "resizing" | "dragging" | "filterSource" | "filterHeight" | "filterTop")} name
* @param {("autoResize" | "style" | "resizing" | "dragging" | "filterSource" | "filterHeight" | "filterTop" | "filterSearched")} name
* @returns {any}
*/
_get(key, name) {
@@ -3618,7 +3683,7 @@ export class Grid {
/**
* @private
* @param {string} key
* @param {("autoResize" | "style" | "filterSource" | "filterHeight" | "filterTop")} name
* @param {("autoResize" | "style" | "filterSource" | "filterHeight" | "filterTop" | "filterSearched")} name
* @param {any} value
*/
_set(key, name, value) {
@@ -3703,7 +3768,7 @@ export class Grid {
if (e.parentElement.classList.contains('ui-switch')) {
return true;
}
return /^(input|label|layer|svg|use)$/i.test(e.tagName);
return /^(i|input|label|layer|svg|use)$/i.test(e.tagName);
}
/**
@@ -3815,6 +3880,7 @@ export class Grid {
panel.style.height = '';
}
this._set(col.key, 'filterSearched', false);
// search
let searchbox;
if (col.allowSearch !== false) {
@@ -3884,6 +3950,7 @@ export class Grid {
const type = this._var.colTypes[col.key];
const isDateColumn = type === GridDateColumn || type instanceof GridDateColumn;
const filterAsValue = col.filterAsValue;
const lgid = this.lgid;
array.sort((itemA, itemB) => {
let a = itemA.Value;
let b = itemB.Value;
@@ -3901,8 +3968,9 @@ export class Grid {
b = itemB.DisplayValue;
}
if (typeof a === 'string' && typeof b === 'string') {
a = a.toLowerCase();
b = b.toLowerCase();
// a = a.toLowerCase();
// b = b.toLowerCase();
return a.localeCompare(b, lgid);
}
}
return a > b ? 1 : (a < b ? -1 : 0);
@@ -3920,7 +3988,7 @@ export class Grid {
};
});
this._fillFilterList(col, itemlist, array, itemall);
itemall.querySelector('input').checked = ![...itemlist.querySelectorAll('.filter-content input')].some(i => !i.checked);
itemall.querySelector('input').checked = array.find(i => !i.__checked) == null;
panel.appendChild(itemlist);
if (searchbox != null) {
searchbox.addEventListener('input', e => {
@@ -3937,6 +4005,7 @@ export class Grid {
}
return String(displayValue).toLowerCase().includes(key);
});
this._set(col.key, 'filterSearched', items.length !== array.length);
this._fillFilterList(col, itemlist, items, itemall);
this._set(col.key, 'filterTop', -1);
itemlist.dispatchEvent(new Event('scroll'));
@@ -3949,24 +4018,34 @@ export class Grid {
ok.className = 'button';
ok.innerText = this.langs.ok;
ok.addEventListener('click', () => {
const array = this._get(col.key, 'filterSource').filter(i => i.__checked !== false);
if (typeof col.onFilterOk === 'function') {
col.onFilterOk.call(this, col, array);
const filterSource = this._get(col.key, 'filterSource');
const filterSearched = this._get(col.key, 'filterSearched');
if (!filterSearched && filterSource.find(i => i.__checked === false) == null) {
// all checked, equals to 'Reset'
delete col.filterValues;
this._var.colAttrs.__filtered = this.columns.some(c => c.filterValues != null);
filter.replaceChildren(createIcon('fa-solid', this.filterIcon));
filter.classList.remove('active');
} else {
if (GridColumnTypeEnum.isAlwaysEditing(col.type)) {
col.filterValues = array.map(a => a.Value);
const array = filterSource.filter(i => i.__checked !== false);
if (typeof col.onFilterOk === 'function') {
col.onFilterOk.call(this, col, array);
} else {
const nullValue = col.filterAllowNull ? null : '';
col.filterValues = array.map(a => a.Value == null ? nullValue : a.DisplayValue);
if (GridColumnTypeEnum.isAlwaysEditing(col.type)) {
col.filterValues = array.map(a => a.Value);
} else {
const nullValue = col.filterAllowNull ? null : '';
col.filterValues = array.map(a => a.Value == null ? nullValue : a.DisplayValue);
}
}
this._var.colAttrs.__filtered = true;
filter.replaceChildren(createIcon('fa-solid', this.filteredIcon));
filter.classList.add('active');
}
this._var.colAttrs.__filtered = true;
this._refreshSource();
if (typeof col.onFiltered === 'function') {
col.onFiltered.call(this, col);
}
filter.replaceChildren(createIcon('fa-solid', this.filteredIcon));
filter.classList.add('active');
this._onCloseFilter();
});
}),
@@ -3975,7 +4054,7 @@ export class Grid {
reset.innerText = this.langs.reset;
reset.addEventListener('click', () => {
delete col.filterValues;
this._var.colAttrs.__filtered = this.columns.some(c => c.filterValues != null)
this._var.colAttrs.__filtered = this.columns.some(c => c.filterValues != null);
this._refreshSource();
if (typeof col.onFiltered === 'function') {
col.onFiltered.call(this, col);
@@ -3998,34 +4077,39 @@ export class Grid {
* @private
* @param {GridColumnDefinition} col
* @param {HTMLDivElement} list
* @param {ValueItem[]} array
* @param {ValueItem[]} source
* @param {HTMLDivElement} all
*/
_fillFilterList(col, list, array, all) {
_fillFilterList(col, list, source, all) {
list.querySelector('.filter-holder')?.remove();
list.querySelector('.filter-content')?.remove();
const rowHeight = this.filterRowHeight;
const height = array.length * rowHeight;
const height = source.length * rowHeight;
this._set(col.key, 'filterHeight', height);
const holder = createElement('div', 'filter-holder');
holder.style.height = `${height}px`;
const content = createElement('div', 'filter-content');
content.style.top = `${rowHeight}px`;
this._set(col.key, 'filterSource', array);
this._set(col.key, 'filterSource', source);
const propKey = GridColumnTypeEnum.isAlwaysEditing(col.type) ? 'Value' : 'DisplayValue';
const nullValue = col.filterAllowNull ? null : '';
const allSelected = !Array.isArray(col.filterValues);
for (let item of array) {
let v = item.Value ?? nullValue;
if (v != null) {
v = Object.prototype.hasOwnProperty.call(item, propKey) ? item[propKey] : item;
for (let item of source) {
if (item.__checked == null) {
let v = item.Value ?? nullValue;
if (v != null) {
v = Object.prototype.hasOwnProperty.call(item, propKey) ? item[propKey] : item;
}
item.__checked = allSelected || col.filterValues.some(it => Array.isArray(it) ? it.includes(v) : it === v);
}
item.__checked = allSelected || col.filterValues.some(it => Array.isArray(it) ? it.includes(v) : it === v);
}
if (array.length > 12) {
array = array.slice(0, 12);
let array;
if (source.length > 12) {
array = source.slice(0, 12);
} else {
array = source;
}
this._doFillFilterList(col, content, array, all);
this._doFillFilterList(col, content, array, source, all);
list.append(holder, content);
}
@@ -4034,9 +4118,10 @@ export class Grid {
* @param {GridColumnDefinition} col
* @param {HTMLDivElement} content
* @param {ValueItem[]} array
* @param {ValueItem[]} source
* @param {HTMLDivElement} all
*/
_doFillFilterList(col, content, array, all) {
_doFillFilterList(col, content, array, source, all) {
for (let item of array) {
const div = createElement('div', 'filter-item');
const title = Object.prototype.hasOwnProperty.call(item, 'DisplayValue') ? item.DisplayValue : item;
@@ -4053,7 +4138,7 @@ export class Grid {
title,
onchange: e => {
item.__checked = e.target.checked;
all.querySelector('input').checked = ![...content.querySelectorAll('input')].some(i => !i.checked);
all.querySelector('input').checked = source.find(i => !i.__checked) == null;
}
}));
content.appendChild(div);
@@ -4083,15 +4168,16 @@ export class Grid {
if (this._get(col.key, 'filterTop') !== top) {
this._set(col.key, 'filterTop', top);
const startIndex = top / rowHeight;
let array = this._get(col.key, 'filterSource');
if (startIndex + 12 < array.length) {
array = array.slice(startIndex, startIndex + 12);
let source = this._get(col.key, 'filterSource');
let array;
if (startIndex + 12 < source.length) {
array = source.slice(startIndex, startIndex + 12);
} else {
array = array.slice(-12);
array = source.slice(-12);
}
const content = list.querySelector('.filter-content');
content.replaceChildren();
this._doFillFilterList(col, content, array, list.querySelector('.filter-all'));
this._doFillFilterList(col, content, array, source, list.querySelector('.filter-all'));
content.style.top = `${top + rowHeight}px`;
}
}
@@ -4341,7 +4427,7 @@ export class Grid {
*/
_onGridMouseMove(e, holder) {
e.stopPropagation();
if (e.target.classList.contains('ui-grid-hover-holder')) {
if (e.target.classList.contains('ui-grid-hover')) {
return;
}
let [parent, target] = this._getRowTarget(e.target);
@@ -4364,8 +4450,10 @@ export class Grid {
holder.dataset.col === col) {
return;
}
const type = this._var.colTypes[this.columns[col]?.key];
if (type?.canEdit && this._var.virtualRows[row]?.editing) {
const key = this.columns[col]?.key ?? col;
const type = this._var.colTypes[key];
const virtualRow = this._var.virtualRows[row];
if (type?.canEdit && virtualRow?.editing) {
delete holder.dataset.row;
delete holder.dataset.col;
if (holder.classList.contains('active')) {
@@ -4391,7 +4479,7 @@ export class Grid {
element.scrollHeight > element.offsetHeight) {
holder.dataset.row = row;
holder.dataset.col = col;
holder.innerText = element.innerText;
holder.querySelector('.ui-grid-hover-content').innerText = element.innerText;
const top = (parent.classList.contains('ui-grid-total-row') ? this._var.refs.footer.parentElement.offsetTop + 1 : target.offsetTop) + this._var.refs.table.offsetTop;
let left = target.offsetLeft;
let width = holder.offsetWidth;
@@ -4402,8 +4490,13 @@ export class Grid {
if (left > maxleft) {
left = maxleft;
}
const height = target.offsetHeight;
holder.style.cssText = `top: ${top}px; left: ${left}px; max-width: ${this._var.wrapClientWidth}px; min-height: ${height - 2}px`;
// const height = target.offsetHeight;
holder.style.cssText = `top: ${top}px; left: ${left}px; max-width: ${this._var.wrapClientWidth}px`; // ; min-height: ${height - 2}px
if (top > this.rowHeight * 2) {
holder.classList.remove('ui-grid-hover-down');
} else {
holder.classList.add('ui-grid-hover-down');
}
holder.classList.add('active');
} else if (holder.classList.contains('active')) {
delete holder.dataset.row;