This commit is contained in:
yanran200730
2022-07-22 14:44:55 +08:00
parent 04beb515d0
commit 0eb642a625
8 changed files with 1540 additions and 4 deletions

View File

@@ -21,8 +21,9 @@
</template>
<script>
import { VueOkrTree } from 'vue-okr-tree'
import 'vue-okr-tree/dist/vue-okr-tree.css'
// import { VueOkrTree } from 'vue-okr-tree'
import OkrTree from './vue-okr-tree/OkrTree'
// import 'vue-okr-tree/dist/vue-okr-tree.css'
export default {
name: 'AiGrid',
@@ -30,7 +31,7 @@
props: ['instance'],
components: {
VueOkrTree
VueOkrTree: OkrTree
},
data () {
@@ -116,7 +117,7 @@
const arr = res.data.filter(v => {
return v.parentGirdId === parentGirdId || !v.parentGirdId
})
this.treeData.map(p => this.addChild(p, arr.map(v => {
this.treeData.map(p => this.addChild(p, res.data.map(v => {
if (v.id === parentGirdId) {
this.defaultExpandedKeys.push(v.id)
}

View File

@@ -0,0 +1,682 @@
<template>
<div class="org-chart-container">
<div
ref="orgChartRoot"
class="org-chart-node-children"
:class="{
vertical: direction === 'vertical',
horizontal: direction === 'horizontal',
'show-collapsable': showCollapsable,
'one-branch': data.length === 1
}"
>
<OkrTreeNode
v-for="child in root.childNodes"
:node="child"
:root="root"
orkstyle
:show-collapsable="showCollapsable"
:label-width="labelWidth"
:label-height="labelHeight"
:renderContent="renderContent"
:nodeBtnContent="nodeBtnContent"
:selected-key="selectedKey"
:default-expand-all="defaultExpandAll"
:node-key="nodeKey"
:show-node-num="showNodeNum"
:key="getNodeKey(child)"
:props="props"
></OkrTreeNode>
</div>
</div>
</template>
<script>
import Vue from "vue";
import OkrTreeNode from "./OkrTreeNode.vue";
import TreeStore from "./model/tree-store.js";
import { getNodeKey } from "./model/util";
export default {
name: "OkrTree",
components: {
OkrTreeNode
},
provide() {
return {
okrEventBus: this.okrEventBus
};
},
props: {
data: {
// 源数据
required: true
},
leftData: {
// 源数据
type: Array
},
// 方向
direction: {
type: String,
default: "vertical"
},
// 子节点是否可折叠
showCollapsable: {
type: Boolean,
default: false
},
// 飞书 OKR 模式
onlyBothTree: {
type: Boolean,
default: false
},
orkstyle: {
type: Boolean,
default: false
},
// 树节点的内容区的渲染 Function
renderContent: Function,
// 展开节点的内容渲染 Function
nodeBtnContent: Function,
// 显示节点数
showNodeNum: Boolean,
// 树节点区域的宽度
labelWidth: [String, Number],
// 树节点区域的高度
labelHeight: [String, Number],
// 树节点的样式
labelClassName: [Function, String],
// 当前选中节点样式
currentLableClassName: [Function, String],
// 用来控制选择节点的字段名
selectedKey: String,
// 是否默认展开所有节点
defaultExpandAll: {
type: Boolean,
default: false
},
// 当前选中的节点
currentNodeKey: [String, Number],
// 每个树节点用来作为唯一标识的属性,整棵树应该是唯一的
nodeKey: String,
defaultExpandedKeys: {
type: Array
},
filterNodeMethod: Function,
props: {
default() {
return {
leftChildren: "leftChildren",
children: "children",
label: "label",
disabled: "disabled"
};
}
},
// 动画
animate: {
type: Boolean,
default: false
},
animateName: {
type: String,
default: "okr-zoom-in-center"
},
animateDuration: {
type: Number,
default: 200
}
},
computed: {
ondeClass() {
return {
findNode: null
};
}
},
data() {
return {
okrEventBus: new Vue(),
store: null,
root: null
};
},
created() {
this.isTree = true;
this.store = new TreeStore({
key: this.nodeKey,
data: Object.freeze(this.data),
leftData: this.leftData,
props: this.props,
defaultExpandedKeys: this.defaultExpandedKeys,
showCollapsable: this.showCollapsable,
currentNodeKey: this.currentNodeKey,
defaultExpandAll: this.defaultExpandAll,
filterNodeMethod: this.filterNodeMethod,
labelClassName: this.labelClassName,
currentLableClassName: this.currentLableClassName,
onlyBothTree: this.onlyBothTree,
direction: this.direction,
animate: this.animate,
animateName: this.animateName
});
this.root = this.store.root;
},
watch: {
data(newVal) {
this.store.setData(newVal);
},
defaultExpandedKeys(newVal) {
this.store.defaultExpandedKeys = newVal;
this.store.setDefaultExpandedKeys(newVal);
}
},
methods: {
filter(value) {
if (!this.filterNodeMethod)
throw new Error("[Tree] filterNodeMethod is required when filter");
this.store.filter(value);
if (this.onlyBothTree) {
this.store.filter(value, "leftChildNodes");
}
},
getNodeKey(node) {
return getNodeKey(this.nodeKey, node.data);
},
// 通过 node 设置某个节点的当前选中状态
setCurrentNode(node) {
if (!this.nodeKey)
throw new Error("[Tree] nodeKey is required in setCurrentNode");
this.store.setUserCurrentNode(node);
},
// 根据 data 或者 key 拿到 Tree 组件中的 node
getNode(data) {
return this.store.getNode(data);
},
// 通过 key 设置某个节点的当前选中状态
setCurrentKey(key) {
if (!this.nodeKey)
throw new Error("[Tree] nodeKey is required in setCurrentKey");
this.store.setCurrentNodeKey(key);
},
remove(data) {
this.store.remove(data);
},
// 获取当前被选中节点的 data
getCurrentNode() {
const currentNode = this.store.getCurrentNode();
return currentNode ? currentNode.data : null;
},
getCurrentKey() {
if (!this.nodeKey)
throw new Error("[Tree] nodeKey is required in getCurrentKey");
const currentNode = this.getCurrentNode();
return currentNode ? currentNode[this.nodeKey] : null;
},
append(data, parentNode) {
this.store.append(data, parentNode);
},
insertBefore(data, refNode) {
this.store.insertBefore(data, refNode);
},
insertAfter(data, refNode) {
this.store.insertAfter(data, refNode);
},
updateKeyChildren(key, data) {
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in updateKeyChild');
this.store.updateChildren(key, data);
}
}
};
</script>
<style>
@import "./model/transition.css";
* {
margin: 0;
padding: 0;
}
.org-chart-container {
display: block;
width: 100%;
text-align: center;
}
.vertical .org-chart-node-children {
position: relative;
padding-top: 20px;
transition: all 0.5s;
}
.vertical .org-chart-node {
float: left;
text-align: center;
list-style-type: none;
position: relative;
padding: 20px 5px 0 5px;
transition: all 0.5s;
}
/*使用 ::before 和 ::after 绘制连接器*/
.vertical .org-chart-node::before,
.vertical .org-chart-node::after {
content: "";
position: absolute;
top: 0;
right: 50%;
width: 50%;
border-top: 1px solid #ccc;
height: 20px;
}
.vertical .org-chart-node::after {
right: auto;
left: 50%;
border-left: 1px solid #ccc;
}
/*我们需要从没有任何兄弟元素的元素中删除左右连接器*/
.vertical.one-branch > .org-chart-node::after,
.vertical.one-branch > .org-chart-node::before {
display: none;
}
/*从单个子节点的顶部移除空格*/
.vertical.one-branch > .org-chart-node {
padding-top: 0;
}
/*从第一个子节点移除左连接器,从最后一个子节点移除右连接器*/
.vertical .org-chart-node:first-child::before,
.vertical .org-chart-node:last-child::after {
border: 0 none;
}
/*将垂直连接器添加回最后的节点*/
.vertical .org-chart-node:last-child::before {
border-right: 1px solid #ccc;
border-radius: 0 5px 0 0;
}
.vertical .org-chart-node:only-child:before {
border-radius: 0 0px 0 0;
margin-right: -1px;
}
.vertical .org-chart-node:first-child::after {
border-radius: 5px 0 0 0;
}
.vertical .org-chart-node.is-leaf {
padding-top: 20px;
padding-bottom: 20px;
}
.vertical .org-chart-node.is-leaf::before {
content: "";
display: block;
height: 20px;
}
.vertical .org-chart-node.is-leaf .org-chart-node-label::after {
display: none;
}
/*从父节点添加向下的连接器了*/
.vertical .org-chart-node-children::before {
content: "";
position: absolute;
top: 0;
left: 50%;
border-left: 1px solid #ccc;
width: 0;
height: 20px;
}
.vertical .org-chart-node-label {
position: relative;
display: inline-block;
}
.vertical .org-chart-node-label .org-chart-node-label-inner {
box-shadow: 0 1px 10px rgba(31, 35, 41, 0.08);
display: inline-block;
padding: 10px;
margin: 0px;
font-size: 16px;
word-break: break-all;
}
.vertical .org-chart-node-label .org-chart-node-label-inner:hover {
box-shadow: 0 1px 14px rgba(31, 35, 41, 0.12);
cursor: pointer;
}
.vertical .org-chart-node-label .org-chart-node-btn {
position: absolute;
top: 100%;
left: 50%;
width: 20px;
height: 20px;
z-index: 10;
margin-left: -11px;
margin-top: 9px;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 50%;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.15);
cursor: pointer;
transition: all 0.35s ease;
}
.vertical .org-chart-node-label .org-chart-node-btn:hover {
transform: scale(1.15);
cursor: pointer;
}
.vertical .org-chart-node-label .org-chart-node-btn::before,
.vertical .org-chart-node-label .org-chart-node-btn::after {
content: "";
position: absolute;
}
.vertical .org-chart-node-label .org-chart-node-btn::before {
top: 50%;
left: 4px;
right: 4px;
height: 0;
border-top: 1px solid #ccc;
}
.vertical .org-chart-node-label .org-chart-node-btn::after {
top: 4px;
left: 50%;
bottom: 4px;
width: 0;
border-left: 1px solid #ccc;
}
.vertical .org-chart-node-label .expanded.org-chart-node-btn::after {
display: none;
}
.vertical .org-chart-node.collapsed .org-chart-node-label {
position: relative;
}
.vertical .org-chart-node.collapsed .org-chart-node-label::after {
content: "";
position: absolute;
top: 100%;
left: 0;
width: 50%;
height: 20px;
border-right: 1px solid #ddd;
}
.horizontal .org-chart-node-children,
.horizontal .org-chart-node-left-children {
position: relative;
padding-left: 20px;
transition: all 0.5s;
}
.horizontal .org-chart-node-left-children {
padding-right: 20px;
}
.horizontal .org-chart-node:not(.is-left-child-node) {
display: flex;
align-items: center;
position: relative;
padding: 0px 5px 0 20px;
transition: all 0.5s;
}
.horizontal .is-left-child-node {
display: flex;
position: relative;
justify-content: flex-end;
align-items: center;
}
.horizontal .is-left-child-node {
padding: 0px 20px 0 5px;
}
/*使用 ::before 和 ::after 绘制连接器*/
.horizontal .org-chart-node:not(.is-left-child-node):before,
.horizontal .org-chart-node:not(.is-left-child-node)::after {
content: "";
position: absolute;
border-left: 1px solid #ccc;
top: 0;
left: 0;
width: 20px;
height: 50%;
}
.horizontal .is-left-child-node:before,
.horizontal .is-left-child-node::after {
content: "";
position: absolute;
border-right: 1px solid #ccc;
top: 0;
right: 0;
width: 20px;
height: 50%;
}
.horizontal .org-chart-node:not(.is-left-child-node):after {
top: 50%;
border-top: 1px solid #ccc;
}
.horizontal .is-left-child-node:after {
top: 50%;
border-top: 1px solid #ccc;
}
/*我们需要从没有任何兄弟元素的元素中删除左右连接器*/
.horizontal.one-branch > .org-chart-node::after,
.horizontal.one-branch > .org-chart-node::before {
display: none;
}
/*从单个子节点的顶部移除空格*/
.horizontal.one-branch > .org-chart-node {
padding-left: 0;
}
/*从第一个子节点移除左连接器,从最后一个子节点移除右连接器*/
.horizontal .org-chart-node:first-child::before,
.horizontal .org-chart-node:last-child::after {
border: 0 none;
}
/*将垂直连接器添加回最后的节点*/
.horizontal .org-chart-node:not(.is-left-child-node):not(.is-not-child):last-child::before {
border-bottom: 1px solid #ccc;
border-radius: 0 0px 0 5px;
}
.horizontal .is-left-child-node:last-child::before {
border-bottom: 1px solid #ccc;
border-radius: 0 0px 5px 0px;
}
.horizontal .org-chart-node:only-child::before {
border-radius: 0 0px 0 0px !important;
border-color: #ccc;
}
.horizontal .org-chart-node:not(.is-left-child-node):first-child::after {
border-radius: 5px 0px 0 0;
}
.horizontal .is-left-child-node:first-child::after {
border-radius: 0 5px 0px 0px;
}
.horizontal .org-chart-node.is-leaf {
position: relative;
padding-left: 20px;
padding-right: 20px;
}
.horizontal .org-chart-node.is-leaf::before {
content: "";
display: block;
}
.horizontal .org-chart-node.is-leaf .org-chart-node-label::after,
.horizontal .is-left-child-node.is-leaf .org-chart-node-label::before {
display: none;
}
.horizontal .is-left-child-node:last-child::after {
/* border-bottom: 1px solid green; */
border-radius: 0 0px 5px 0px;
}
.horizontal .is-left-child-node:only-child::after {
border-radius: 0 0px 0 0px;
}
.horizontal .is-left-child-node.is-leaf {
position: relative;
padding-left: 20px;
padding-right: 20px;
}
.horizontal .is-left-child-node.is-leaf::before {
content: "";
display: block;
}
.horizontal .is-left-child-node .org-chart-node-label::after {
display: none;
}
/*从父节点添加向下的连接器了*/
.horizontal .org-chart-node-children::before,
.horizontal .org-chart-node-left-children::before {
content: "";
position: absolute;
left: 0;
top: 50%;
border-top: 1px solid #ccc;
width: 12px;
height: 10px;
}
.horizontal .org-chart-node-children::before {
width: 20px;
}
.horizontal .org-chart-node-left-children::before {
left: calc(100% - 11px);
}
.horizontal > .only-both-tree-node > .org-chart-node-left-children::before {
display: none;
}
.horizontal .org-chart-node-label {
position: relative;
display: inline-block;
}
.horizontal .org-chart-node-label .org-chart-node-label-inner {
box-shadow: 0 1px 10px rgba(31, 35, 41, 0.08);
display: inline-block;
padding: 10px;
margin: 10px 0;
font-size: 16px;
word-break: break-all;
}
.horizontal .org-chart-node-label .org-chart-node-label-inner:hover {
box-shadow: 0 1px 14px rgba(31, 35, 41, 0.12);
cursor: pointer;
}
.horizontal .org-chart-node-label .org-chart-node-btn {
position: absolute;
left: 100%;
top: 50%;
width: 20px;
height: 20px;
z-index: 10;
margin-top: -11px;
margin-left: 9px;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 50%;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.15);
cursor: pointer;
transition: all 0.35s ease;
}
.horizontal .org-chart-node-label .org-chart-node-btn:hover,
.horizontal .org-chart-node-label .org-chart-node-left-btn:hover {
transform: scale(1.15);
}
.horizontal .org-chart-node-label .org-chart-node-btn::before,
.horizontal .org-chart-node-label .org-chart-node-btn::after,
.horizontal .org-chart-node-label .org-chart-node-left-btn::before,
.horizontal .org-chart-node-label .org-chart-node-left-btn::after {
content: "";
position: absolute;
}
.horizontal .org-chart-node-label .org-chart-node-btn::before,
.horizontal .org-chart-node-label .org-chart-node-left-btn::before {
top: 50%;
left: 4px;
right: 3px;
border-top: 1px solid #ccc;
height: 0;
transform: translateY(-50%);
}
.horizontal .org-chart-node-label .org-chart-node-btn::after,
.horizontal .org-chart-node-label .org-chart-node-left-btn::after {
top: 4px;
left: 50%;
bottom: 4px;
width: 0;
border-left: 1px solid #ccc;
}
.horizontal .org-chart-node-label .expanded.org-chart-node-btn::after,
.horizontal .org-chart-node-label .expanded.org-chart-node-left-btn::after {
display: none;
}
.horizontal .org-chart-node-label .org-chart-node-left-btn {
position: absolute;
right: 100%;
top: 50%;
width: 20px;
height: 20px;
z-index: 10;
margin-top: -11px;
margin-right: 9px;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 50%;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.15);
cursor: pointer;
transition: all 0.35s ease;
}
.horizontal .org-chart-node.collapsed .org-chart-node-label,
.horizontal .is-left-child-node.collapsed .org-chart-node-label {
position: relative;
}
.horizontal .org-chart-node.collapsed .org-chart-node-label::after,
.horizontal .is-left-child-node.collapsed .org-chart-node-label::before {
content: "";
border-bottom: 1px solid #ccc;
position: absolute;
top: 0;
left: 100%;
height: 50%;
width: 10px;
}
.horizontal .org-chart-node .is-root-label.is-not-right-child::after,
.horizontal .org-chart-node .is-root-label.is-not-left-child::before {
display: none !important;
}
/* .horizontal .org-chart-node.collapsed .is-root-label.is-right-child::after,
.horizontal .org-chart-node.collapsed .is-root-label.is-left-child::before {
display: block;
} */
.horizontal .is-left-child-node.collapsed .org-chart-node-label::before {
left: -10px;
}
.horizontal .only-both-tree-node > .org-chart-node-label::before {
content: "";
border-bottom: 1px solid #ccc;
position: absolute;
top: 0;
right: 100%;
height: 50%;
width: 20px;
}
.org-chart-node-children .org-chart-node-btn-text {
position: absolute;
top:0;
left: 0;
right: 0;
bottom: 0;
background: #fff;
border-radius: 50%;
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
color: #909090;
z-index: 2;
}
</style>

View File

@@ -0,0 +1,381 @@
<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 {
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>

View File

@@ -0,0 +1,14 @@
export default function(target) {
for (let i = 1, j = arguments.length; i < j; i++) {
let source = arguments[i] || {};
for (let prop in source) {
if (source.hasOwnProperty(prop)) {
let value = source[prop];
if (value !== undefined) {
target[prop] = value;
}
}
}
}
return target;
}

View File

@@ -0,0 +1,254 @@
import { markNodeData, NODE_KEY } from './util';
import objectAssign from './merge';
const getPropertyFromData = function (node, prop) {
const props = node.store.props;
const data = node.data || {};
const config = props[prop];
if (typeof config === 'function') {
return config(data, node);
} else if (typeof config === 'string') {
return data[config];
} else if (typeof config === 'undefined') {
const dataProp = data[prop];
return dataProp === undefined ? '' : dataProp;
}
};
let nodeIdSeed = 0;
export default class Node {
constructor(options, isLeftChild = false) {
this.isLeftChild = isLeftChild;
this.id = nodeIdSeed++;
this.data = null;
this.expanded = false;
this.leftExpanded = false;
this.isCurrent = false;
this.visible = true;
this.parent = null;
for (let name in options) {
if (options.hasOwnProperty(name)) {
this[name] = options[name];
}
}
this.level = 0;
this.childNodes = [];
this.leftChildNodes = [];
if (this.parent) {
this.level = this.parent.level + 1;
}
const store = this.store;
if (!store) {
throw new Error('[Node]store is required!');
}
store.registerNode(this);
if (this.data) {
this.setData(this.data, isLeftChild);
if (store.defaultExpandAll || !store.showCollapsable) {
this.expanded = true;
this.leftExpanded = true;
}
}
if (!Array.isArray(this.data)) {
markNodeData(this, this.data);
}
if (!this.data) return;
const defaultExpandedKeys = store.defaultExpandedKeys;
const key = store.key;
if (
key &&
defaultExpandedKeys &&
defaultExpandedKeys.indexOf(this.key) !== -1
) {
this.expand(null, true);
}
if (
key &&
store.currentNodeKey !== undefined &&
this.key === store.currentNodeKey
) {
store.currentNode = this;
store.currentNode.isCurrent = true;
}
this.updateLeafState();
}
setData(data, isLeftChild) {
if (!Array.isArray(data)) {
markNodeData(this, data);
}
const store = this.store;
this.data = data;
this.childNodes = [];
let children;
if (this.level === 0 && this.data instanceof Array) {
children = this.data;
} else {
children = getPropertyFromData(this, 'children') || [];
}
for (let i = 0, j = children.length; i < j; i++) {
this.insertChild({ data: children[i] }, null, null, isLeftChild);
}
}
get key() {
const nodeKey = this.store.key;
if (this.data) return this.data[nodeKey];
return null;
}
get label() {
return getPropertyFromData(this, 'label');
}
// 是否是 OKR 飞书模式
hasLeftChild() {
const store = this.store;
return store.onlyBothTree && store.direction === 'horizontal';
}
insertChild(child, index, batch, isLeftChild) {
if (!child) throw new Error('insertChild error: child is required.');
if (!(child instanceof Node)) {
if (!batch) {
const children = this.getChildren(true);
if (children.indexOf(child.data) === -1) {
if (index === undefined || index === null || index < 0) {
children.push(child.data);
} else {
children.splice(index, 0, child.data);
}
}
}
objectAssign(child, {
parent: this,
store: this.store,
});
child = new Node(child, isLeftChild);
}
child.level = this.level + 1;
if (index === undefined || index === null || index < 0) {
this.childNodes.push(child);
} else {
this.childNodes.splice(index, 0, child);
}
this.updateLeafState();
}
getChildren(forceInit = false) {
// this is data
if (this.level === 0) return this.data;
const data = this.data;
if (!data) return null;
const props = this.store.props;
let children = 'children';
if (props) {
children = props.children || 'children';
}
if (data[children] === undefined) {
data[children] = null;
}
if (forceInit && !data[children]) {
data[children] = [];
}
return data[children];
}
updateLeafState() {
if (
this.store.lazy === true &&
this.loaded !== true &&
typeof this.isLeafByUser !== 'undefined'
) {
this.isLeaf = this.isLeafByUser;
return;
}
const childNodes = this.childNodes;
if (
!this.store.lazy ||
(this.store.lazy === true && this.loaded === true)
) {
this.isLeaf = !childNodes || childNodes.length === 0;
return;
}
this.isLeaf = false;
}
updateLeftLeafState() {
if (
this.store.lazy === true &&
this.loaded !== true &&
typeof this.isLeafByUser !== 'undefined'
) {
this.isLeaf = this.isLeafByUser;
return;
}
const childNodes = this.leftChildNodes;
if (
!this.store.lazy ||
(this.store.lazy === true && this.loaded === true)
) {
this.isLeaf = !childNodes || childNodes.length === 0;
return;
}
this.isLeaf = false;
}
// 节点的收起
collapse() {
this.expanded = false;
}
// 节点的展开
expand(callback, expandParent) {
const done = () => {
if (expandParent) {
let parent = this.parent;
while (parent.level > 0) {
parent.isLeftChild
? (parent.leftExpanded = true)
: (parent.expanded = true);
parent = parent.parent;
}
}
this.isLeftChild ? (this.leftExpanded = true) : (this.expanded = true);
if (callback) callback();
};
done();
}
removeChild(child) {
const children = this.getChildren() || [];
const dataIndex = children.indexOf(child.data);
if (dataIndex > -1) {
children.splice(dataIndex, 1);
}
const index = this.childNodes.indexOf(child);
if (index > -1) {
this.store && this.store.deregisterNode(child);
child.parent = null;
this.childNodes.splice(index, 1);
}
this.updateLeafState();
}
insertBefore(child, ref) {
let index;
if (ref) {
index = this.childNodes.indexOf(ref);
}
this.insertChild(child, index);
}
insertAfter(child, ref) {
let index;
if (ref) {
index = this.childNodes.indexOf(ref);
if (index !== -1) index += 1;
}
this.insertChild(child, index);
}
}

View File

@@ -0,0 +1 @@
.okr-fade-in-enter,.okr-fade-in-leave-active,.okr-fade-in-linear-enter,.okr-fade-in-linear-leave,.okr-fade-in-linear-leave-active,.fade-in-linear-enter,.fade-in-linear-leave,.fade-in-linear-leave-active{opacity:0}.fade-in-linear-enter-active,.fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.okr-fade-in-linear-enter-active,.okr-fade-in-linear-leave-active{-webkit-transition:opacity .2s linear;transition:opacity .2s linear}.okr-fade-in-enter-active,.okr-fade-in-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.okr-zoom-in-center-enter-active,.okr-zoom-in-center-leave-active{-webkit-transition:all .3s cubic-bezier(.55,0,.1,1);transition:all .3s cubic-bezier(.55,0,.1,1)}.okr-zoom-in-center-enter,.okr-zoom-in-center-leave-active{opacity:0;-webkit-transform:scaleX(0);transform:scaleX(0)}.okr-zoom-in-top-enter-active,.okr-zoom-in-top-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);-webkit-transform-origin:center top;transform-origin:center top}.okr-zoom-in-top-enter,.okr-zoom-in-top-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.okr-zoom-in-bottom-enter-active,.okr-zoom-in-bottom-leave-active{opacity:1;-webkit-transform:scaleY(1);transform:scaleY(1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);-webkit-transform-origin:center bottom;transform-origin:center bottom}.okr-zoom-in-bottom-enter,.okr-zoom-in-bottom-leave-active{opacity:0;-webkit-transform:scaleY(0);transform:scaleY(0)}.okr-zoom-in-left-enter-active,.okr-zoom-in-left-leave-active{opacity:1;-webkit-transform:scale(1,1);transform:scale(1,1);-webkit-transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1);transition:transform .3s cubic-bezier(.23,1,.32,1),opacity .3s cubic-bezier(.23,1,.32,1),-webkit-transform .3s cubic-bezier(.23,1,.32,1);-webkit-transform-origin:top left;transform-origin:top left}.okr-zoom-in-left-enter,.okr-zoom-in-left-leave-active{opacity:0;-webkit-transform:scale(.45,.45);transform:scale(.45,.45)}.collapse-transition{-webkit-transition:.3s height ease-in-out,.3s padding-top ease-in-out,.3s padding-bottom ease-in-out;transition:.3s height ease-in-out,.3s padding-top ease-in-out,.3s padding-bottom ease-in-out}.horizontal-collapse-transition{-webkit-transition:.3s width ease-in-out,.3s padding-left ease-in-out,.3s padding-right ease-in-out;transition:.3s width ease-in-out,.3s padding-left ease-in-out,.3s padding-right ease-in-out}.okr-list-enter-active,.okr-list-leave-active{-webkit-transition:all 1s;transition:all 1s}.okr-list-enter,.okr-list-leave-active{opacity:0;-webkit-transform:translateY(-30px);transform:translateY(-30px)}.okr-opacity-transition{-webkit-transition:opacity .3s cubic-bezier(.55,0,.1,1);transition:opacity .3s cubic-bezier(.55,0,.1,1)}

View File

@@ -0,0 +1,176 @@
import Node from "./node";
import { getNodeKey } from "./util";
export default class TreeStore {
constructor(options) {
this.currentNode = null;
this.currentNodeKey = null;
for (let option in options) {
if (options.hasOwnProperty(option)) {
this[option] = options[option];
}
}
this.nodesMap = {};
this.root = new Node(
{
data: this.data,
store: this
},
false
);
if (this.root.store.onlyBothTree) {
if (!this.leftData)
throw new Error("[Tree] leftData is required in onlyBothTree");
if (this.leftData) {
this.isLeftChilds = new Node(
{
data: this.leftData,
store: this
},
true
);
if (this.isLeftChilds) {
this.root.childNodes[0].leftChildNodes = this.isLeftChilds.childNodes[0].childNodes;
this.root.childNodes[0].leftExpanded = this.isLeftChilds.childNodes[0].leftExpanded;
}
}
}
}
filter(value, childName = "childNodes") {
this.filterRight(value, childName);
}
// 过滤默认节点
filterRight(value, childName) {
const filterNodeMethod = this.filterNodeMethod;
const traverse = function(node, childName) {
let childNodes;
if (node.root) {
childNodes = node.root.childNodes[0][childName];
} else {
childNodes = node.childNodes;
}
childNodes.forEach(child => {
child.visible = filterNodeMethod.call(child, value, child.data, child);
traverse(child, childName);
});
if (!node.visible && childNodes.length) {
let allHidden = true;
allHidden = !childNodes.some(child => child.visible);
if (node.root) {
node.root.visible = allHidden === false;
} else {
node.visible = allHidden === false;
}
}
if (!value) return;
if (node.visible) node.expand();
};
traverse(this, childName);
}
registerNode(node) {
const key = this.key;
if (!key || !node || !node.data) return;
const nodeKey = node.key;
if (nodeKey !== undefined) this.nodesMap[node.key] = node;
}
deregisterNode(node) {
const key = this.key;
if (!key || !node || !node.data) return;
node.childNodes.forEach(child => {
this.deregisterNode(child);
});
delete this.nodesMap[node.key];
}
setData(newVal) {
const instanceChanged = newVal !== this.root.data;
if (instanceChanged) {
this.root.setData(newVal);
} else {
this.root.updateChildren();
}
}
updateChildren(key, data) {
const node = this.nodesMap[key];
if (!node) return;
const childNodes = node.childNodes;
for (let i = childNodes.length - 1; i >= 0; i--) {
const child = childNodes[i];
this.remove(child.data);
}
for (let i = 0, j = data.length; i < j; i++) {
const child = data[i];
this.append(child, node.data);
}
}
getNode(data) {
if (data instanceof Node) return data;
const key = typeof data !== "object" ? data : getNodeKey(this.key, data);
return this.nodesMap[key] || null;
}
setDefaultExpandedKeys(keys) {
keys = keys || [];
this.defaultExpandedKeys = keys;
keys.forEach(key => {
const node = this.getNode(key);
if (node) node.expand(null, true);
});
}
setCurrentNode(currentNode) {
const prevCurrentNode = this.currentNode;
if (prevCurrentNode) {
prevCurrentNode.isCurrent = false;
}
this.currentNode = currentNode;
this.currentNode.isCurrent = true;
}
setUserCurrentNode(node) {
const key = node.key;
const currNode = this.nodesMap[key];
this.setCurrentNode(currNode);
}
setCurrentNodeKey(key) {
if (key === null || key === undefined) {
this.currentNode && (this.currentNode.isCurrent = false);
this.currentNode = null;
return;
}
const node = this.getNode(key);
if (node) {
this.setCurrentNode(node);
}
}
getCurrentNode() {
return this.currentNode;
}
remove(data) {
const node = this.getNode(data);
if (node && node.parent) {
if (node === this.currentNode) {
this.currentNode = null;
}
node.parent.removeChild(node);
}
}
append(data, parentData) {
const parentNode = parentData ? this.getNode(parentData) : this.root;
if (parentNode) {
parentNode.insertChild({ data });
}
}
insertBefore(data, refData) {
const refNode = this.getNode(refData);
refNode.parent.insertBefore({ data }, refNode);
}
insertAfter(data, refData) {
const refNode = this.getNode(refData);
refNode.parent.insertAfter({ data }, refNode);
}
}

View File

@@ -0,0 +1,27 @@
export const NODE_KEY = "$treeNodeId";
export const markNodeData = function(node, data) {
if (!data || data[NODE_KEY]) return;
Object.defineProperty(data, NODE_KEY, {
value: node.id,
enumerable: false,
configurable: false,
writable: false
});
};
export const getNodeKey = function(key, data) {
if (!key) return data[NODE_KEY];
return data[key];
};
export const findNearestComponent = (element, componentName) => {
let target = element;
while (target && target.tagName !== "BODY") {
if (target.__vue__ && target.__vue__.$options.name === componentName) {
return target.__vue__;
}
target = target.parentNode;
}
return null;
};