侧边栏壁纸
博主头像
liuxy博主等级

细水长流,吃穿不愁

  • 累计撰写 38 篇文章
  • 累计创建 30 个标签
  • 累计收到 6 条评论

目 录CONTENT

文章目录

基于 Vue 实现动态家谱图

liuxy
2023-05-16 / 0 评论 / 0 点赞 / 70 阅读 / 1,284 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2023-05-16,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

实现动态家谱图可以采用Vue族谱架构,以下是具体实现代码:

数据库设计
使用MySQL数据库,设计以下表结构:

  1. member表:成员信息
字段名 类型 说明
id int 成员id
name varchar(50) 成员姓名
gender tinyint 成员性别(0:女,1:男)
birth date 成员出生日期
death date 成员去世日期
father_id int 父亲id
mother_id int 母亲id
  1. 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实现动态家谱图的功能,具有一定的实用性。

0
广告 广告

评论区