需求背景

需要实现可编辑的家谱树,作为入口,增加删除家庭人员,预览和查看家庭人员信息

解决效果

效果链接

解决方案

代码链接

index.vue

// index.vue
<template><div><div class="tree-con"><h2>递归家谱树</h2><TreeChart :json="treedata" @click-node="clickNode"/></div></div>
</template><script lang="ts" setup>
import TreeChart from "./TreeChart.vue";
import data from './data';
import {reactive, toRefs} from "vue";const state = reactive({treedata: {} as any//家谱树数据
})
const {treedata} = toRefs(state)
const clickNode = (node: any) => { // 节点点击事件console.log(node, "==> 节点点击事件")
}
// 异步数据
window.setTimeout(()=>{state.treedata = data;
},1000)
</script>

data.ts

// data.ts
interface Data{id: number;name: string;  // 姓名headPortrait: string;  // 头像urlcallName: string;  // 称呼mate: any[];  // 配偶children: any[]; // 后代[propName:string]:any; ...
}const genealogTreeData:Data ={"id": 1,"name": "*某某*","headPortrait": "https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLxWhtkFhVKpfXib0BibMaIzeOAVCGVScnR5ibsibdENiaibjvnfy7AxeSSCTbn9IBvqMe1iaJ6BWTxIjZtg/132","callName": "父亲","mate": [{"id": 2,"name": "*某某*","headPortrait": "https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLxWhtkFhVKpfXib0BibMaIzeOAVCGVScnR5ibsibdENiaibjvnfy7AxeSSCTbn9IBvqMe1iaJ6BWTxIjZtg/132","callName": "母亲","mate": []}],"children": [{"id": 3,"name": "*某某*","headPortrait": "https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLxWhtkFhVKpfXib0BibMaIzeOAVCGVScnR5ibsibdENiaibjvnfy7AxeSSCTbn9IBvqMe1iaJ6BWTxIjZtg/132","callName": "本人","mate": []},{"id": 4,"name": "*某某*","headPortrait": "https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLxWhtkFhVKpfXib0BibMaIzeOAVCGVScnR5ibsibdENiaibjvnfy7AxeSSCTbn9IBvqMe1iaJ6BWTxIjZtg/132","callName": "兄弟","children": [{"id": 5,"name": "*某某*","headPortrait": "https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLxWhtkFhVKpfXib0BibMaIzeOAVCGVScnR5ibsibdENiaibjvnfy7AxeSSCTbn9IBvqMe1iaJ6BWTxIjZtg/132","callName": "儿子","children": [],"mate": []},{"id": 6,"name": "*某某*","headPortrait": "https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLxWhtkFhVKpfXib0BibMaIzeOAVCGVScnR5ibsibdENiaibjvnfy7AxeSSCTbn9IBvqMe1iaJ6BWTxIjZtg/132","callName": "儿子","children": [],"mate": []}],"mate": []},{"id": 7,"name": "*某某*","headPortrait": "https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLxWhtkFhVKpfXib0BibMaIzeOAVCGVScnR5ibsibdENiaibjvnfy7AxeSSCTbn9IBvqMe1iaJ6BWTxIjZtg/132","callName": "妹妹","children": [],"mate": [{"id":8,"name": "*某某*","headPortrait": "https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLxWhtkFhVKpfXib0BibMaIzeOAVCGVScnR5ibsibdENiaibjvnfy7AxeSSCTbn9IBvqMe1iaJ6BWTxIjZtg/132","callName": "妹夫","children": [],"mate": []}]}],}export default genealogTreeData

TreeChart.vue

