179 lines
3.2 KiB
Vue
179 lines
3.2 KiB
Vue
<template>
|
|
<view class="mind-node-row">
|
|
<view class="node-card" :class="nodeClass" @click.stop="handleNodeClick">
|
|
<text class="node-title">{{ node.title }}</text>
|
|
<view v-if="isBranch" class="toggle-icon">{{ expanded ? '-' : '+' }}</view>
|
|
<uni-icons v-else type="forward" color="#5b6d86" :size="12" />
|
|
</view>
|
|
|
|
<view v-if="isBranch && expanded" class="children-shell">
|
|
<view class="main-link"></view>
|
|
<view class="children-list">
|
|
<view v-for="child in node.children" :key="child.id" class="child-row">
|
|
<mind-map-node :node="child" @open-detail="emitDetail" />
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
name: 'MindMapNode',
|
|
props: {
|
|
node: {
|
|
type: Object,
|
|
required: true
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
expanded: false
|
|
};
|
|
},
|
|
watch: {
|
|
node: {
|
|
immediate: true,
|
|
handler(val) {
|
|
if (!val) {
|
|
return;
|
|
}
|
|
this.expanded = val.defaultExpanded === true;
|
|
}
|
|
}
|
|
},
|
|
computed: {
|
|
isBranch() {
|
|
return this.node.type === 'children' && Array.isArray(this.node.children) && this.node.children.length > 0;
|
|
},
|
|
nodeClass() {
|
|
if (this.node.type === 'children') {
|
|
return this.expanded ? 'branch expanded' : 'branch';
|
|
}
|
|
return 'leaf';
|
|
}
|
|
},
|
|
methods: {
|
|
handleNodeClick() {
|
|
if (this.node.type === 'detail') {
|
|
this.$emit('open-detail', this.node);
|
|
return;
|
|
}
|
|
if (!this.isBranch) {
|
|
return;
|
|
}
|
|
this.expanded = !this.expanded;
|
|
},
|
|
emitDetail(node) {
|
|
this.$emit('open-detail', node);
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.mind-node-row {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.node-card {
|
|
min-width: 200rpx;
|
|
max-width: 320rpx;
|
|
padding: 16rpx 18rpx;
|
|
border-radius: 16rpx;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 14rpx;
|
|
box-sizing: border-box;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.node-title {
|
|
font-size: 24rpx;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.node-card.branch {
|
|
color: #ffffff;
|
|
background: linear-gradient(135deg, #58af6e 0%, #2d7f49 100%);
|
|
box-shadow: 0 6rpx 16rpx rgba(30, 97, 56, 0.25);
|
|
}
|
|
|
|
.node-card.branch.expanded {
|
|
background: linear-gradient(135deg, #3a98ee 0%, #2f6fcf 100%);
|
|
box-shadow: 0 8rpx 20rpx rgba(45, 111, 207, 0.28);
|
|
}
|
|
|
|
.node-card.leaf {
|
|
color: #334158;
|
|
background: #edf2fb;
|
|
border: 1px solid #d8e1f3;
|
|
}
|
|
|
|
.toggle-icon {
|
|
width: 28rpx;
|
|
height: 28rpx;
|
|
border-radius: 50%;
|
|
background: rgba(255, 255, 255, 0.25);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 20rpx;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.children-shell {
|
|
display: inline-flex;
|
|
align-items: flex-start;
|
|
margin-left: 24rpx;
|
|
}
|
|
|
|
.main-link {
|
|
width: 28rpx;
|
|
height: 2rpx;
|
|
background: #8ab598;
|
|
margin-right: 8rpx;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.children-list {
|
|
position: relative;
|
|
padding-left: 18rpx;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.children-list::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 0;
|
|
top: 10rpx;
|
|
bottom: 10rpx;
|
|
width: 2rpx;
|
|
background: #8ab598;
|
|
}
|
|
|
|
.child-row {
|
|
position: relative;
|
|
padding: 10rpx 0;
|
|
}
|
|
|
|
.child-row::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: -18rpx;
|
|
top: 50%;
|
|
width: 18rpx;
|
|
height: 2rpx;
|
|
background: #8ab598;
|
|
}
|
|
|
|
.child-row:last-child {
|
|
padding-bottom: 0;
|
|
}
|
|
</style>
|