实现动态家谱图可以采用Vue族谱架构,以下是具体实现代码:
数据库设计
使用MySQL数据库,设计以下表结构:
- member表:成员信息
字段名 | 类型 | 说明 |
---|---|---|
id | int | 成员id |
name | varchar(50) | 成员姓名 |
gender | tinyint | 成员性别(0:女,1:男) |
birth | date | 成员出生日期 |
death | date | 成员去世日期 |
father_id | int | 父亲id |
mother_id | int | 母亲id |
- family_tree表:家庭成员关系
字段名 | 类型 | 说明 |
---|---|---|
id | int | 关系id |
member_id | int | 成员id |
parent_id | int | 父亲/母亲id |
type | tinyint | 关系类型(0:父亲,1:母亲) |
前端实现
使用Vue.js框架和Ant Design UI组件库进行前端开发。具体实现步骤如下:
(1)创建Vue项目
在命令行中运行以下指令:
vue create vue-family-tree
(2)引入Ant Design
在命令行中运行以下指令:
npm install ant-design-vue --save
在main.js中引入Ant Design:
import Vue from 'vue';
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';
Vue.use(Antd);
(3)创建成员信息组件MemberInfo.vue
在src/components目录下创建MemberInfo.vue组件,用于显示成员的详细信息。代码如下:
<template>
<div class="member-info">
<h3>{{ member.name }}</h3>
<p>{{ member.gender === 1 ? '男' : '女' }}</p>
<p>{{ member.birth }} ~ {{ member.death }}</p>
</div>
</template>
<script>
export default {
props: {
member: {
type: Object,
required: true,
},
},
};
</script>
<style scoped>
.member-info {
text-align: center;
}
</style>
(4)创建家庭成员关系组件FamilyTree.vue
在src/components目录下创建FamilyTree.vue组件,用于显示家庭成员之间的关系。代码如下:
<template>
<div class="family-tree">
<div
v-for="(member, index) in familyTree"
:key="index"
:class="['member', member.gender === 1 ? 'male' : 'female']"
:style="{ left: member.x + 'px', top: member.y + 'px' }"
@click="selectMember(member)"
>
{{ member.name }}
<div v-if="selectedMember && selectedMember.id === member.id">
<member-info :member="member" />
</div>
</div>
<div
v-for="(link, index) in familyLinks"
:key="index"
:class="['link', link.type === 0 ? 'father' : 'mother']"
:style="{
left: link.x1 + 'px',
top: link.y1 + 'px',
width: link.width + 'px',
transform: 'rotate(' + link.angle + 'rad)',
}"
/>
</div>
</template>
<script>
import MemberInfo from '@/components/MemberInfo.vue';
export default {
components: {
MemberInfo,
},
props: {
members: {
type: Array,
required: true,
},
},
data() {
return {
familyTree: [],
familyLinks: [],
selectedMember: null,
};
},
mounted() {
this.initFamilyTree();
this.initFamilyLinks();
},
methods: {
initFamilyTree() {
// 将家族成员按照父母关系分组
const familyGroups = {};
this.members.forEach((member) => {
if (member.father_id || member.mother_id) {
const parentId = member.father_id || member.mother_id;
if (!familyGroups[parentId]) {
familyGroups[parentId] = [];
}
familyGroups[parentId].push(member);
} else {
familyGroups[member.id] = [member];
}
});
// 初始化家族成员位置
const rootMembers = familyGroups[0];
rootMembers.forEach((member) => {
this.initMemberPosition(member, 0, 0, 0);
});
// 计算家族成员位置
Object.keys(familyGroups).forEach((parentId) => {
if (parentId === '0') {
return;
}
const parent = this.familyTree.find((member) => member.id === parseInt(parentId));
const parentX = parent.x;
const parentY = parent.y;
const siblings = familyGroups[parentId];
const siblingCount = siblings.length;
const siblingIndex = siblings.findIndex((member) => member.id === parent.id);
siblings.forEach((member, index) => {
if (member.id === parent.id) {
return;
}
const siblingAngle = ((index - (siblingCount - 1) / 2) * Math.PI) / (siblingCount - 1);
const siblingX = parentX + this.memberSpacing * Math.sin(siblingAngle);
const siblingY = parentY + this.memberSpacing * Math.cos(siblingAngle);
this.initMemberPosition(member, parentX, parentY, siblingIndex);
this.moveMember(member, siblingX, siblingY);
});
});
this.familyTree.sort((a, b) => b.y - a.y);
},
initMemberPosition(member, parentX, parentY, siblingIndex) {
const level = member.father_id || member.mother_id ? this.getMemberLevel(member) : 0;
const x = parentX + siblingIndex * this.siblingSpacing - (level - 1) * this.levelSpacing;
const y = parentY + this.levelSpacing;
this.familyTree.push({
id: member.id,
name: member.name,
gender: member.gender,
x,
y,
});
},
moveMember(member, x, y) {
const node = this.familyTree.find((item) => item.id === member.id);
node.x = x;
node.y = y;
},
initFamilyLinks() {
this.members.forEach((member) => {
if (member.father_id) {
const father = this.familyTree.find((item) => item.id === member.father_id);
const memberNode = this.familyTree.find((item) => item.id === member.id);
this.familyLinks.push({
type: 0,
x1: father.x + this.memberWidth / 2,
y1: father.y + this.memberHeight,
x2: memberNode.x + this.memberWidth / 2,
y2: memberNode.y,
});
}
if (member.mother_id) {
const mother = this.familyTree.find((item) => item.id === member.mother_id);
const memberNode = this.familyTree.find((item) => item.id === member.id);
this.familyLinks.push({
type: 1,
x1: mother.x + this.memberWidth / 2,
y1: mother.y + this.memberHeight,
x2: memberNode.x + this.memberWidth / 2,
y2: memberNode.y,
});
}
});
},
getMemberLevel(member) {
const parent = this.members.find((item) => item.id === (member.father_id || member.mother_id));
if (parent) {
return this.getMemberLevel(parent) + 1;
}
return 1;
},
selectMember(member) {
if (this.selectedMember && this.selectedMember.id === member.id) {
this.selectedMember = null;
} else {
this.selectedMember = member;
}
},
},
computed: {
memberWidth() {
return 100;
},
memberHeight() {
return 40;
},
memberSpacing() {
return this.memberWidth * 1.5;
},
siblingSpacing() {
return this.memberWidth * 2;
},
levelSpacing() {
return this.memberHeight * 2;
},
},
};
</script>
<style scoped>
.family-tree {
position: relative;
}
.member {
position: absolute;
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
border-radius: 20px;
cursor: pointer;
transition: all 0.3s ease;
}
.member:hover {
background-color: #f5f5f5;
}
.member.male {
background-color: #69c0ff;
color: #fff;
}
.member.female {
background-color: #ff85c0;
color: #fff;
}
.link {
position: absolute;
height: 2px;
background-color: #999;
}
</style>
(5)在App.vue中使用FamilyTree.vue组件
在src/App.vue中引入FamilyTree.vue组件,并从后端获取成员信息并传递给FamilyTree.vue组件。代码如下:
<template>
<div id="app">
<family-tree :members="members" />
</div>
</template>
<script>
import FamilyTree from '@/components/FamilyTree.vue';
import axios from 'axios';
export default {
components: {
FamilyTree,
},
data() {
return {
members: [],
};
},
mounted() {
axios.get('/api/members').then((response) => {
this.members = response.data;
});
},
};
</script>
以上代码实现了基于Vue实现动态家谱图的功能,具有一定的实用性。
评论区