0、演示视频:
家谱可视化demo演示_演示 (bilibili.com)
1、思路分析视频:
202206151735_哔哩哔哩bilibili_演示
2、项目基本构成:
????????后端:go语言+gin+gorm框架
????????前端:vue+element-plus
????????数据库:postgresSQL
????????所使用的插件官网:
Royal Family Tree - Family Tree JS (balkan.app)
3、数据表设计:
?sql语句:
DROP TABLE IF EXISTS "public"."visualization";
CREATE TABLE "public"."visualization" (
"id" int primary key,
"pids" int,
"mid" int,
"fid" int,
"name" varchar(255),
"gender" varchar(255),
"birthday" varchar(255),
"familytree" varchar(255),
"img" varchar(255),
"tel" varchar(255),
"email" varchar(255),
"resume" varchar(255),
"is_del" bool NOT NULL DEFAULT false,
"update_time" timestamptz(6) NOT NULL
);
/*
INSERT INTO "public"."visualization" (id,pids,mid,fid,name,gender,birthday,familytree,img,tel,email,resume,is_del,update_time)
VALUES('1','0','0','0','蒋介石','男','1887年10月31日-1975年4月5日','蒋介石家族','1.jpg','123456','123456@tel.com','原名瑞元,学名志清、中正,字介石。', 'f', '2022-06-08 08:00:00+08');
*/
4、后端业务逻辑实现+接口
model层:数据表的结构体设计:
package models
import "time"
type Visualization struct {
Id int `gorm:"column:id"`
Pids int `gorm:"column:pids"`
Mid int `gorm:"column:mid"`
Fid int `gorm:"column:fid"`
Name string `gorm:"column:name"`
Gender string `gorm:"column:gender"`
Birth string `gorm:"column:birthday"`
Familytree string `gorm:"column:familytree"`
Img string `gorm:"column:img"`
Tel string `gorm:"column:tel"`
Email string `gorm:"column:email"`
Resume string `gorm:"column:resume"`
Isdel bool `gorm:"column:is_del"`
UpdateTime time.Time `gorm:"column:update_time"`
}
dao层:主要负责对数据的基本操作。
? ? ? ? familytree.go 获取显示内容:
package dao
import "familytree/models"
func GetFamilytree() (error, []models.Visualization, int64) {
var familytreeData []models.Visualization
var total int64
err := db.Table("visualization").Select("id,pids,mid,fid,name,gender,img").Order("id ASC").Count(&total).Find(&familytreeData).Error
return err, familytreeData, total
}
? ? ? ? visualization.go 对数据表进行增删改查:?
package dao
import "familytree/models"
func GetAllVisual(visual map[string]interface{}) (error, []models.Visualization, int64) {
var visualData []models.Visualization
page := visual["page"].(int)
pageSize := visual["limit"].(int)
searchName := visual["searchName"].(string)
var total int64
err := db.Table("visualization").Select("*").Where("name like ? And is_del = false", searchName+"%").Order("id ASC").Count(&total).Offset((page - 1) * pageSize).Limit(pageSize).Find(&visualData).Error
return err, visualData, total
}
func DelVisual(id string) error {
err := db.Table("visualization").Where("id = ?", id).Update("is_del", true).Error
return err
}
func UpdateVisual(visual models.Visualization) error {
err := db.Table("visualization").Where("id = ?", visual.Id).Updates(&visual).Error
return err
}
func Addvisual(visual models.Visualization) error {
err := db.Table("visualization").Select("id", "pids", "mid", "fid", "name", "gender", "birthday", "familytree", "img", "tel", "email", "resume", "update_time", "is_del").Create(&visual).Error
return err
}
service层:实现基本业务逻辑,调用dao层封装函数。
????????visualization.go调用dao层函数:
package services
import (
"familytree/dao"
"familytree/models"
)
func GetAllVisual(visual map[string]interface{}) (error, []models.Visualization, int64) {
err, visualData, total := dao.GetAllVisual(visual)
return err, visualData, total
}
func DelVisual(id string) error {
err := dao.DelVisual(id)
return err
}
func UpdateVisual(visual models.Visualization) error {
err := dao.UpdateVisual(visual)
return err
}
func AddVisual(visual models.Visualization) error {
err := dao.Addvisual(visual)
return err
}
? ? ? ?familytree.go数据可视化函数:
package services
import (
"familytree/dao"
"familytree/models"
)
func GetFamilytree() (error, []models.Visualization, int64) {
err, familytreeData, total := dao.GetFamilytree()
return err, familytreeData, total
}
controller层:调用service层函数进行前后端数据操作:
familytree,go显示数据:
package controllers
import (
"familytree/pkg/app"
"familytree/pkg/e"
"familytree/services"
"github.com/gin-gonic/gin"
)
func GetFamilytree(c *gin.Context) {
err, info, total := services.GetFamilytree()
if err != nil {
app.Error(c, e.ERROR, err, err.Error())
return
}
var m = map[string]interface{}{"value": info, "total": total}
app.OK(c, m, "查询成功")
}
visualization.go表操作
package controllers
import (
"encoding/json"
"familytree/models"
"familytree/pkg/app"
"familytree/pkg/e"
"familytree/services"
"time"
"github.com/gin-gonic/gin"
"github.com/unknwon/com"
)
func GetVisual(c *gin.Context) {
page := -1
if arg := c.Query("page"); arg != "" {
page = com.StrTo(arg).MustInt()
}
limit := -1
if arg := c.Query("limit"); arg != "" {
limit = com.StrTo(arg).MustInt()
}
searchName := ""
if arg := c.Query("searchName"); arg != "" {
searchName = arg
}
supplierParam := map[string]interface{}{
"page": page,
"limit": limit,
"searchName": searchName,
}
err, info, total := services.GetAllVisual(supplierParam)
if err != nil {
app.Error(c, e.ERROR, err, err.Error())
return
}
app.OK(c, map[string]interface{}{"value": info, "total": total}, "查询成功")
}
func DelVisual(c *gin.Context) {
id := ""
if arg := c.Query("id"); arg != "" {
id = arg
}
if id == "" {
app.INFO(c, 30001, "参数错误")
return
}
err := services.DelVisual(id)
if err != nil {
app.Error(c, e.ERROR, err, err.Error())
return
}
app.OK(c, map[string]interface{}{}, "删除成功")
}
func UpdateVisual(c *gin.Context) {
b, _ := c.GetRawData()
var m map[string]string
_ = json.Unmarshal(b, &m)
if m["Id"] == "" {
app.INFO(c, 30000, "参数非法")
return
}
Id := com.StrTo(m["Id"]).MustInt()
Pids := com.StrTo(m["Pids"]).MustInt()
Mid := com.StrTo(m["Mid"]).MustInt()
Fid := com.StrTo(m["Fid"]).MustInt()
Name := m["Name"]
Gender := m["Gender"]
Birth := m["Birth"]
Familytree := m["Familytree"]
Img := m["Img"]
Tel := m["Tel"]
Email := m["Email"]
Resume := m["Resume"]
err := services.UpdateVisual(models.Visualization{Id: Id, Pids: Pids, Mid: Mid, Fid: Fid, Name: Name, Gender: Gender, Birth: Birth, Familytree: Familytree, Img: Img, Tel: Tel, Email: Email, Resume: Resume, Isdel: false, UpdateTime: time.Now()})
if err != nil {
app.Error(c, e.ERROR, err, err.Error())
return
}
app.OK(c, map[string]interface{}{}, "更新成功")
}
func Addvisual(c *gin.Context) {
b, _ := c.GetRawData()
var m map[string]string
_ = json.Unmarshal(b, &m)
Id := com.StrTo(m["Id"]).MustInt()
Pids := com.StrTo(m["Pids"]).MustInt()
Mid := com.StrTo(m["Mid"]).MustInt()
Fid := com.StrTo(m["Fid"]).MustInt()
Name := m["Name"]
Gender := m["Gender"]
Birth := m["Birth"]
Familytree := m["Familytree"]
Img := m["Img"]
Tel := m["Tel"]
Email := m["Email"]
Resume := m["Resume"]
err := services.AddVisual(models.Visualization{Id: Id, Pids: Pids, Mid: Mid, Fid: Fid, Name: Name, Gender: Gender, Birth: Birth, Familytree: Familytree, Img: Img, Tel: Tel, Email: Email, Resume: Resume, Isdel: false, UpdateTime: time.Now()})
if err != nil {
app.Error(c, e.ERROR, err, err.Error())
return
}
app.OK(c, map[string]interface{}{}, "添加成功")
}
router层:设置路由接口
数据可视化接口:
package routers
import (
"familytree/controllers"
"github.com/gin-gonic/gin"
)
func FamilytreeRouter(r *gin.RouterGroup) {
r.GET("/familytree", controllers.GetFamilytree)
}
操作表接口:
package routers
import (
"familytree/controllers"
"github.com/gin-gonic/gin"
)
func VisualRouter(r *gin.RouterGroup) {
r.GET("/visualization", controllers.GetVisual)
r.DELETE("/visualization", controllers.DelVisual)
r.PUT("/visualization", controllers.UpdateVisual)
r.POST("/visualization", controllers.Addvisual)
}
????????
5、前端代码设计:
表操作代码实现:
<template>
<!-- 卡片视图区 -->
<el-card>
<el-row :gutter="25">
<el-col :span="7">
<!-- 搜索添加 -->
<el-input
placeholder="请输入搜索成员的名字"
v-model.lazy="queryInfo.searchName"
@change="getVisualList"
>
</el-input>
</el-col>
<el-col :span="4">
<el-button type="primary"
>搜索</el-button
>
<el-button type="primary" @click="dialogAddVisible = true"
>添加</el-button
>
</el-col>
</el-row>
<!-- 列表 -->
<el-table
:data="VisualList"
border
stripe
v-loading="isLoading"
element-loading-background="rgba(255, 255, 255, .5)"
element-loading-text="加载中,请稍后..."
element-loading-spinner="el-icon-loading"
>
<el-table-column label="序号" type="index" fixed="left"></el-table-column>
<el-table-column label="成员ID" prop="Id"></el-table-column>
<el-table-column label="伴侣ID" prop="Pids"></el-table-column>
<el-table-column label="母亲ID" prop="Mid"></el-table-column>
<el-table-column label="父亲ID" prop="Fid"></el-table-column>
<el-table-column label="姓名" prop="Name"></el-table-column>
<el-table-column label="性别" prop="Gender"></el-table-column>
<el-table-column label="生日" prop="Birth"></el-table-column>
<el-table-column label="家族" prop="Familytree"></el-table-column>
<el-table-column label="头像" prop="Img"></el-table-column>
<el-table-column label="电话" prop="Tel"></el-table-column>
<el-table-column label="邮箱" prop="Email"></el-table-column>
<el-table-column label="履历" prop="Resume"></el-table-column>
<el-table-column label="更新时间" prop="UpdateTime"></el-table-column>
<el-table-column label="操作" fixed="right">
<template #default="scope">
<!-- 修改 -->
<el-button
type="primary"
icon="el-icon-edit"
size="mini"
@click="
dialogEditOpen(
scope.row.Id,
scope.row.Pids,
scope.row.Mid,
scope.row.Fid,
scope.row.Name,
scope.row.Gender,
scope.row.Birth,
scope.row.Familytree,
scope.row.Img,
scope.row.Tel,
scope.row.Email,
scope.row.Resume,
)
"
>
</el-button>
<!-- 删除 -->
<el-button
type="danger"
icon="el-icon-delete"
size="mini"
@click="deleteVisual(scope.row.Id)"
>
</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="queryInfo.page"
:page-sizes="[1, 2, 10, 100]"
:page-size="queryInfo.limit"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
></el-pagination>
</el-card>
<el-dialog title="添加成员" v-model="dialogAddVisible" width="50%">
<el-form
:model="addForm"
:rules="addFormRules"
ref="addFormRef"
label-width="100px"
>
<el-form-item label="成员ID" prop="Id">
<el-input v-model="addForm.Id" @blur="test()"></el-input>
</el-form-item>
<el-form-item label="伴侣ID" prop="Pids">
<el-input v-model="addForm.Pids"></el-input>
</el-form-item>
<el-form-item label="母亲ID" prop="Mid">
<el-input v-model="addForm.Mid"></el-input>
</el-form-item>
<el-form-item label="父亲ID" prop="Fid">
<el-input v-model="addForm.Fid"></el-input>
</el-form-item>
<el-form-item label="姓名" prop="Name">
<el-input v-model="addForm.Name"></el-input>
</el-form-item>
<el-form-item label="性别" prop="Gender">
<el-input v-model="addForm.Gender"></el-input>
</el-form-item>
<el-form-item label="生日" prop="Birth">
<el-input v-model="addForm.Birth"></el-input>
</el-form-item>
<el-form-item label="家族" prop="Familytree">
<el-input v-model="addForm.Familytree"></el-input>
</el-form-item>
<el-form-item label="头像" prop="Img">
<el-input v-model="addForm.Img"></el-input>
</el-form-item>
<el-form-item label="电话" prop="Tel">
<el-input v-model="addForm.Tel"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="Email">
<el-input v-model="addForm.Email"></el-input>
</el-form-item>
<el-form-item label="履历" prop="Resume">
<el-input v-model="addForm.Resume"></el-input>
</el-form-item>
<el-form-item label="更新时间" prop="UpdateTime">
<el-input v-model="addForm.UpdateTime"></el-input>
</el-form-item>
</el-form>
<el-button type="primary" @click="addVisual()">确 定</el-button>
<el-button @click="dialogAddVisible = false">取 消</el-button>
</el-dialog>
<!-- 编辑对话框 -->
<el-dialog title="编辑成员" v-model="dialogEditVisible" width="50%">
<el-form
:model="editForm"
:rules="editFormRules"
ref="editFormRef"
label-width="100px"
>
<el-form-item label="成员ID" prop="Id">
<el-input v-model="editForm.Id"></el-input>
</el-form-item>
<el-form-item label="伴侣ID" prop="Pids">
<el-input v-model="editForm.Pids"></el-input>
</el-form-item>
<el-form-item label="母亲ID" prop="Mid">
<el-input v-model="editForm.Mid"></el-input>
</el-form-item>
<el-form-item label="父亲ID" prop="Fid">
<el-input v-model="editForm.Fid"></el-input>
</el-form-item>
<el-form-item label="姓名" prop="Name">
<el-input v-model="editForm.Name"></el-input>
</el-form-item>
<el-form-item label="性别" prop="Gender">
<el-input v-model="editForm.Gender"></el-input>
</el-form-item>
<el-form-item label="生日" prop="Birth">
<el-input v-model="editForm.Birth"></el-input>
</el-form-item>
<el-form-item label="家族" prop="Familytree">
<el-input v-model="editForm.Familytree"></el-input>
</el-form-item>
<el-form-item label="头像" prop="Img">
<el-input v-model="editForm.Img"></el-input>
</el-form-item>
<el-form-item label="电话" prop="Tel">
<el-input v-model="editForm.Tel"></el-input>
</el-form-item>
<el-form-item label="邮箱" prop="Email">
<el-input v-model="editForm.Email"></el-input>
</el-form-item>
<el-form-item label="履历" prop="Resume">
<el-input v-model="editForm.Resume"></el-input>
</el-form-item>
</el-form>
<el-button type="primary" @click="updateVisual">确 定</el-button>
<el-button @click="dialogEditVisible = false">取 消</el-button>
</el-dialog>
</template>
<script>
export default {
data() {
return {
isLoading: false,
dialogAddVisible: false,
dialogEditVisible: false,
queryInfo: {
searchName: "",
page: 1,
limit: 10,
},
VisualList: [], // 成员列表
total: 0, // 最大数据记录
addForm: {
Id: "",
Pids: "",
Mid: "",
Fid: "",
Name: "",
Gender: "",
Birth: "",
Familytree: "",
Img: "",
Tel: "",
Email: "",
Resume: "",
UpdateTime: "",
},
editForm: {
Id: "",
Pids: "",
Mid: "",
Fid: "",
Name: "",
Gender: "",
Birth: "",
Familytree: "",
Img: "",
Tel: "",
Email: "",
Resume: "",
UpdateTime: "",
},
addFormRules: {
Id: [
{
required: true,
message: "请输入成员ID",
trigger: "blur",
},
],
Pids: [
{
required: true,
message: "请输入伴侣ID",
trigger: "blur",
},
],
Mid: [
{
required: true,
message: "请输入母亲ID",
trigger: "blur",
},
],
Fid: [
{
required: true,
message: "请输入父亲ID",
trigger: "blur",
},
],
Name: [
{
required: true,
message: "请输入姓名姓名",
trigger: "blur",
},
],
Gender: [
{
required: true,
message: "请输入母亲姓名",
trigger: "blur",
},
],
Birth: [
{
required: true,
message: "请输入成员生日",
trigger: "blur",
},
],
Familytree: [
{
required: true,
message: "请输入成员家族",
trigger: "blur",
},
],
Img: [
{
required: true,
message: "请输入头像",
trigger: "blur",
},
],
Tel: [
{
required: true,
message: "请输入电话",
trigger: "blur",
},
],
Email: [
{
required: true,
message: "请输入成员邮箱",
trigger: "blur",
},
],
Resume:[
{
required: true,
message: "请输入成员履历",
trigger: "blur",
},
],
},
editFormRules: {
Id: [
{
required: false,
message: "请输入成员ID",
trigger: "blur",
},
],
Pids: [
{
required: false,
message: "请输入伴侣ID",
trigger: "blur",
},
],
Mid: [
{
required: false,
message: "请输入母亲ID",
trigger: "blur",
},
],
Fid: [
{
required: false,
message: "请输入父亲ID",
trigger: "blur",
},
],
Name: [
{
required: false,
message: "请输入姓名",
trigger: "blur",
},
],
Gender: [
{
required: false,
message: "请输入性别",
trigger: "blur",
},
],
Birth: [
{
required: false,
message: "请输入成员生日",
trigger: "blur",
},
],
Familytree: [
{
required: false,
message: "请输入成员家族",
trigger: "blur",
},
],
Img: [
{
required: false,
message: "请输入头像",
trigger: "blur",
},
],
Tel: [
{
required: false,
message: "请输入电话",
trigger: "blur",
},
],
Email: [
{
required: false,
message: "请输入成员邮箱",
trigger: "blur",
},
],
Resume:[
{
required: true,
message: "请输入成员履历",
trigger: "blur",
},
],
},
};
},
created() {
this.getVisualList();
},
methods: {
async getVisualList() {
this.isLoading = true;
// 调用post请求
console.log(this.VisualList)
const { data: res } = await this.$http.get("system/apis/visualization", {
params: this.queryInfo,
});
if (res.code != 20000) {
this.$message.error("加载用户列表失败");
this.isLoading = false;
}
this.VisualList = res.data.value; // 将返回数据赋值
this.total = res.data.total; // 总个数
this.isLoading = false;
},
// 监听pageSize改变的事件
handleSizeChange(newLimit) {
this.queryInfo.limit = newLimit;
this.getVisualList(); // 数据发生改变重新申请数据
},
// 监听pageNum改变的事件
handleCurrentChange(newPage) {
this.queryInfo.page = newPage;
this.getVisualList(); // 数据发生改变重新申请数据
},
test(){
console.log(this.addForm.Id);
},
async deleteVisual(Id) {
// 弹框
const confirmResult = await this.$confirm(
"此操作将永久删除该成员, 是否继续?",
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
).catch((err) => err);
// 成功删除为confirm 取消为 cancel
if (confirmResult != "confirm") {
return this.$message.info("已取消删除");
}
const { data: res } = await this.$http.delete(
"system/apis/visualization?id=" + Id
);
if (res.code != 20000) {
return this.$message.error("删除失败");
}
this.$message.success("删除成功");
this.getVisualList();
},
// 添加成员
addVisual() {
this.$refs.addFormRef.validate(async (valid) => {
if (!valid) return;
// 发起请求
const { data: res } = await this.$http.post(
"/system/apis/visualization",
this.addForm
);
if (res.code == 20000) {
this.getVisualList();
this.dialogAddVisible = false;
return this.$message.success("添加成功");
}
this.$message.error("添加失败");
});
},
// 编辑
updateVisual() {
this.$refs.editFormRef.validate(async (valid) => {
if (!valid) return;
// 发起请求
console.log(this.Id)
const { data: res } = await this.$http.put(
"/system/apis/visualization",
this.editForm
);
if (res.code == 20000) {
this.getVisualList();
this.dialogEditVisible = false;
return this.$message.success("编辑成功");
}
this.$message.error("编辑失败");
});
},
dialogEditOpen(
Id,
Pids,
Mid,
Fid,
Name,
Gender,
Birth,
Familytree,
Img,
Tel,
Email,
Resume
) {
this.editForm.Id = String(Id);
this.editForm.Pids = String(Pids);
this.editForm.Mid = String(Mid);
this.editForm.Fid = String(Fid);
this.editForm.Name = String(Name);
this.editForm.Gender = String(Gender);
this.editForm.Birth = String(Birth);
this.editForm.Familytree = String(Familytree);
this.editForm.Img = String(Img);
this.editForm.Tel = String(Tel);
this.editForm.Email = String(Email);
this.editForm.Resume = String(Resume);
this.dialogEditVisible = true;
},
},
};
</script>
<style>
</style>
可视化代码实现:
<template>
<div>{{total}},{{v}},{{nodes}}</div>
<div id="tree" ref="tree"></div>
</template>
<script>
import FamilyTree from '@balkangraph/familytree.js'
export default {
name: 'tree',
data() {
return {
/*
queryInfo: {
page: 1,
limit: 10,
},*/
nodes:[],
temp:[],
map:{"id":0,"pids":0,"name":""},
//map:{"id":0,"pids":0,"mid":0,"fid":0,"name":""},
total:0,
v:"",
}},
created(){
this.getNodeList();
},
methods: {
mytree: function(domEl, x) {
this.family = new FamilyTree (domEl, {
nodes: x,
nodeBinding: {
field_0: "name",
img_0: "img"
}
});
},
async getNodeList(){
const { data: res } = await this.$http.get("system/apis/familytree");
if (res.code != 20000) {
this.$message.error("加载家族族谱失败");
}
this.total=res.data["total"];
this.v=res.data.value;
var count=0;
for(var i in res.data.value)
//for(var i =0;i<this.total;i++)
{
count++;
if(count==1)
{
var map={"id":0,"pids":[],"name":"","gender":"","img":""};
map["id"]=res.data.value[i]["Id"];
map["pids"][0]=res.data.value[i]["Pids"];
//this.map["mid"]=res.data.value[i]["Mid"];
//this.map["fid"]=res.data.value[i]["Fid"];
map["name"]=res.data.value[i]["Name"];
map["gender"]=res.data.value[i]["Gender"];
map["img"]=res.data.value[i]["Img"];
this.temp[i]=map;
}
else{
var map={"id":0,"pids":[],"mid":0,"fid":0,"name":"","gender":"","img":""};
map["id"]=res.data.value[i]["Id"];
map["pids"][0]=res.data.value[i]["Pids"];
map["mid"]=res.data.value[i]["Mid"];
map["fid"]=res.data.value[i]["Fid"];
map["name"]=res.data.value[i]["Name"];
map["gender"]=res.data.value[i]["Gender"];
map["img"]=res.data.value[i]["Img"];
this.temp[i]=map;
}
}
//this.nodes[0] = this.temp[0]; // 将返回数据赋值
//this.nodes[1] = this.temp[1]; // 将返回数据赋值
for(var i =this.total-1;i>=0;i--)
{
this.nodes[i] = this.temp[i]; // 将返回数据赋值
}
}
},
mounted(){
this.mytree(this.$refs.tree, this.nodes)
}
}
</script>
<style scoped>
</style>
6、在前端项目导入familytree插件方法:
cnpm install
cnpm i @balkangraph/familytree.js
然后复制粘贴代码使用即可。
7、演示:
插入表:
?可视化:
示例2:?
8、如果发生这种报错暂可不用理会,再次运行即可。
优点:完成了数据库连接的可视化界面
缺点:目前不支持多父母和多前任夫妻关系,可以将pids和mid、fid的值改为列表即可。
|