fineui是帆软报表和BI产品线所使用的前端框架。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

999 lines
34 KiB

import {
Widget,
extend,
emptyFn,
isNotNull,
some,
Tree,
isEmpty,
each,
clone,
isNull,
UUID,
size,
i18nText,
deepClone,
isNotEmptyArray,
last,
has,
nextTick,
concat,
filter,
Func,
any,
every,
map,
difference,
keys,
isPlainObject,
get,
set
} from "@/core";
import { TreeView } from "@/case";
export class AbstractTreeValueChooser extends Widget {
_const = { perPage: 100 };
_defaultConfig() {
return extend(super._defaultConfig(...arguments), {
items: null,
itemsCreator: emptyFn,
open: false,
});
}
_valueFormatter(v) {
let text = v;
if (this.options.valueFormatter) {
return this.options.valueFormatter(v);
}
if (isNotNull(this.items)) {
some(this.items, (i, item) => {
if (item.value === v || `${item.value}` === v) {
text = item.text;
return true;
}
});
}
return text;
}
_initData(items) {
this.items = items;
const nodes = Tree.treeFormat(items);
this.tree = new Tree();
this.tree.initTree(nodes);
}
_itemsCreator(options, callback) {
const call = () => {
switch (options.type) {
case TreeView.REQ_TYPE_INIT_DATA:
this._reqInitTreeNode(options, callback);
break;
case TreeView.REQ_TYPE_ADJUST_DATA:
this._reqAdjustTreeNode(options, callback);
break;
case TreeView.REQ_TYPE_SELECT_DATA:
this._reqSelectedTreeNode(options, callback);
break;
case TreeView.REQ_TYPE_GET_SELECTED_DATA:
this._reqDisplayTreeNode(options, callback);
break;
default:
this._reqTreeNode(options, callback);
break;
}
};
const o = this.options;
if (!this.items) {
o.itemsCreator({}, items => {
this._initData(items);
call();
});
} else {
call();
}
}
_reqDisplayTreeNode(op, callback) {
const result = [];
const selectedValues = op.selectedValues;
if (selectedValues == null || isEmpty(selectedValues)) {
callback({});
return;
}
const getCount = (jo, parentValues) => {
if (jo == null) {
return 0;
}
if (isEmpty(jo)) {
return this._getChildCount(parentValues);
}
return size(jo);
};
const doCheck = (parentValues, node, selected) => {
if (selected == null || isEmpty(selected)) {
each(node.getChildren(), (i, child) => {
const newParents = clone(parentValues);
newParents.push(child.value);
const llen = this._getChildCount(newParents);
createOneJson(child, node.id, llen);
doCheck(newParents, child, {});
});
return;
}
each(selected, k => {
const node = this._getTreeNode(parentValues, k);
// 找不到就是新增值
if (isNull(node)) {
createOneJson(
{
id: UUID(),
text: k,
value: k,
},
UUID(),
0
);
} else {
const newParents = clone(parentValues);
newParents.push(node.value);
createOneJson(node, node.parent && node.parent.id, getCount(selected[k], newParents));
doCheck(newParents, node, selected[k]);
}
});
};
doCheck([], this.tree.getRoot(), selectedValues);
callback({
items: result,
});
function createOneJson(node, pId, llen) {
result.push({
id: node.id,
pId,
text:
node.text +
(llen > 0 ? `(${i18nText("BI-Basic_Altogether")}${llen}${i18nText("BI-Basic_Count")})` : ""),
value: node.value,
open: true,
disabled: node.disabled,
iconCls: node.iconCls,
});
}
}
_reqSelectedTreeNode(op, callback) {
const self = this;
const selectedValues = deepClone(op.selectedValues);
const notSelectedValue = op.notSelectedValue || {};
const keyword = op.keyword || "";
const parentValues = op.parentValues || [];
if (selectedValues == null || isEmpty(selectedValues)) {
callback({});
return;
}
dealWithSelectedValues(selectedValues);
callback(selectedValues);
function dealWithSelectedValues(selectedValues) {
let p = parentValues.concat(notSelectedValue);
// 存储的值中存在这个值就把它删掉
// 例如选中了中国-江苏-南京, 取消中国或江苏或南京
// p长度不大于selectedValues的情况才可能找到,这样可以直接删除selectedValues的节点
if (canFindKey(selectedValues, p)) {
// 如果搜索的值在父亲链中
if (isSearchValueInParent(p)) {
// 例如选中了 中国-江苏, 搜索江苏, 取消江苏(干掉了江苏)
self._deleteNode(selectedValues, p);
} else {
const searched = [];
// 要找到所有以notSelectedValue为叶子节点的链路
const find = search(parentValues, notSelectedValue, [], searched);
if (find && isNotEmptyArray(searched)) {
each(searched, (i, arr) => {
const node = self._getNode(selectedValues, arr);
if (node) {
// 例如选中了 中国-江苏, 搜索江苏, 取消中国(实际上只想删除中国-江苏,因为搜的是江苏)
// 例如选中了 中国-江苏-南京,搜索南京,取消中国(实际上只想删除中国-江苏-南京,因为搜的是南京)
self._deleteNode(selectedValues, arr);
} else {
// 例如选中了 中国-江苏,搜索南京,取消中国(实际上只想删除中国-江苏-南京,因为搜的是南京)
expandSelectedValue(selectedValues, arr, last(arr));
}
});
}
}
}
// 存储的值中不存在这个值,但父亲节点是全选的情况
// 例如选中了中国-江苏,取消南京
// important 选中了中国-江苏,取消了江苏,但是搜索的是南京
if (isChild(selectedValues, p)) {
const result = [];
let find = false;
// 如果parentValues中有匹配的值,说明搜索结果不在当前值下
if (isSearchValueInParent(p)) {
find = true;
} else {
// 从当前值开始搜
find = search(parentValues, notSelectedValue, result);
p = parentValues;
}
if (find === true) {
// 去掉点击的节点之后的结果集
expandSelectedValue(selectedValues, p, notSelectedValue);
// 添加去掉搜索的结果集
if (result.length > 0) {
each(result, (i, strs) => {
self._buildTree(selectedValues, strs);
});
}
}
}
};
function expandSelectedValue(selectedValues, parents, notSelectedValue) {
let next = selectedValues;
const childrenCount = [];
const path = [];
// 去掉点击的节点之后的结果集
some(parents, (i, v) => {
const t = next[v];
if (t == null) {
if (i === 0) {
return true;
}
if (isEmpty(next)) {
const split = parents.slice(0, i);
const expanded = self._getChildren(split);
path.push(split);
childrenCount.push(expanded.length);
// 如果只有一个值且取消的就是这个值
if (
i === parents.length - 1 &&
expanded.length === 1 &&
expanded[0].value === notSelectedValue
) {
for (let j = childrenCount.length - 1; j >= 0; j--) {
if (childrenCount[j] === 1) {
self._deleteNode(selectedValues, path[j]);
} else {
break;
}
}
} else {
each(expanded, (m, child) => {
if (i === parents.length - 1 && child.value === notSelectedValue) {
return true;
}
next[child.value] = {};
});
}
next = next[v];
} else {
return true;
// next = {};
// next[v] = {};
}
} else {
next = t;
}
});
};
function search(parents, current, result, searched) {
const newParents = clone(parents);
newParents.push(current);
if (self._isMatch(parents, current, keyword)) {
searched && searched.push(newParents);
return true;
}
const children = self._getChildren(newParents);
const notSearch = [];
let can = false;
each(children, (i, child) => {
if (search(newParents, child.value, result, searched)) {
can = true;
} else {
notSearch.push(child.value);
}
});
if (can === true) {
each(notSearch, (i, v) => {
const next = clone(newParents);
next.push(v);
result.push(next);
});
}
return can;
};
function isSearchValueInParent(parentValues) {
for (let i = 0, len = parentValues.length; i < len; i++) {
if (self._isMatch(parentValues.slice(0, i), parentValues[i], keyword)) {
return true;
}
}
return false;
};
function canFindKey(selectedValues, parents) {
let t = selectedValues;
for (let i = 0; i < parents.length; i++) {
const v = parents[i];
t = t[v];
if (t == null) {
return false;
}
}
return true;
}
function isChild(selectedValues, parents) {
let t = selectedValues;
for (let i = 0; i < parents.length; i++) {
const v = parents[i];
if (!has(t, v)) {
return false;
}
t = t[v];
if (isEmpty(t)) {
return true;
}
}
return false;
}
}
_reqAdjustTreeNode(op, callback) {
const result = [];
const selectedValues = op.selectedValues;
if (selectedValues == null || isEmpty(selectedValues)) {
callback({});
return;
}
each(selectedValues, (k, v) => {
result.push([k]);
});
const isAllSelected = (selected, parents) => isEmpty(selected) || this._getChildCount(parents) === size(selected);
function dealWithSelectedValues(selected, parents) {
if (selected == null || isEmpty(selected)) {
return true;
}
let can = true;
each(selected, (k, v) => {
const p = clone(parents);
p.push(k);
if (!dealWithSelectedValues(selected[k], p) || op.searcherPaneAutoShrink === false) {
each(selected[k], (nk, nv) => {
const t = clone(p);
t.push(nk);
result.push(t);
});
can = false;
}
});
return can && isAllSelected(selected, parents);
}
dealWithSelectedValues(selectedValues, []);
const jo = {};
each(result, (i, strs) => {
this._buildTree(jo, strs);
});
callback(jo);
}
_reqInitTreeNode(op, callback) {
let result = [];
const self = this;
const keyword = op.keyword || "";
const selectedValues = op.selectedValues;
const lastSearchValue = op.lastSearchValue || ""; // 一次请求100个,但是搜索是拿全部的,lastSearchValue是上一次遍历到的节点索引
const output = search();
nextTick(() => {
callback({
hasNext: output.length > this._const.perPage,
items: result,
lastSearchValue: last(output),
});
});
function search() {
const children = self._getChildren([]);
let start = children.length;
if (lastSearchValue !== "") {
for (let j = 0, len = start; j < len; j++) {
if (children[j].value === lastSearchValue) {
start = j + 1;
break;
}
}
} else {
start = 0;
}
const output = [];
for (let i = start, len = children.length; i < len; i++) {
let find;
if (output.length < self._const.perPage) {
find = nodeSearch(1, [], children[i].value, false, result);
} else if (output.length === self._const.perPage) {
find = nodeSearch(1, [], children[i].value, false, []);
}
if (find[0] === true) {
output.push(children[i].value);
}
if (output.length > self._const.perPage) {
break;
}
}
// 深层嵌套的比较麻烦,这边先实现的是在根节点添加
if (op.times === 1) {
const nodes = self._getAddedValueNode([], selectedValues);
result = concat(
filter(nodes, (idx, node) => {
const find = Func.getSearchResult([node.text || node.value], keyword);
return find.find.length > 0 || find.match.length > 0;
}),
result
);
}
return output;
}
function nodeSearch(deep, parentValues, current, isAllSelect, result) {
if (self._isMatch(parentValues, current, keyword)) {
const checked = isAllSelect || isSelected(parentValues, current);
createOneJson(
parentValues,
current,
false,
checked,
!isAllSelect && isHalf(parentValues, current),
true,
result
);
return [true, checked];
}
const newParents = clone(parentValues);
newParents.push(current);
const children = self._getChildren(newParents);
let can = false,
checked = false;
const isCurAllSelected = isAllSelect || isAllSelected(parentValues, current);
each(children, (i, child) => {
const state = nodeSearch(deep + 1, newParents, child.value, isCurAllSelected, result);
// 当前节点的子节点是否选中,并不确定全选还是半选
if (state[1] === true) {
checked = true;
}
// 当前节点的子节点要不要加入到结果集中
if (state[0] === true) {
can = true;
}
});
// 子节点匹配, 补充父节点
if (can === true) {
checked = isCurAllSelected || (isSelected(parentValues, current) && checked);
createOneJson(parentValues, current, true, checked, false, false, result);
}
return [can, checked];
}
function createOneJson(parentValues, value, isOpen, checked, half, flag, result) {
const node = self._getTreeNode(parentValues, value);
result.push({
id: node.id,
pId: node.pId,
text: node.text,
value: node.value,
title: node.title,
isParent: node.getChildrenLength() > 0,
open: isOpen,
checked,
halfCheck: half,
flag,
disabled: node.disabled,
iconCls: node.iconCls,
});
}
function isHalf(parentValues, value) {
const find = findSelectedObj(parentValues);
if (find == null) {
return null;
}
return any(find, (v, ob) => {
if (v === value) {
if (ob != null && !isEmpty(ob)) {
return true;
}
}
});
}
function isAllSelected(parentValues, value) {
const find = findSelectedObj(parentValues);
if (find == null) {
return null;
}
return any(find, (v, ob) => {
if (v === value) {
if (ob != null && isEmpty(ob)) {
return true;
}
}
});
}
function isSelected(parentValues, value) {
const find = findSelectedObj(parentValues);
if (find == null) {
return false;
}
return any(find, v => {
if (v === value) {
return true;
}
});
}
function findSelectedObj(parentValues) {
let find = selectedValues;
if (find == null) {
return null;
}
every(parentValues, (i, v) => {
find = find[v];
if (find == null) {
return false;
}
return true;
});
return find;
}
}
_reqTreeNode(op, callback) {
const o = this.options;
let result = [];
const times = op.times;
const checkState = op.checkState || {};
const parentValues = op.parentValues || [];
const selectedValues = op.selectedValues || {};
const getCheckState = (current, parentValues, valueMap, checkState) => {
// 节点本身的checked和half优先级最高
const checked = checkState.checked,
half = checkState.half;
let tempCheck = false,
halfCheck = false;
if (has(valueMap, current)) {
// 可能是半选
if (valueMap[current][0] === 1) {
const values = clone(parentValues);
values.push(current);
const childCount = this._getChildCount(values);
if (childCount > 0 && childCount !== valueMap[current][1]) {
halfCheck = true;
}
} else if (valueMap[current][0] === 2) {
tempCheck = true;
}
}
let check;
// 展开的节点checked为false 且没有明确得出当前子节点是半选或者全选, 则check状态取决于valueMap
if (!checked && !halfCheck && !tempCheck) {
check = has(valueMap, current);
} else {
// 不是上面那种情况就先看在节点没有带有明确半选的时候,通过节点自身的checked和valueMap的状态能都得到选中信息
check = ((tempCheck || checked) && !half) || has(valueMap, current);
}
return [check, halfCheck];
};
const getResult = (parentValues, checkState) => {
let valueMap = {};
// if (judgeState(parentValues, selectedValues, checkState)) {
const isAllSelected = (selected, parents) => {
if (isEmpty(selected)) {
return true;
}
if (this._getChildCount(parents) !== size(selected)) {
return false;
}
return every(selected, value => isAllSelected(selected[value], concat(parents, value)));
};
function dealWithSelectedValue(parentValues, selectedValues) {
const valueMap = {},
parents = (parentValues || []).slice(0);
each(parentValues, (i, v) => {
parents.push(v);
selectedValues = selectedValues[v] || {};
});
each(selectedValues, (value, obj) => {
const currentParents = concat(parents, value);
if (isNull(obj)) {
valueMap[value] = [0, 0];
return;
}
if (isEmpty(obj)) {
valueMap[value] = [2, 0];
return;
}
const nextNames = {};
each(obj, (t, o) => {
if (isNull(o) || isEmpty(o)) {
nextNames[t] = true;
} else {
isAllSelected(o, concat(currentParents, [t])) && (nextNames[t] = true);
}
});
// valueMap的数组第一个参数为不选: 0, 半选: 1, 全选:2, 第二个参数为改节点下选中的子节点个数(子节点全选或者不存在)
valueMap[value] = [1, size(nextNames)];
});
return valueMap;
}
valueMap = dealWithSelectedValue(parentValues, selectedValues);
// }
const nodes = this._getChildren(parentValues);
for (let i = (times - 1) * this._const.perPage; nodes[i] && i < times * this._const.perPage; i++) {
const state = getCheckState(nodes[i].value, parentValues, valueMap, checkState);
const openState = o.open || nodes[i].open;
result.push({
id: nodes[i].id,
pId: nodes[i].pId,
value: nodes[i].value,
text: nodes[i].text,
times: 1,
isParent: nodes[i].isParent || nodes[i].getChildrenLength() > 0,
checked: state[0],
half: state[1],
halfCheck: openState ? false : state[1],
open: openState,
disabled: nodes[i].disabled,
title: nodes[i].title || nodes[i].text,
warningTitle: nodes[i].warningTitle,
iconCls: nodes[i].iconCls,
});
if (openState) {
getResult(parentValues.concat([nodes[i].value]), { checked: state[0], half: state[1] });
}
}
};
getResult(parentValues, checkState);
// 如果指定节点全部打开
// if (o.open) {
// var allNodes = [];
// // 获取所有节点
// each(nodes, function (idx, node) {
// allNodes = concat(allNodes, self._getAllChildren(parentValues.concat([node.value])));
// });
// var lastFind;
// each(allNodes, function (idx, node) {
// var valueMap = dealWithSelectedValue(node.parentValues, selectedValues);
// // REPORT-24409 fix: 设置节点全部展开,添加的节点没有给状态
// var parentCheckState = {};
// var find = find(result, function (idx, pNode) {
// return pNode.id === node.pId;
// });
// if (find) {
// parentCheckState.checked = find.halfCheck ? false : find.checked;
// parentCheckState.half = find.halfCheck;
// // 默认展开也需要重置父节点的halfCheck
// if (isNotNull(lastFind) && (lastFind !== find || allNodes.length - 1 === idx)) {
// lastFind.half = lastFind.halfCheck;
// lastFind.halfCheck = false;
// }
// }
// lastFind = find;
// var state = getCheckState(node.value, node.parentValues, valueMap, parentCheckState);
// result.push({
// id: node.id,
// pId: node.pId,
// value: node.value,
// text: node.text,
// times: 1,
// isParent: node.getChildrenLength() > 0,
// checked: state[0],
// halfCheck: state[1],
// open: true,
// disabled: node.disabled,
// title: node.title || node.text,
// warningTitle: node.warningTitle,
// });
// });
// }
// 深层嵌套的比较麻烦,这边先实现的是在根节点添加
if (parentValues.length === 0 && times === 1) {
result = concat(this._getAddedValueNode(parentValues, selectedValues), result);
}
nextTick(() => {
callback({
items: result,
hasNext: this._getChildren(parentValues).length > times * this._const.perPage,
});
});
// function judgeState(parentValues, selected_value, checkState) {
// var checked = checkState.checked, half = checkState.half;
// if (parentValues.length > 0 && !checked) {
// return false;
// }
// return (parentValues.length === 0 || (checked && half) && !isEmpty(selected_value));
// }
}
_getAddedValueNode(parentValues, selectedValues) {
const nodes = this._getChildren(parentValues);
return map(difference(keys(selectedValues), map(nodes, "value")), (idx, v) => {
return {
id: UUID(),
pId: nodes.length > 0 ? nodes[0].pId : UUID(),
value: v,
text: v,
times: 1,
isParent: false,
checked: true,
halfCheck: false,
};
});
}
_getNode(selectedValues, parentValues) {
let pNode = selectedValues;
for (let i = 0, len = parentValues.length; i < len; i++) {
if (pNode == null) {
return null;
}
pNode = pNode[parentValues[i]];
}
return pNode;
}
_deleteNode(selectedValues, values) {
let name = values[values.length - 1];
let p = values.slice(0, values.length - 1);
let pNode = this._getNode(selectedValues, p);
if (pNode != null && pNode[name]) {
delete pNode[name];
// 递归删掉空父节点
while (p.length > 0 && isEmpty(pNode)) {
name = p[p.length - 1];
p = p.slice(0, p.length - 1);
pNode = this._getNode(selectedValues, p);
if (pNode != null) {
delete pNode[name];
}
}
}
}
_buildTree(jo, values) {
let t = jo;
each(values, (i, v) => {
if (!has(t, v)) {
t[v] = {};
}
t = t[v];
});
}
_isMatch(parentValues, value, keyword) {
const o = this.options;
const node = this._getTreeNode(parentValues, value);
if (!node) {
return false;
}
const find = Func.getSearchResult([node.text || node.value], keyword);
if (o.allowSearchValue && node.value) {
const valueFind = Func.getSearchResult([node.value], keyword);
return (
valueFind.find.length > 0 || valueFind.match.length > 0 || find.find.length > 0 || find.match.length > 0
);
}
return find.find.length > 0 || find.match.length > 0;
}
_getTreeNode(parentValues, v) {
let findParentNode;
let index = 0;
let currentParent = this.tree.getRoot();
this.tree.traverse(node => {
if (this.tree.isRoot(node)) {
return;
}
if (index > parentValues.length) {
return false;
}
/**
* 一个树结构。要找root_1_3的子节点
* {root: { 1: {1: {}, 2: {}, 3: {}}, 3: {1: {}, 2: {}} } }
* 当遍历到root_1节点时,index++,而下一个节点root_3时,符合下面的if逻辑,这样找到的节点就是root_3节点了,需要加步判断是否是root_1的子节点
*/
if (index === parentValues.length && node.value === v) {
if (node.getParent() !== currentParent) {
return;
}
findParentNode = node;
return false;
}
if (node.value === parentValues[index] && node.getParent() === currentParent) {
index++;
currentParent = node;
return;
}
return true;
});
return findParentNode;
}
_getChildren(parentValues) {
let parent;
if (parentValues.length > 0) {
const value = last(parentValues);
parent = this._getTreeNode(parentValues.slice(0, parentValues.length - 1), value);
} else {
parent = this.tree.getRoot();
}
return parent ? parent.getChildren() : [];
}
_getAllChildren(parentValues) {
const children = this._getChildren(parentValues);
let nodes = [].concat(children);
each(nodes, (idx, node) => {
node.parentValues = parentValues;
});
let queue = map(children, (idx, node) => {
return {
parentValues,
value: node.value,
};
});
while (isNotEmptyArray(queue)) {
const node = queue.shift();
const pValues = node.parentValues.concat(node.value);
const childNodes = this._getChildren(pValues);
each(childNodes, (idx, node) => {
node.parentValues = pValues;
});
queue = queue.concat(childNodes);
nodes = nodes.concat(childNodes);
}
return nodes;
}
_getChildCount(parentValues) {
return this._getChildren(parentValues).length;
}
assertSelectedValue(selectedValues, items = []) {
if (isPlainObject(selectedValues)) {
return selectedValues;
}
const tree = Tree.transformToTreeFormat(items);
const value2ParentMap = {};
Tree.traversal(tree, (index, node, pNode) => {
value2ParentMap[node.value] = pNode;
});
const result = {};
each(selectedValues, (index, value) => {
let curr = value;
const parentPath = [];
while (curr) {
parentPath.unshift(curr);
curr = value2ParentMap[curr]?.value;
}
each(parentPath, index => {
if (isNull(get(result, parentPath.slice(0, index + 1)))) {
set(result, parentPath.slice(0, index + 1), {});
}
});
// 执行完一条路径,check一下
const lengths = size(get(result, parentPath.slice(0, -1)));
if (lengths === value2ParentMap[value]?.children?.length) {
set(result, parentPath.slice(0, -1), {});
}
});
return result;
}
buildCompleteTree(selectedValues) {
const result = {};
const fill = (parentValues, node, selected, r) => {
if (selected === null || isEmpty(selected)) {
each(node.getChildren(), (i, child) => {
const newParents = clone(parentValues);
newParents.push(child.value);
r[child.value] = {};
fill(newParents, child, null, r[child.value]);
});
return;
}
each(selected, k => {
const node = this._getTreeNode(parentValues, k);
const newParents = clone(parentValues);
newParents.push(node.value);
r[k] = {};
fill(newParents, node, selected[k], r[k]);
});
}
if (selectedValues !== null && !isEmpty(selectedValues)) {
fill([], this.tree.getRoot(), selectedValues, result);
}
return result;
}
}