387 lines
10 KiB
Vue
387 lines
10 KiB
Vue
<template>
|
|
<div
|
|
class="org-chart-node"
|
|
@contextmenu="$event => this.handleContextMenu($event)"
|
|
v-if="node.visible"
|
|
:class="{
|
|
collapsed: !node.leftExpanded || !node.expanded,
|
|
'is-leaf': isLeaf,
|
|
'is-current': node.isCurrent,
|
|
'is-left-child-node': isLeftChildNode,
|
|
'is-not-child': node.level === 1 && node.childNodes.length <= 0 && leftChildNodes.length <= 0,
|
|
'only-both-tree-node': node.level === 1 && tree.store.onlyBothTree
|
|
}"
|
|
>
|
|
<transition :duration="animateDuration" :name="animateName">
|
|
<div
|
|
class="org-chart-node-left-children"
|
|
v-if="showLeftChildNode"
|
|
v-show="node.leftExpanded"
|
|
>
|
|
<OkrTreeNode
|
|
v-for="child in leftChildNodes"
|
|
:show-collapsable="showCollapsable"
|
|
:node="child"
|
|
:label-width="labelWidth"
|
|
:label-height="labelHeight"
|
|
:renderContent="renderContent"
|
|
:nodeBtnContent="nodeBtnContent"
|
|
:selected-key="selectedKey"
|
|
:node-key="nodeKey"
|
|
:key="getNodeKey(child)"
|
|
:props="props"
|
|
:show-node-num="showNodeNum"
|
|
is-left-child-node
|
|
></OkrTreeNode>
|
|
</div>
|
|
</transition>
|
|
<div class="org-chart-node-label"
|
|
:class="{
|
|
'is-root-label': node.level === 1,
|
|
'is-not-right-child': node.level === 1 && node.childNodes.length <= 0,
|
|
'is-not-left-child': node.level === 1 && leftChildNodes.length <= 0
|
|
}"
|
|
>
|
|
<div
|
|
v-if="showNodeLeftBtn && leftChildNodes.length > 0"
|
|
class="org-chart-node-left-btn"
|
|
:class="{ expanded: node.leftExpanded }"
|
|
@click="handleBtnClick('left')">
|
|
<template v-if="showNodeNum" >
|
|
<span v-if="!node.leftExpanded" class="org-chart-node-btn-text">
|
|
{{ (node.level === 1 && leftChildNodes.length > 0) ? leftChildNodes.length : node.childNodes.length }}
|
|
</span>
|
|
</template>
|
|
<node-btn-content v-else :node="node">
|
|
<slot>
|
|
</slot>
|
|
</node-btn-content>
|
|
</div>
|
|
<div
|
|
class="org-chart-node-label-inner"
|
|
@click="handleNodeClick"
|
|
:class="computeLabelClass"
|
|
:style="computeLabelStyle"
|
|
>
|
|
<node-content :node="node">
|
|
<slot>
|
|
{{ node.label }}
|
|
</slot>
|
|
</node-content>
|
|
</div>
|
|
<div
|
|
v-if="showNodeBtn && !isLeftChildNode"
|
|
class="org-chart-node-btn"
|
|
:class="{ expanded: node.expanded }"
|
|
@click="handleBtnClick('right')">
|
|
<template v-if="showNodeNum">
|
|
<span v-if="!node.expanded " class="org-chart-node-btn-text">
|
|
{{ node.childNodes.length }}
|
|
</span>
|
|
</template>
|
|
<node-btn-content v-else :node="node">
|
|
<slot>
|
|
<!-- <div class="org-chart-node-btn-text">10</div> -->
|
|
</slot>
|
|
</node-btn-content>
|
|
</div>
|
|
</div>
|
|
<transition :duration="animateDuration" :name="animateName">
|
|
<div
|
|
class="org-chart-node-children"
|
|
v-if="!isLeftChildNode && node.childNodes && node.childNodes.length > 0"
|
|
v-show="node.expanded"
|
|
>
|
|
<OkrTreeNode
|
|
v-for="child in node.childNodes"
|
|
:show-collapsable="showCollapsable"
|
|
:node="child"
|
|
:label-width="labelWidth"
|
|
:label-height="labelHeight"
|
|
:renderContent="renderContent"
|
|
:nodeBtnContent="nodeBtnContent"
|
|
:selected-key="selectedKey"
|
|
:node-key="nodeKey"
|
|
:key="getNodeKey(child)"
|
|
:show-node-num='showNodeNum'
|
|
:props="props"
|
|
></OkrTreeNode>
|
|
</div>
|
|
</transition>
|
|
</div>
|
|
</template>
|
|
<script>
|
|
import { getNodeKey } from "./model/util";
|
|
export default {
|
|
name: "OkrTreeNode",
|
|
inject: ["okrEventBus"],
|
|
props: {
|
|
props: {},
|
|
node: {
|
|
default() {
|
|
return {};
|
|
}
|
|
},
|
|
root: {
|
|
default() {
|
|
return {};
|
|
}
|
|
},
|
|
// 子节点是否可折叠
|
|
showCollapsable: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
// 判断是否是左子树的节点,样式需要改
|
|
isLeftChildNode: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
// 树节点的内容区的渲染 Function
|
|
renderContent: Function,
|
|
// 展开节点的内容渲染 Function
|
|
nodeBtnContent: Function,
|
|
// 显示节点数
|
|
showNodeNum: Boolean,
|
|
// 树节点区域的宽度
|
|
labelWidth: [String, Number],
|
|
// 树节点区域的高度
|
|
labelHeight: [String, Number],
|
|
// 用来控制选择节点的字段名
|
|
selectedKey: String,
|
|
// 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的
|
|
nodeKey: String
|
|
},
|
|
components: {
|
|
NodeContent: {
|
|
props: {
|
|
node: {
|
|
required: true
|
|
}
|
|
},
|
|
render(h) {
|
|
const parent = this.$parent;
|
|
if (parent._props.renderContent) {
|
|
return parent._props.renderContent(h, this.node);
|
|
} else {
|
|
return this.$scopedSlots.default(this.node);
|
|
}
|
|
}
|
|
},
|
|
// 渲染展开节点的样式
|
|
NodeBtnContent: {
|
|
props: {
|
|
node: {
|
|
required: true
|
|
}
|
|
},
|
|
render(h) {
|
|
const parent = this.$parent;
|
|
if (parent._props.nodeBtnContent) {
|
|
return parent._props.nodeBtnContent(h, this.node);
|
|
} else {
|
|
if (this.$scopedSlots.default) {
|
|
return this.$scopedSlots.default(this.node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
computed: {
|
|
isLeaf () {
|
|
if (this.node.level === 1) {
|
|
if (this.leftChildNodes.length == 0 && this.node.childNodes.length == 0) {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
} else {
|
|
return this.node.isLeaf
|
|
}
|
|
},
|
|
leftChildNodes() {
|
|
if (this.tree.store.onlyBothTree) {
|
|
if (this.isLeftChildNode) {
|
|
return this.node.childNodes;
|
|
} else {
|
|
return this.node.leftChildNodes;
|
|
}
|
|
}
|
|
return [];
|
|
},
|
|
animateName() {
|
|
if (this.tree.store.animate) {
|
|
return this.tree.store.animateName;
|
|
}
|
|
return "";
|
|
},
|
|
animateDuration() {
|
|
if (this.tree.store.animate) {
|
|
return this.tree.store.animateDuration;
|
|
}
|
|
return 0;
|
|
},
|
|
// 是否显示展开按钮
|
|
showNodeBtn() {
|
|
if (this.isLeftChildNode) {
|
|
return (
|
|
(this.tree.store.direction === "horizontal" &&
|
|
this.showCollapsable &&
|
|
this.leftChildNodes.length > 0) || this.level === 1
|
|
);
|
|
}
|
|
return (
|
|
this.showCollapsable &&
|
|
this.node.childNodes &&
|
|
this.node.childNodes.length > 0
|
|
)
|
|
},
|
|
showNodeLeftBtn() {
|
|
return (
|
|
(this.tree.store.direction === "horizontal" &&
|
|
this.showCollapsable &&
|
|
this.leftChildNodes.length > 0)
|
|
)
|
|
},
|
|
// 节点的宽度
|
|
computeLabelStyle() {
|
|
let { labelWidth = "auto", labelHeight = "auto" } = this;
|
|
if (typeof labelWidth === "number") {
|
|
labelWidth = `${labelWidth}px`;
|
|
}
|
|
if (typeof labelHeight === "number") {
|
|
labelHeight = `${labelHeight}px`;
|
|
}
|
|
return {
|
|
width: labelWidth,
|
|
height: labelHeight
|
|
};
|
|
},
|
|
computeLabelClass() {
|
|
let clsArr = [];
|
|
const store = this.tree.store;
|
|
if (store.labelClassName) {
|
|
if (typeof store.labelClassName === "function") {
|
|
clsArr.push(store.labelClassName(this.node));
|
|
} else {
|
|
clsArr.push(store.labelClassName);
|
|
}
|
|
}
|
|
if (store.currentLableClassName && this.node.isCurrent) {
|
|
if (typeof store.currentLableClassName === "function") {
|
|
clsArr.push(store.currentLableClassName(this.node));
|
|
} else {
|
|
clsArr.push(store.currentLableClassName);
|
|
}
|
|
}
|
|
if (this.node.isCurrent) {
|
|
clsArr.push("is-current");
|
|
}
|
|
return clsArr;
|
|
},
|
|
computNodeStyle() {
|
|
return {
|
|
display: this.node.expanded ? "" : "none"
|
|
};
|
|
},
|
|
computLeftNodeStyle() {
|
|
return {
|
|
display: this.node.leftExpanded ? "" : "none"
|
|
};
|
|
},
|
|
// 是否显示左子数
|
|
showLeftChildNode() {
|
|
return (
|
|
this.tree.onlyBothTree &&
|
|
this.tree.store.direction === "horizontal" &&
|
|
this.leftChildNodes &&
|
|
this.leftChildNodes.length > 0
|
|
);
|
|
}
|
|
},
|
|
watch: {
|
|
"node.expanded"(val) {
|
|
// this.$nextTick(() => this.expanded = val);
|
|
},
|
|
"node.leftExpanded"(val) {
|
|
// this.$nextTick(() => this.expanded = val);
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
// node 的展开状态
|
|
expanded: false,
|
|
tree: null
|
|
};
|
|
},
|
|
created() {
|
|
const parent = this.$parent;
|
|
if (parent.isTree) {
|
|
this.tree = parent;
|
|
} else {
|
|
this.tree = parent.tree;
|
|
}
|
|
|
|
const tree = this.tree;
|
|
if (!tree) {
|
|
console.warn("Can not find node's tree.");
|
|
}
|
|
},
|
|
methods: {
|
|
getNodeKey(node) {
|
|
return getNodeKey(this.nodeKey, node.data);
|
|
},
|
|
handleNodeClick() {
|
|
const store = this.tree.store;
|
|
store.setCurrentNode(this.node);
|
|
this.tree.$emit("node-click", this.node.data, this.node, this);
|
|
},
|
|
handleBtnClick(isLeft) {
|
|
isLeft = isLeft === "left";
|
|
const store = this.tree.store;
|
|
// 表示是OKR飞书模式
|
|
if (store.onlyBothTree) {
|
|
if (isLeft) {
|
|
if (this.node.leftExpanded) {
|
|
this.node.leftExpanded = false;
|
|
this.tree.$emit("node-collapse", this.node.data, this.node, this);
|
|
} else {
|
|
this.node.leftExpanded = true;
|
|
this.tree.$emit("node-expand", this.node.data, this.node, this);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
if (this.node.expanded) {
|
|
this.node.collapse();
|
|
this.tree.$emit("node-collapse", this.node.data, this.node, this);
|
|
} else {
|
|
if (this.node.parent.childNodes && this.node.parent.childNodes.length) {
|
|
this.node.parent.childNodes.forEach(e => {
|
|
e.collapse()
|
|
})
|
|
}
|
|
this.node.expand();
|
|
this.tree.$emit("node-expand", this.node.data, this.node, this);
|
|
}
|
|
},
|
|
handleContextMenu(event) {
|
|
if (
|
|
this.tree._events["node-contextmenu"] &&
|
|
this.tree._events["node-contextmenu"].length > 0
|
|
) {
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
}
|
|
this.tree.$emit(
|
|
"node-contextmenu",
|
|
event,
|
|
this.node.data,
|
|
this.node,
|
|
this
|
|
);
|
|
}
|
|
}
|
|
};
|
|
</script>
|