feat: tree-style Dropdown.

This commit is contained in:
2026-08-17 15:31:24 +08:00
parent 2e1bb9ad20
commit af00428d14
+44 -43
View File
@@ -104,6 +104,7 @@ function getValue(it, valuekey, textkey) {
* @property {string} [textKey=text] - 文本关键字
* @property {string} [valueKey=value] - 值关键字
* @property {string} [htmlKey=html] - 源码显示的关键字
* @property {string} [childKey=children] - 子集合的关键字
* @property {Function} [htmlTemplate] - 模板创建函数
* @property {number} [maxLength=500] - 最大输入长度
* @property {boolean} [multiSelect] - 是否允许多选
@@ -144,6 +145,7 @@ export class Dropdown {
options.textKey ??= 'text';
options.valueKey ??= 'value';
options.htmlKey ??= 'html';
options.childKey ??= 'children';
options.maxLength ??= 500;
this._var.options = options;
const getText = options.getText;
@@ -303,11 +305,16 @@ export class Dropdown {
return;
}
const valuekey = this._var.options.valueKey;
const childkey = this._var.options.childKey;
function reduceItems(list, id, level = 0) {
if (!Array.isArray(list)) {
return [];
}
return list.reduce((array, item) => [...array, { __p: id, __level: level, ...item }, ...reduceItems(item.children, item[valuekey], level + 1)], []);
return list.reduce((array, item) => [
...array,
(id == null ? item : { __p: String(id), __level: level, ...item }),
...reduceItems(item[childkey], item[valuekey], level + 1)
], []);
}
this._var.source = reduceItems(list);
if (this._expanded) {
@@ -580,19 +587,18 @@ export class Dropdown {
const multiselect = this.multiSelect;
const valuekey = this._var.options.valueKey;
const textkey = this._var.options.textKey;
const childkey = this._var.options.childKey;
const allchecked = this._var.allChecked;
const selectedlist = this.selectedList;
source.forEach((item, i) => {
let val = getValue(item, valuekey, textkey);
if (typeof val !== 'string') {
val = String(val);
}
const val = String(getValue(item, valuekey, textkey));
if (multiselect) {
const selected = selectedlist.some(s => String(getValue(s, valuekey, textkey)) === val);
if (allchecked || selected) {
item.__checked = 1;
source.filter(it => it.__p === val).forEach(it => it.__checked = 1);
} else {
const indeterminate = selectedlist.some(s => this._contains(String(getValue(s, valuekey, textkey)), item, valuekey, textkey));
const indeterminate = selectedlist.some(s => this._contains(String(getValue(s, valuekey, textkey)), item, valuekey, textkey, childkey));
if (indeterminate) {
item.__checked = 2;
}
@@ -609,10 +615,10 @@ export class Dropdown {
}
}
_contains(it, item, valuekey, textkey) {
if (Array.isArray(item.children)) {
for (let t of item.children) {
if (it === getValue(t, valuekey, textkey)) {
_contains(it, item, valuekey, textkey, childkey) {
if (Array.isArray(item[childkey])) {
for (let t of item[childkey]) {
if (it === String(getValue(t, valuekey, textkey))) {
return true;
}
if (this._contains(it, t, valuekey, textkey)) {
@@ -629,34 +635,32 @@ export class Dropdown {
const textkey = this._var.options.textKey;
const template = this._var.options.htmlTemplate;
const htmlkey = this._var.options.htmlKey;
const childkey = this._var.options.childKey;
const selected = this.selected;
let scrolled;
array.forEach((item, i) => {
let val = getValue(item, valuekey, textkey);
if (typeof val !== 'string') {
val = String(val);
}
const val = String(getValue(item, valuekey, textkey));
const li = createElement('li');
li.dataset.value = val;
li.title = item[textkey];
if (item.__level > 0) {
if (!isNaN(item.__level) && item.__level > 0) {
li.style.marginLeft = `${item.__level * 24}px`;
}
const wrapper = createElement('span', 'li-wrapper',
createElement('span', span => {
// events
span.className = 'ui-expandor';
if (Array.isArray(item.children) && item.children.length > 0) {
span.classList.add('active');
function hideChildren(children) {
for (let c of children) {
c.__visible = false;
if (Array.isArray(c.children)) {
hideChildren(c.children);
}
}
}
}
// if (Array.isArray(item[childkey]) && item[childkey].length > 0) {
// span.classList.add('active');
// function hideChildren(children) {
// for (let c of children) {
// c.__visible = false;
// if (Array.isArray(c[childkey])) {
// hideChildren(c[childkey]);
// }
// }
// }
// }
},
createIcon('fa-solid', 'caret-down')
)
@@ -713,6 +717,7 @@ export class Dropdown {
const textkey = this._var.options.textKey;
const template = this._var.options.htmlTemplate;
const htmlkey = this._var.options.htmlKey;
const childkey = this._var.options.childKey;
const source = this.source;
if (checkbox.getAttribute('isall') === '1') {
const allchecked = this._var.allChecked = checkbox.checked;
@@ -725,19 +730,15 @@ export class Dropdown {
const val = checkbox.dataset.value;
const getSelectedList = () => {
const list = [];
const cache = {};
source.filter(it => it.__p == null).forEach(it => {
const key = String(getValue(it, valuekey, textkey));
if (it.__checked === 1) {
list.push(it);
cache[getValue(it, valuekey, textkey)] = true;
}
});
source.filter(it => it.__p != null).forEach(it => {
if (cache[it.__p]) {
return;
}
if (it.__checked === 1) {
list.push(it);
} else if (it.__checked === 2) {
const children = source.filter(c => c.__checked === 1 && c.__p === key);
if (children.length > 0) {
Array.prototype.push.apply(list, children);
}
}
});
return list;
@@ -746,15 +747,16 @@ export class Dropdown {
item.__checked = 1;
if (item.__p != null) {
// parent
if (!source.filter(it => it.__p === item.__p).some(it => it.__checked !== 1)) {
const parent = source.filter(it => String(getValue(it, valuekey, textkey)) === item.__p)[0];
if (parent != null) {
const parent = source.filter(it => String(getValue(it, valuekey, textkey)) === item.__p)[0];
if (parent != null) {
if (!source.filter(it => it.__p === item.__p).some(it => it.__checked !== 1)) {
parent.__checked = 1;
} else if (parent.__checked !== 2) {
parent.__checked = 2;
}
}
}
if (item.children?.length > 0) {
// children
if (item[childkey]?.length > 0) {
source.filter(it => it.__p === val).forEach(it => it.__checked = 1);
}
if (!source.some(it => it.__checked !== 1)) {
@@ -767,10 +769,9 @@ export class Dropdown {
list = getSelectedList();
}
} else {
if (item.children?.length > 0) {
if (item[childkey]?.length > 0) {
if (item.__checked === 2) {
item.__checked = 0;
// children
source.filter(it => it.__p === val).forEach(it => it.__checked = 0);
} else {
item.__checked = 2;