| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- <template>
- <div id="tree-container" :style="`height:${HEIGHT}px`">
- <div id="node-container" :style="`height:${HEIGHT}px; width:${WIDTH}px`">
- <template v-for="(col, index) in treeNodeColList" :key="index">
- <TreeNode
- v-for="item in col"
- v-bind="item"
- :key="item.id"
- :position-x="index"
- :node-style="NODE_STYLE"
- @addChild="addChild"
- @deleteNode="deleteNode"
- @nodeClick="nodeClick"
- :ref="`treenode-${item.id}`"
- />
- </template>
- <!-- <TreeNode-->
- <!-- v-for="requirement in data"-->
- <!-- :key="requirement.id"-->
- <!-- v-bind="requirement"-->
- <!-- :ref="`treenode-${requirement.id}`"-->
- <!-- @addChild="addChild"-->
- <!-- @deleteNode="deleteNode"-->
- <!-- @nodeClick="nodeClick"-->
- <!-- />-->
- </div>
- <canvas id="line-drawer" :height="HEIGHT" :width="WIDTH"></canvas>
- </div>
- </template>
- <script>
- import TreeNode from './TreeNode.vue';
- // 容器宽高
- const HEIGHT = 600;
- const WIDTH = 1100;
- const MARGIN_TOP = 80;
- const MARGIN_LEFT = 100;
- const TREE_MAX_HEIGHT = 4;
- const NODE_STYLE = {
- width: 250,
- height: 100,
- padding: 25,
- };
- export default {
- name: 'Tree',
- components: { TreeNode },
- props: {
- data: Array,
- },
- computed: {
- /**
- * @returns {*[][]}
- */
- treeNodeColList() {
- // 将点存入map便于调用
- const nodeMap = new Map();
- this.data.forEach((item) => {
- nodeMap.set(item.id, item);
- });
- // 将array转为树状结构;顺便拆成col
- let rootNode;
- const colList = [];
- for (let i = 0; i < TREE_MAX_HEIGHT; i += 1) {
- colList.push([]);
- }
- this.data.forEach((item) => {
- if (item.deep > 0 && item.deep <= TREE_MAX_HEIGHT) {
- colList[item.deep - 1].push(item);
- } else {
- console.warn('需求树数据有误,树高度错误');
- }
- if (item.parent) {
- const tempParent = nodeMap.get(item.parent);
- if (!tempParent.childs) {
- tempParent.childs = [];
- }
- tempParent.childs.push(item);
- } else {
- rootNode = item;
- }
- });
- // 计算一个节点包含他的后代所需要的空间
- const calculateNodeContentHeight = (node) => {
- if (!node.childs) {
- node.contentHeight = 1;
- return 1;
- }
- node.contentHeight = node.childs.reduce((total, current) => total + calculateNodeContentHeight(current), 0);
- return node.contentHeight;
- };
- calculateNodeContentHeight(rootNode);
- // 计算节点基准Y轴位置
- rootNode.positionY = 0;
- for (let i = 1; i < TREE_MAX_HEIGHT; i += 1) {
- let sum = 0;
- colList[i].forEach((item) => {
- item.positionY = nodeMap.get(item.parent).positionY + sum;
- sum += item.contentHeight;
- });
- }
- // 计算节点Y轴偏移位置
- for (let i = TREE_MAX_HEIGHT - 2; i >= 0; i -= 1) {
- colList[i].forEach((item) => {
- if (item.childs) {
- const { length } = item.childs;
- item.positionY = item.childs.reduce((total, current) => total + current.positionY / length, 0);
- }
- });
- }
- // 返回col
- this.drawLine();
- return colList;
- },
- },
- data() {
- return {
- HEIGHT,
- WIDTH,
- TREE_MAX_HEIGHT,
- NODE_STYLE,
- parent2children: {},
- depth2node: {},
- layout: {},
- ctx: null,
- };
- },
- mounted() {
- this.initCanvasCtx();
- // this.regenerateTree();
- },
- methods: {
- initCanvasCtx() {
- this.ctx = document.getElementById('line-drawer').getContext('2d');
- },
- drawLine() {
- setTimeout(() => {
- this.ctx.clearRect(0, 0, WIDTH, HEIGHT);
- this.ctx.lineWidth = 1;
- this.ctx.strokeStyle = '#bbbbbb';
- this.data.forEach((item) => {
- if (!item.childs) return;
- const parentCard = this.$refs[`treenode-${item.id}`].getTreeNodeSizeAndPosition();
- console.warn(parentCard);
- const start = {
- x: parentCard.left + parentCard.width,
- y: parentCard.top + parentCard.height / 2,
- };
- item.childs.forEach((child) => {
- const childCard = this.$refs[`treenode-${child.id}`].getTreeNodeSizeAndPosition();
- const end = {
- x: childCard.left,
- y: childCard.top + childCard.height / 2,
- };
- this.ctx.beginPath();
- this.ctx.moveTo(start.x, start.y);
- this.ctx.lineTo(end.x, end.y);
- this.ctx.stroke();
- });
- });
- });
- },
- addChild(id) {
- this.$emit('change', {
- type: 'addChild',
- id,
- });
- },
- deleteNode(id) {
- this.$emit('change', {
- type: 'deleteNode',
- id,
- });
- },
- nodeClick(id) {
- this.$emit('node-click', id);
- },
- },
- };
- </script>
- <style lang="scss">
- #tree-container {
- position: relative;
- display: flex;
- justify-content:center;
- }
- #node-container {
- position:absolute;
- z-index: 11;
- }
- #line-drawer {
- position: absolute;
- }
- .jsmind-inner {
- position:relative;
- overflow:auto;
- width:100%;
- height:100%;
- user-select:none;
- }
- canvas {
- position:absolute;
- z-index:1;
- }
- jmnodes {
- position:absolute;
- z-index:2;
- background-color:rgba(0,0,0,0);
- }
- jmnode {
- position:absolute;
- cursor:default;
- max-width:400px;
- white-space:nowrap;
- overflow:hidden;
- text-overflow:ellipsis;
- padding:10px;
- background-color:#fff;
- color:#333;
- border-radius:1px;
- border: 1px solid #333;
- font:16px/1.125 Verdana,Arial,Helvetica,sans-serif;
- &:hover {
- box-shadow:2px 2px 4px #333;
- background-color:#ebebeb;
- color:#333;
- }
- &.selected {
- background-color:#11f;
- color:#fff;
- box-shadow:2px 2px 8px #000;
- }
- &.root {
- font-size:24px;
- }
- }
- jmexpander {
- position:absolute;
- width:11px;
- height:11px;
- display:block;
- overflow:hidden;
- line-height:12px;
- font-size:12px;
- text-align:center;
- border-radius:6px;
- border-width:1px;
- border-style:solid;
- cursor:pointer;
- border-color:gray;
- &:hover {
- border-color:#000;
- }
- }
- @media screen and (max-device-width: 1024px) {
- jmnode{padding:5px;border-radius:3px;font-size:14px;}
- jmnode.root{font-size:21px;}
- }
- </style>
|