族谱树对接完成

main
helloworld180 3 months ago
parent 0f196ca887
commit 56071bdbf4

@ -5,7 +5,7 @@ import {getToken} from '@/token/auth' // 注意这里使用了解构赋值来导
// 创建axios实例
const service = axios.create({
// 去vue.config.js文件改baseurl
baseURL: '/api', // 注意!! 这里全局统一加上了 '/api' 前缀但是vue.config.js文件在发送请求时去掉了/api所以页面的请求接口若有/api还是要携带/api
baseURL: 'http://10.133.25.59:8083/loveforest/api', // 注意!! 这里全局统一加上了 '/api' 前缀但是vue.config.js文件在发送请求时去掉了/api所以页面的请求接口若有/api还是要携带/api
timeout: 5000, // 请求超时时间
});

@ -5,7 +5,7 @@
<div class="operation-bar">
<el-button type="primary" @click="showAddDialog"></el-button>
<el-button type="success" @click="editMember"></el-button>
<el-button type="danger" @click="saveTreeData"></el-button>
<!-- <el-button type="danger" @click="saveTreeData"></el-button> -->
</div>
<!-- 族谱树图 -->
@ -15,9 +15,9 @@
:collapse-enabled="true"
node-text="name"
>
<template v-slot:node="{ node }">
<template #node="{ node }">
<div class="custom-node" :class="{ 'current-user': node.isCurrentUser }">
<el-avatar :size="50" :src="node.avatar" />
<img :size="50" :src="node.avatar" />
<div class="node-info">
<span class="name">{{ node.name }}</span>
<span class="relation">{{ node.relation }}</span>
@ -35,8 +35,8 @@
v-model="dialogVisible"
width="500px"
>
<el-form :model="currentMember" label-width="100px">
<el-form-item label="姓名">
<el-form :model="currentMember" ref="memberForm" label-width="100px" :rules="rules">
<el-form-item label="姓名" prop="name">
<el-input v-model="currentMember.name"></el-input>
</el-form-item>
@ -56,7 +56,7 @@
</div>
</el-form-item>
<el-form-item label="所属分支">
<el-form-item label="所属分支" prop="parentId">
<el-select v-model="currentMember.parentId" placeholder="选择所属分支">
<el-option
v-for="member in availableParents"
@ -64,16 +64,16 @@
:label="member.name"
:value="member.id"
/>
<el-option label="新建分支" value="new" />
<!-- <el-option label="新建分支" value="new" /> -->
</el-select>
</el-form-item>
<el-form-item label="关系">
<el-form-item label="关系" prop="relation">
<el-select v-model="currentMember.relation">
<el-option label="祖辈" value="grandparent" />
<el-option label="父辈" value="parent" />
<el-option label="子女" value="grandparent" />
<!-- <el-option label="父辈" value="parent" />
<el-option label="同辈" value="sibling" />
<el-option label="子辈" value="child" />
<el-option label="子辈" value="child" /> -->
</el-select>
</el-form-item>
@ -143,10 +143,22 @@ export default {
},
data() {
return {
userId: 3,
rules: {
name: [
{ required: true, message: '请输入姓名', trigger: 'blur' }
],
parentId: [
{ required: true, message: '请选择所属分支', trigger: 'change' }
],
relation: [
{ required: true, message: '请选择关系', trigger: 'change' }
]
},
treeData :{
id: '1',
name: '爷爷',
avatar: '/avatar/1.jpg',
avatar: 'https://via.placeholder.com/150',
relation: 'grandfather',
birthDate: '1940-01-01',
phone: '13800138000',
@ -154,7 +166,7 @@ export default {
{
id: '2',
name: '父亲',
avatar: '/avatar/2.jpg',
avatar: '../../assets/pictures/space/post1.png',
relation: 'father',
birthDate: '1965-01-01',
phone: '13800138001',
@ -207,7 +219,7 @@ export default {
phone: '',
isCurrentUser: false
},
uploadUrl: '/api/upload',
uploadUrl: '/upload',
flattenedTreeData: [
{
id: 1,
@ -281,15 +293,28 @@ export default {
//
async fetchTreeData() {
try {
const { data } = await request.get('/api/family-tree')
this.treeData = data
this.flattenedTreeData = flattenTree(data)
const response = await request.get(`/family-tree/${this.userId}`)
this.processTreeData(response)
this.treeData = response
this.flattenedTreeData = flattenTree(response)
this.availableParents = this.flattenedTreeData
// console.log('', response)
} catch (error) {
ElMessage.error('获取族谱数据失败')
}
},
processTreeData(node) {
//
node.avatar = node.avatar || '/default-avatar.png'
node.relation = node.relation || '未知'
node.birthDate = node.birthDate || '未知'
node.phone = node.phone || '未知'
//
if (node.children && node.children.length) {
node.children.forEach(child => this.processTreeData(child))
}
},
//
showAddDialog() {
this.dialogTitle = '添加成员'
@ -354,23 +379,31 @@ export default {
//
async saveMember() {
const isValid = await new Promise((resolve) => {
this.$refs.memberForm.validate((valid) => {
resolve(valid);
});
});
if (!isValid) {
ElMessage.error('请填写完整的表单信息!');
return;
}
try {
const url = this.currentMember.id ?
`/api/family-tree/${this.currentMember.id}` :
'/api/family-tree'
const { data } = await request({
`/family-tree/${this.currentMember.id}` :
'/family-tree'
await request({
url,
method: this.currentMember.id ? 'put' : 'post',
data: this.currentMember
})
if (data.code === 0) {
// console.log(':',response) response
ElMessage.success('保存成功')
this.dialogVisible = false
this.fetchTreeData()
}
} catch (error) {
console.log('新增成员失败', this.currentMember)
ElMessage.error('保存失败')
}
},
@ -378,12 +411,28 @@ export default {
//
async deleteMember() {
try {
const { data } = await request.delete(`/api/family-tree/${this.currentMember.id}`)
if (data.code === 0) {
ElMessage.success('删除成功')
this.dialogVisible = false
this.fetchTreeData()
const confirmResult = await this.$confirm(
'此操作将永久删除该成员, 是否继续?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
).catch(err => err);
// confirmResult 'confirm'
// confirmResult 'cancel'
if (confirmResult !== 'confirm') {
return; //
}
//
await request.delete(`/family-tree/${this.currentMember.id}`);
ElMessage.success('删除成功');
this.dialogVisible = false;
this.fetchTreeData();
} catch (error) {
ElMessage.error('删除失败')
}
@ -392,10 +441,8 @@ export default {
//
async saveTreeData() {
try {
const { data } = await request.put('/api/family-tree', this.treeData)
if (data.code === 0) {
ElMessage.success('保存成功')
}
await request.put('/family-tree', this.treeData)
ElMessage.success('保存成功')
} catch (error) {
ElMessage.error('保存失败')
}

@ -1,337 +1,178 @@
<template>
<div class="background">
<div class="tree">
<!-- 操作按钮 -->
<div class="operation-bar">
<el-button type="primary" @click="showAddDialog()"></el-button>
<el-button type="success" @click="saveTreeData"></el-button>
</div>
<!-- 族谱树 -->
<TreeChart :json="treeData">
<template v-slot:node="{ node }">
<div class="node-info" :class="{ 'is-current-user': node.isCurrentUser }">
<img :src="node.image_url" alt="image" class="node-image" @click="handleNodeClick(node)" />
<div class="info">
<div class="family-tree">
<!-- 工具栏 -->
<div class="toolbar">
<el-button @click="zoomIn"></el-button>
<el-button @click="zoomOut"></el-button>
<el-button @click="resetZoom"></el-button>
</div>
<!-- 树图容器 -->
<div class="tree-wrapper" ref="treeWrapper">
<TreeChart
:json="treeData"
:collapse-enabled="true"
:horizontal-gap="60"
:vertical-gap="60"
node-text="name"
class="tree-chart"
>
<template #node="{ node }">
<div
class="custom-node"
:class="{ 'is-current': node.isCurrentUser }"
>
<!-- 头像区域 -->
<div class="avatar-wrapper">
<el-avatar
:size="50"
:src="node.avatar"
@error="() => true"
/>
</div>
<!-- 信息区域 -->
<div class="info-wrapper">
<div class="name">{{ node.name }}</div>
<button @click="toggleNode(node)">
{{ node.expanded ? '隐藏下一代' : '显示下一代' }}
</button>
<div class="relation">{{ node.relation }}</div>
<div class="birth-date">{{ node.birthDate }}</div>
</div>
</div>
<div v-if="node.expanded" class="children">
<TreeChart :json="node.children" v-if="node.children && node.children.length"/>
</div>
</template>
</TreeChart>
<!-- 节点编辑弹窗 -->
<el-dialog
:title="dialogTitle"
v-model="dialogVisible"
width="500px"
:destroy-on-close="true"
>
<el-form :model="currentNode" label-width="100px">
<el-form-item label="姓名">
<el-input v-model="currentNode.name"></el-input>
</el-form-item>
<el-form-item label="辈分">
<el-select v-model="currentNode.generation">
<el-option label="祖辈" value="grandparent"></el-option>
<el-option label="父辈" value="parent"></el-option>
<el-option label="同辈" value="sibling"></el-option>
<el-option label="子辈" value="child"></el-option>
</el-select>
</el-form-item>
<el-form-item label="头像">
<el-upload
class="avatar-uploader"
:action="uploadUrl"
:show-file-list="false"
:on-success="handleAvatarSuccess"
>
<img v-if="currentNode.image_url" :src="currentNode.image_url" class="avatar">
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
</el-form-item>
<el-form-item label="是否本人">
<el-switch v-model="currentNode.isCurrentUser"></el-switch>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveNode"></el-button>
<el-button type="danger" v-if="currentNode.id" @click="deleteNode"></el-button>
</template>
</el-dialog>
</div>
</div>
</template>
<script>
import TreeChart from "vue-tree-chart-3";
import { ElMessage } from 'element-plus';
import request from '@/utils/axiosConfig';
import TreeChart from 'vue-tree-chart-3'
import { ref } from 'vue'
export default {
components: {
TreeChart
components: { TreeChart },
setup() {
const treeWrapper = ref(null)
const scale = ref(1)
//
const zoomIn = () => {
scale.value = Math.min(scale.value + 0.1, 2)
updateScale()
}
const zoomOut = () => {
scale.value = Math.max(scale.value - 0.1, 0.5)
updateScale()
}
const resetZoom = () => {
scale.value = 1
updateScale()
}
const updateScale = () => {
if (treeWrapper.value) {
treeWrapper.value.style.transform = `scale(${scale.value})`
}
}
return {
treeWrapper,
zoomIn,
zoomOut,
resetZoom
}
},
data() {
return {
treeData: {
name: '爸爸',
image_url: require('@/assets/pictures/space/Inbase.png'),
expanded: true, //
name: '爷爷',
avatar: '../../assets/pictures/space/post1.png',
relation: '祖父',
birthDate: '1940-01-01',
children: [
{
name: 'children1',
image_url: "https://static.refined-x.com/avat1.jpg",
expanded: true,
},
{
name: 'children2',
image_url: "https://static.refined-x.com/avat2.jpg",
expanded: true,
mate: [{
name: 'mate',
image_url: "https://static.refined-x.com/avat3.jpg"
}],
name: '父亲',
avatar: 'https://via.placeholder.com/150',
relation: '父亲',
birthDate: '1965-01-01',
children: [
{
name: 'grandchild',
image_url: "https://static.refined-x.com/avat.jpg"
},
{
name: 'grandchild2',
image_url: "https://static.refined-x.com/avat1.jpg",
expanded: true,
children:[
{
name:'dd',
image_url: "https://static.refined-x.com/avat1.jpg",
}
]
},
{
name: 'grandchild3',
image_url: "https://static.refined-x.com/avat2.jpg"
},
{
name:'hh',
image_url: "https://static.refined-x.com/avat2.jpg"
name: '我',
avatar: '/avatars/me.jpg',
relation: '本人',
birthDate: '1990-01-01',
isCurrentUser: true,
children: []
}
]
},
{
name: 'children3',
image_url: "https://static.refined-x.com/avat.jpg"
}
]
},
dialogVisible: false,
dialogTitle: '',
currentNode: {
id: '',
name: '',
generation: '',
image_url: '',
isCurrentUser: false,
expanded: true,
children: []
},
uploadUrl: '',
};
},
created() {
this.fetchTreeData();
},
methods: {
//
async fetchTreeData() {
try {
const res = await request.get('/api/family-tree');
if (res.code === 0 && res.data) {
this.treeData = res.data;
}
} catch (error) {
ElMessage.error('获取族谱数据失败');
}
},
// /
toggleNode(node) {
node.expanded = !node.expanded;
},
//
handleNodeClick(node) {
this.dialogTitle = '编辑成员';
this.currentNode = { ...node };
this.dialogVisible = true;
console.log('Node clicked:', node);
},
//
showAddDialog() {
this.dialogTitle = '添加成员';
this.currentNode = {
id: '',
name: '',
generation: '',
image_url: '',
isCurrentUser: false,
expanded: true,
children: []
};
this.dialogVisible = true;
},
//
handleAvatarSuccess(response) {
if (response && response.url) {
this.currentNode.image_url = response.url; // URL
console.log('上传头像的url' + response)
}
},
//
async saveNode() {
try {
const url = this.currentNode.id ?
`/api/family-tree/node/${this.currentNode.id}` :
'/api/family-tree/node';
const method = this.currentNode.id ? 'put' : 'post';
const res = await request[method](url, this.currentNode);
if (res.code === 0) {
ElMessage.success('保存成功');
this.dialogVisible = false;
await this.fetchTreeData();
}
} catch (error) {
ElMessage.error('保存失败');
}
},
//
async deleteNode() {
try {
const res = await request.delete(`/api/family-tree/node/${this.currentNode.id}`);
if (res.code === 0) {
ElMessage.success('删除成功');
this.dialogVisible = false;
await this.fetchTreeData();
}
} catch (error) {
ElMessage.error('删除失败');
}
},
//
async saveTreeData() {
try {
const res = await request.put('/api/family-tree', this.treeData);
if (res.code === 0) {
ElMessage.success('保存成功');
}
} catch (error) {
ElMessage.error('保存失败');
}
}
}
};
}
</script>
<style scoped>
.background {
.family-tree {
height: 100%;
display: flex;
align-content: center;
justify-content: center;
background-image: url("../../assets/pictures/space/Inbase3.png");
background-size: cover;
background-position: center top;
background-repeat: no-repeat;
min-height: calc(100vh - 60px);
width: 100%;
flex-direction: column;
}
.tree {
display: flex;
/* position: absolute; */
flex-direction: column;
background: rgba(255, 255, 255, 0.8);
width: 1100px;
/* height: 700px; */
border-radius: 30px;
margin-top: 30px;
margin-left: 240px;
/* padding: 20px; */
justify-content: center;
align-items: center;
flex-shrink: 0;
.toolbar {
padding: 10px;
border-bottom: 1px solid #eee;
}
.operation-bar {
margin-bottom: 20px;
text-align: center;
.tree-wrapper {
flex: 1;
overflow: auto;
padding: 20px;
transform-origin: center center;
transition: transform 0.3s;
}
.node-info {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
cursor: pointer;
.custom-node {
padding: 10px;
border: 1px solid #ddd;
border-radius: 8px;
transition: all 0.3s;
background: white;
min-width: 150px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.node-info:hover {
background: rgba(0, 0, 0, 0.1);
}
.is-current-user {
background: rgba(64, 158, 255, 0.1);
.is-current {
border: 2px solid #409EFF;
background: #ecf5ff;
}
.node-image {
width: 40px;
height: 40px;
border-radius: 50%;
margin-bottom: 10px;
.avatar-wrapper {
margin-bottom: 8px;
text-align: center;
}
.info {
font-size: 14px;
.info-wrapper {
text-align: center;
}
.children {
margin-top: 20px;
padding-left: 20px;
display: flex;
flex-direction: column;
.name {
font-weight: bold;
font-size: 16px;
margin-bottom: 4px;
}
.avatar-uploader {
display: flex;
justify-content: center;
align-items: center;
width: 80px;
height: 80px;
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
.relation {
color: #666;
font-size: 14px;
margin-bottom: 2px;
}
.avatar {
width: 100px;
height: 100px;
display: block;
.birth-date {
color: #999;
font-size: 12px;
}
</style>
</style>

@ -1,12 +1,12 @@
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
devServer:{
host: 'localhost',
port: 8080,
// host: 'localhost',
// port: 8080,
https: false,
proxy:{
'/api': { //设置拦截器 拦截器格式 斜杠+拦截器名字,名字可以自己定
target: 'https://911fb0525ms3.vicp.fun/loveforest/api/', //代理的目标地址
target: 'http://10.133.25.59:8083/loveforest', //代理的目标地址
changeOrigin: true, //是否设置同源,输入是的
pathRewrite: { //路径重写
'^/api': '' //选择忽略拦截器里面的内容

Loading…
Cancel
Save