<!--/*** @author: liuk* @date: 2022/12/28* @describe: 递归族谱树*/-->
<template><table v-if="treeData.id"><tr><td :colspan="Array.isArray(treeData.children) ? treeData.children.length * 2 : 1":class="{parentLevel: Array.isArray(treeData.children) && treeData.children.length, extend: Array.isArray(treeData.children) && treeData.children.length && treeData.extend}"style="word-wrap:break-word;"><div :class="{node: true, hasMate: treeData.mate?.length}"><popover><div class="person":class="Array.isArray(treeData.class) ? treeData.class : []"@click="$emit('click-node', treeData)"><div class="avat"><img :src="treeData.headPortrait" alt="头像"/></div><div class="name">{{ treeData.name }}</div><div class="relation">{{ treeData.callName || '昵称' }}</div></div></Popover><template v-if="Array.isArray(treeData.mate) && treeData.mate.length"><popover><div class="person" v-for="(mate, mateIndex) in treeData.mate" :key="treeData.name+mateIndex":class="Array.isArray(mate.class) ? mate.class : []"@click="$emit('click-node', mate)"><div class="avat"><img :src="mate.headPortrait" alt="头像"/></div><div class="name">{{ mate.name }}</div><div class="relation">{{ mate.callName || '昵称' }}</div></div></popover></template></div><div class="extend_handle" v-if="Array.isArray(treeData.children) && treeData.children.length"@click="toggleExtend(treeData)"></div></td></tr><tr v-if="Array.isArray(treeData.children) && treeData.children.length && treeData.extend"><td v-for="(children, index) in treeData.children" :key="index" colspan="2" class="childLevel"><TreeChart :json="children" @click-node="$emit('click-node', $event)"/></td></tr></table>
</template><script lang="ts" setup>
import {ref, watch} from "vue";
import Popover from "./Popover.vue"// Prop
const props = defineProps<{json: any // 族谱书数据
}>();// Emit
const emit = defineEmits<{(e: 'click-node', value: any): void
}>()const treeData= ref({});
const toggleExtend = (node: any) => { // 折叠功能node.extend = !node.extend;
}
watch(() => props.json,(json) => {let extendKey = function (jsonData: any) {jsonData.extend = (jsonData.extend === void 0 ? true : !!jsonData.extend); // viod 等价于  undefinedif (Array.isArray(jsonData.children)) {jsonData.children.forEach((c: any) => {extendKey(c)})}return jsonData;}if (json) {treeData.value = extendKey(json);}},{immediate: true}
)
</script><style lang="scss" scoped>
table {margin: auto;border-collapse: separate !important;border-spacing: 0 !important;user-select: none;td {position: relative;vertical-align: top;padding: 0 0 50px 0;text-align: center;&.extend {&::after {content: "";position: absolute;left: calc(50% - 1px);bottom: 15px;height: 15px;border-left: 2px solid #ccc;}.extend_handle:before {transform: rotate(-45deg);}}.node {position: relative;display: inline-block;margin: 0 1em;box-sizing: border-box;text-align: center;&.hasMate {width: 200px;&::after {content: "";position: absolute;left: 2em;right: 2em;top: 2em;border-top: 2px solid #ccc;z-index: 1;}}.person {position: relative;display: inline-block;z-index: 2;width: 6em;overflow: hidden;.avat {display: block;width: 4em;height: 4em;margin: auto;overflow: hidden;background: #fff;border: 1px solid #ccc;box-sizing: border-box;img {width: 100%;height: 100%;}}.name {height: 2em;line-height: 2em;overflow: hidden;width: 100%;}}}.extend_handle {position: absolute;left: calc(50% - 15px);bottom: 30px;width: 10px;height: 10px;padding: 10px;cursor: pointer;&::before {content: "";display: block;width: 100%;height: 100%;box-sizing: border-box;border: 4px solid;border-color: #ccc #ccc transparent transparent;transform: rotate(135deg);transform-origin: 50% 50% 0;transition: transform ease 300ms;}&:hover::before {border-color: #333 #333 transparent transparent;}}}.childLevel {&::before {content: "";position: absolute;left: calc(50% - 1px);bottom: 100%;height: 15px;border-left: 2px solid #ccc;}&::after {content: "";position: absolute;left: 0;right: 0;top: -15px;border-top: 2px solid #ccc;}&:first-child {&::before {display: none}&::after {left: calc(50% - 1px);height: 15px;border: 2px solid;border-color: #ccc transparent transparent #ccc;border-radius: 6px 0 0 0;}}&:last-child {&::before {display: none}&::after {right: calc(50% - 1px);height: 15px;border: 2px solid;border-color: #ccc #ccc transparent transparent;border-radius: 0 6px 0 0;}}}
}
/*菜单栏*/
:deep(.el-popover.el-popper) {min-width: 100px !important;padding: 0 !important;.el-menu--collapse {width: 100px;}
}
</style>

Popover.vue

<!--/*** @author: liuk* @date: 2022/12/28* @describe: 菜单选项弹出框
*/-->
<template><el-popover placement="right" :width="100" trigger="click" :teleported="false" :show-arrow="false"><template #reference><slot></slot></template><el-menuclass="el-menu-demo"mode="vertical":collapse="true"@select="handleSelect"style="width:100px;min-width: 100px"><el-menu-item index="preview">查看详情</el-menu-item><el-menu-item index="edit">编辑信息</el-menu-item><el-popover placement="right" :offset="3" :width="100" trigger="hover" :teleported="false":show-arrow="false"><template #reference><el-menu-item>添加成员 &gt;</el-menu-item></template><el-menu-item v-for="item of popoverList" :key="item.value" :index="String(item.value)">{{item.name}}</el-menu-item></el-popover><el-menu-item index="del" >删除</el-menu-item></el-menu></el-popover>
</template><script lang="ts" setup>
const popoverList = [{value: 1, name: '配偶'},{value: 2, name: '父亲'},{value: 3, name: '母亲'},{value: 4, name: '姐姐'},{value: 5, name: '妹妹'},{value: 6, name: '兄长'},{value: 7, name: '弟弟'},{value: 8, name: '儿子'},{value: 9, name: '女儿'},
]
const handleSelect = (val: string) => {console.log(val, "=> 菜单点击事件",typeof val)
}
</script>

相关逻辑问题

1.中间节点只有一个,不能删除
2.只能有一个配偶,不能添加(前端)
3.添加父亲/母亲时,如果已存在父亲/母亲则不能添加
4.添加兄弟姐妹,如果没有父节点,则不能添加
5.添加人时,按生日升序

vue3+ts实现 家谱树相关推荐

  1. vue自定义组件递归实现树状_一道价值25k的腾讯递归组件面试题(Vue3 + TS 实现)...

    前言 小伙伴们好久不见,最近刚入职新公司,需求排的很满,平常是实在没时间写文章了,更新频率会变得比较慢. 周末在家闲着无聊,突然小弟过来紧急求助,说是面试腾讯的时候,对方给了个 Vue 的递归菜单要求 ...

  2. vue3+ts 引入ztree插件

    vue3+ts+ztree 新公司 要用新技术 vue3+ts 一切从头学 最近要引一个树的插件 ztree 所以把遇到的问题 都记下来 一.ztree 找了无数个tree的插件,最后被ztree征服 ...

  3. php递归面包屑,php可应用于面包屑导航的递归寻找家谱树实现方法

    本文实例讲述了php可应用于面包屑导航的递归寻找家谱树实现方法.分享给大家供大家参考.具体实现方法如下:<?phpecho " ";$area = array(array(& ...

  4. 组件库实战 | 用vue3+ts实现全局Header和列表数据渲染ColumnList

    用vue3+ts实现全局Header和列表数据渲染ColumnList

  5. Php数组面包屑导航,php可应用于面包屑导航的迭代寻找家谱树实现方法

    php是通过定义类来实现迭代器接口来构造迭代器,通过yield构造迭代器可以提高性能并节省系统开销,下面就跟着爱站技术频道小编的步伐来学习php可应用于面包屑导航的迭代寻找家谱树实现方法吧. 具体实现 ...

  6. java家谱树_青锋家谱系统-基于springboot+orgtree的青锋家谱树管理系统

    gen 青锋家谱系统:基于springboot架构的家谱项目系统 项目介绍 基于springboot.orgtree的家谱树管理系统,将纸质版的家谱进行电子化.信息化,建立家族的家谱血脉联系. [青锋 ...

  7. 2、Gantt 入门 (vue3 + ts)

    首先把 gantt 官网下载的相关文件放入 resource 文件中. 下载地址:https://dhtmlx.com/docs/products/dhtmlxGantt/download.shtml ...

  8. vue3+ts+vite后台管理模板

    vue3+ts+vite后台管理模板 支持前后端控制权限,使用uniapp+vue3+ts+elementplus+vite开发,码云地址:https://gitee.com/yongqiang062 ...

  9. 家谱树 (并查集拓扑排序)

    目录 拓扑排序 Kahn. 最后附上Kahn的代码: 链式前向星做法: 其次是我用Kahn做的家谱树的代码(矩阵): 其次是我用Kahn做的家谱树的代码(链式前向星): [题目描述] 有个人的家族很大 ...

最新文章

  1. mysql出现1499错误_连接MySQL时出现1449与1045异常解决办法
  2. python turtle循环图案-有趣的Python turtle绘图
  3. iOSUI视图面试及原理总结
  4. 【Greenplum代码】记录一次不了解GP数据库编号函数使用方法导致的问题(1次疏忽 + 1次不必要 = 2次弯路)
  5. 《迷宫》之站立会议—5.15
  6. [html] 你有使用过meter标签吗?说说它的用途有哪些?
  7. C#灰度图转伪彩色图
  8. 加载geojson面数据_地理数据可视化
  9. Spring3.1.1+Jersey2.2+ehcache实现WebApp与服务器接口交互获取令牌及校验过程
  10. centos6.8安装telnet
  11. php和python-现在自学php和python那个合适?
  12. 安装matlab7.0步骤,Matlab 7.0 安装指导
  13. 人生苦短_人生苦短,懂事太晚!
  14. SvnAnt authentication cancelled 的解决
  15. 云游昆大丽(八)——木府流芳
  16. discuz模板文件列表
  17. 微信小程序周报(第二期)
  18. 分享【脑机接口 + 人工智能】的学习之路
  19. [bzoj1787][Ahoi2008]Meet 紧急集合 倍增LCA
  20. 事件分发机制Android,阿里P7级别面试经验总结,实战篇

热门文章

  1. ps怎样给图片加logo
  2. 通给给定旋转轴向量v,旋转角度ang,计算出旋转矩阵
  3. SAP中采购非评估收货应用分析实例
  4. 字节主管工程师年薪中位数 56.4 万美元,排世界第 7,2022 全球程序员收入报告出炉!...
  5. 数据中心技术最新发展趋势
  6. 使用的tk集成mybatis,报No MyBatis mapper was found in的警告解决方案
  7. vue项目常用依赖安装②——element-ui
  8. liunx关闭端口命令
  9. 2021-11-29 轨迹规划五次多项式
  10. 在一棵IPv4地址树中彻底理解IP路由表的各种查找过程