完成效果"
uniapp树状族谱关系图
本章效果:

1.设置可移动缩放区域
template:
html
<view class="tree cx-fex cx-fex-itemsc cx-fex-c">
<movable-area class="movable-area" v-show="treeData.length"
:style="{'paddingBottom': `calc(${systemInfo.safeAreaInsetBottom}px + 136rpx)`, 'box-sizing': 'border-box', 'width': `${windowSizeComputed.width}`, 'height': `calc(${windowSizeComputed.height}px - ${systemInfo.safeAreaInsetBottom}px - 136rpx)`}">
<movable-view :x="left" :y="top" direction="all" scale :scale-value="scaleNum" scale-min="0.01"
class="movable-view" :out-of-bounds="false" :style="{ 'width': `fit-content`, 'height': `fit-content` }"
@scale="movableViewScale" @change="movableViewScale">
<view class="content">
<tree-item ref="treeItem" :tree-data="treeData" :tree-first="true" :pageType="pageType" init />
</view>
</movable-view>
</movable-area>
<view class="addBtn" v-show="!treeData.length" @click="addAncestors">
+ 点击马上添加族人
</view>
<bottom-button @touch="pageSubmit">
{{ pageType == 'preview' ? '下载族谱' : '发布' }}
</bottom-button>
</view>
script:
javascript
computed:{
...mapState(['systemInfo']),
//屏幕宽高
windowSizeComputed: {
get() {
let {
width,
height
} = this.x_y
let {
systemInfo
} = this
return {
width: width || '100%',
height: height || systemInfo.windowHeight
}
},
set(width, height) {
this.x_y = {
width,
height
}
}
},
},
data:{
//族谱数据
treeData: [
// {
// name: "章龙操",
// sex: 1,
// address: "江西省抚州市",
// desc: 1,
// type: ['刘配氏', '章配氏'],
// child: [],
// checked: true,
// addressCopy: ['江西省', '抚州市', '临川区']
// },
],
}
css:
html
@mixin formStyle {
.form {
height: calc(100% - 200rpx);
// 家庭成员类型
.family-item {
width: 231rpx;
height: 71rpx;
line-height: 71rpx;
text-align: center;
background: #FFFFFF;
border-radius: 10rpx;
border: 1px solid #B98C52;
margin-bottom: 20rpx;
&.lastChild {
width: 100%;
}
}
.for-item {
height: 25%;
margin-bottom: 20rpx;
.label {
width: 120rpx;
.required {
color: red;
}
}
.input {
width: calc(100% - 120rpx);
height: 70rpx;
line-height: 70rpx;
&.textarea {
height: auto;
}
&.text {
padding: 0 5px;
box-sizing: border-box;
border: 1px solid rgba(231, 231, 231, 1);
border-radius: 10rpx;
font-size: 12px;
}
.man {
margin-right: 90rpx;
}
.icon {
width: 24rpx;
height: 24rpx;
border-radius: 50%;
border: 1px solid rgba(231, 231, 231, 1);
position: relative;
margin-right: 13rpx;
}
.icon.active {
width: 24rpx;
height: 24rpx;
border-radius: 50%;
border: 1px solid #B98C52;
position: relative;
margin-right: 13rpx;
.iconContent {
width: 14rpx;
height: 14rpx;
border-radius: 50%;
background: #B98C52;
}
}
.ReleaseAreaText {
width: 100%;
height: 240rpx !important;
max-height: 240rpx;
border: 1px solid rgba(231, 231, 231, 1);
border-radius: 10rpx;
padding: 5px;
box-sizing: border-box;
}
&.desc {
& /deep/ .uni-select {
padding: 0 5px;
box-sizing: border-box;
}
}
&.spouse {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 2.5%;
.item {
width: 100%;
padding: 12rpx 0;
line-height: 1;
border-radius: 10rpx;
margin: 8rpx 0;
text-align: center;
position: relative;
&.active {
background: rgba(179, 136, 80, .1);
color: #895C22;
}
&.off {
background-color: #f8f8f8;
}
.zlc-bianji {
position: absolute;
top: -6rpx;
right: -6rpx;
font-size: 28rpx;
}
}
}
.timePicker {
width: calc(100% - 30rpx);
}
}
}
}
}
@mixin headerStyle {
.header {
width: 100%;
height: 100rpx;
line-height: 100rpx;
.title {
font-weight: 700;
}
.icon {
font-size: 40rpx;
}
}
}
@mixin footerStyle {
.footer {
height: 100rpx;
line-height: 100rpx;
.btn {
width: 280rpx;
height: 80rpx;
text-align: center;
line-height: 80rpx;
background: #B98D55;
border-radius: 40rpx;
color: #fff;
}
}
}
.tree {
width: 100%;
height: 100%;
background-color: #f8f8f8;
background-size: 0.5cm 0.5cm;
background-image:
linear-gradient(to right, rgba(231, 231, 231, 1) 1px, transparent 1px),
linear-gradient(to bottom, rgba(231, 231, 231, 1) 1px, transparent 1px);
// padding-bottom: calc(env(safe-area-inset-bottom) + 116rpx);
// padding-bottom: calc(constant(safe-area-inset-bottom) + 116rpx);
box-sizing: border-box;
overflow: hidden;
position: relative;
.movable-area-add {
width: 70rpx;
right: 0;
position: fixed;
pointer-events: none;
z-index: 999;
.movable-view-add {
width: fit-content;
height: fit-content;
pointer-events: auto;
.addShow {
width: 90rpx;
height: 90rpx;
line-height: 90rpx;
text-align: center;
background: #FFFFFF;
box-shadow: 0rpx 2rpx 13rpx 0rpx rgba(0, 0, 0, 0.05);
border-radius: 50%;
font-weight: 400;
font-size: 26rpx;
color: #895C22;
}
}
}
.addBtn {
width: 352rpx;
height: 152rpx;
line-height: 152rpx;
text-align: center;
background: #FFFFFF;
box-shadow: 0rpx 2rpx 13rpx 0rpx rgba(0, 0, 0, 0.05);
border-radius: 10rpx;
border: 2px solid #B98C52;
}
.main {
width: 570rpx;
height: fit-content;
background: #FFFFFF;
border-radius: 20rpx;
padding: 0 34rpx 40rpx;
box-sizing: border-box;
&.deletePersonalPopup_main {
height: 350rpx;
}
&.loadPicture_main {
width: 570rpx;
height: 1000rpx;
image {
width: 100%;
height: 100%;
}
}
&.datePicker{
width: 100%;
height: 500rpx;
.header{
width: 100%;
height: 100rpx;
line-height: 100rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
.cancel{
color: #895C22;
}
.confirm{
color: #895C22;
}
.tabs{
width: 300rpx;
height: 50rpx;
display: flex;
flex-direction: row;
align-items: center;
.pre{
width: 50%;
border-radius: 20rpx 0 0 20rpx;
text-align: center;
line-height: 50rpx;
border-top: 1px solid #895C22;
border-bottom: 1px solid #895C22;
border-left: 1px solid #895C22;
color: #895C22;
&.on{
color: #FFFFFF;
background-color: #895C22;
}
}
.next{
width: 50%;
border-radius: 0 20rpx 20rpx 0;
text-align: center;
line-height: 50rpx;
border: 1px solid #895C22;
color: #895C22;
&.on{
color: #FFFFFF;
background-color: #895C22;
}
}
}
}
.content{
width: 100%;
height: 400rpx;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
.BC{
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
.ipt{
width: 100rpx;
height: 50rpx;
border: 1px solid #895C22;
border-radius: 10rpx;
&.month{
margin-left: 20rpx;
}
}
.label{
&.month{
margin-right: 20rpx;
}
}
}
}
}
@include headerStyle();
@include formStyle();
@include footerStyle();
&.ReleasePopup {
height: fit-content;
.form {
.for-item {
height: fit-content;
margin-bottom: 20rpx;
.label {
width: 140rpx;
}
.input {
width: calc(100% - 140rpx);
& /deep/ input {
width: 100%;
text-align: left;
}
&.noBorder {
border: none;
height: fit-content;
}
&.birthday {
.zlcIcon {
font-size: 40rpx;
}
}
& /deep/ .uni-select {
padding: 0 5px;
box-sizing: border-box;
}
}
.genealogy_image {
.imgBox {
width: fit-content;
height: fit-content;
position: relative;
.cx-icon {
width: 30rpx;
height: 30rpx;
font-size: 30rpx;
position: absolute;
right: -9rpx;
top: -30rpx;
color: #B98C52;
// background: #fff;
border-radius: 50%;
&::before {
width: 100%;
height: 100%;
border-radius: 50%;
background: #FFFFFF;
top: -30rpx !important;
}
}
.item {
width: 150rpx;
height: 150rpx;
border-radius: 20rpx;
box-shadow: 0rpx 0rpx 10rpx rgba(0, 0, 0, 0.5);
}
}
}
}
}
}
}
.content {
width: fit-content;
height: fit-content;
}
.loadGenealogy {
position: absolute;
left: -500%;
top: -500%;
width: fit-content;
height: fit-content;
}
}
2.在app.vue获取设备信息
<script>
export default {
onLaunch: function() {
console.log('App Launch');
this.getPhoneHeight();
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
},
methods: {
getPhoneHeight() { //获取高度
let that = this;
let systemInfo = {};
uni.getSystemInfo({
success: function(res) {
if (res.platform == "ios") {
systemInfo.toBar = 44;
} else if (res.platform == "android") {
systemInfo.toBar = 48;
} else {
systemInfo.toBar = 44;
}
if (res.safeArea.top > 40) {
systemInfo.isIphoneX = true;
}
systemInfo.windowWidth = res.screenWidth; //windowWidth
systemInfo.windowHeight = res.screenHeight; //windowHeight
systemInfo.statusBarHeight = res.statusBarHeight; //状态栏的高度
systemInfo.heightBlank = Number(systemInfo.statusBarHeight) + Number(systemInfo.toBar);
systemInfo.safeAreaInsetBottom = res.safeAreaInsets.bottom //ios底部安全距离
that.$store.commit('SET_SYSTEMINFO', systemInfo);
},
});
},
}
}
</script>
<style>
@import url('@/static/css/iconfont.css');
/*每个页面公共css */
page {
background-color: #f8f8f8;
--default-color: #895C22;
width: 100%;
height: 100%;
}
:not(not) {
box-sizing: border-box
}
</style>
3.vuex仓库代码
import {
stat
} from 'fs';
import Vue from 'vue';
import VueX from 'vuex';
Vue.use(VueX);
const store = new VueX.Store({
state: {
systemInfo: null,
},
mutations: {
SET_SYSTEMINFO(state, payload) {
state.systemInfo = payload;
state.systemInfo.pageMarginTop = state.systemInfo.statusBarHeight * 2 + 90;
},
},
})
export default store