238 lines
4.7 KiB
Vue
238 lines
4.7 KiB
Vue
<template>
|
|
<view class="mind-columns" :style="{ '--accent-color': accentColor, '--accent-soft': accentSoft }">
|
|
<scroll-view class="columns-scroll" scroll-x :scroll-into-view="scrollTarget" :scroll-with-animation="true" show-scrollbar="false">
|
|
<transition-group name="col-slide" tag="view" class="columns-track">
|
|
<view v-for="(column, index) in columns" :id="`col-${index}`" :key="column.key" class="map-column">
|
|
<view v-if="index > 0" class="column-header">{{ column.parentTitle }}</view>
|
|
<view class="column-body" :class="{ child: index > 0 }">
|
|
<view
|
|
v-for="node in column.nodes"
|
|
:key="node.id"
|
|
class="column-item"
|
|
:class="{
|
|
active: column.selectedId === node.id,
|
|
branch: node.type === 'children',
|
|
leaf: node.type === 'detail'
|
|
}"
|
|
@click="handleNodeClick(node, index)"
|
|
>
|
|
<text class="item-title">{{ node.title }}</text>
|
|
<uni-icons
|
|
:type="node.type === 'children' ? (column.selectedId === node.id ? 'minus-filled' : 'plus-filled') : 'forward'"
|
|
:size="14"
|
|
:color="column.selectedId === node.id ? '#ffffff' : '#7e899b'"
|
|
/>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</transition-group>
|
|
</scroll-view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
let columnSeed = 0;
|
|
|
|
export default {
|
|
name: 'MindMapColumns',
|
|
props: {
|
|
treeData: {
|
|
type: Object,
|
|
required: true
|
|
},
|
|
accentColor: {
|
|
type: String,
|
|
default: '#2b9c57'
|
|
},
|
|
accentSoft: {
|
|
type: String,
|
|
default: 'rgba(43, 156, 87, 0.12)'
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
columns: [],
|
|
scrollTarget: ''
|
|
};
|
|
},
|
|
watch: {
|
|
treeData: {
|
|
immediate: true,
|
|
handler(val) {
|
|
if (!val) {
|
|
this.columns = [];
|
|
return;
|
|
}
|
|
this.initColumns();
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
initColumns() {
|
|
const rootChildren = this.getChildren(this.treeData);
|
|
this.columns = [this.createColumn(rootChildren, this.treeData.title || '根节点')];
|
|
this.$nextTick(() => {
|
|
this.scrollTarget = 'col-0';
|
|
});
|
|
},
|
|
createColumn(nodes, parentTitle) {
|
|
columnSeed += 1;
|
|
return {
|
|
key: `col-${columnSeed}`,
|
|
nodes,
|
|
selectedId: '',
|
|
parentTitle
|
|
};
|
|
},
|
|
getChildren(node) {
|
|
if (!node || !Array.isArray(node.children)) {
|
|
return [];
|
|
}
|
|
return node.children;
|
|
},
|
|
handleNodeClick(node, columnIndex) {
|
|
const current = this.columns[columnIndex];
|
|
if (!current) {
|
|
return;
|
|
}
|
|
|
|
if (node.type === 'detail') {
|
|
this.$emit('open-detail', node);
|
|
return;
|
|
}
|
|
|
|
if (node.type !== 'children') {
|
|
return;
|
|
}
|
|
|
|
const hasNextColumn = this.columns.length > columnIndex + 1;
|
|
const isSameActive = current.selectedId === node.id;
|
|
|
|
if (isSameActive && hasNextColumn) {
|
|
current.selectedId = '';
|
|
this.columns = this.columns.slice(0, columnIndex + 1);
|
|
return;
|
|
}
|
|
|
|
current.selectedId = node.id;
|
|
this.columns = this.columns.slice(0, columnIndex + 1);
|
|
|
|
const children = this.getChildren(node);
|
|
if (!children.length) {
|
|
return;
|
|
}
|
|
|
|
this.columns.push(this.createColumn(children, node.title));
|
|
this.$nextTick(() => {
|
|
this.scrollTarget = `col-${this.columns.length - 1}`;
|
|
});
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.mind-columns {
|
|
height: 100%;
|
|
}
|
|
|
|
.columns-scroll {
|
|
width: 100%;
|
|
height: 100%;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.columns-track {
|
|
display: inline-flex;
|
|
align-items: flex-start;
|
|
min-height: 100%;
|
|
padding-right: 20rpx;
|
|
}
|
|
|
|
.map-column {
|
|
position: relative;
|
|
width: 280rpx;
|
|
flex-shrink: 0;
|
|
margin-right: 22rpx;
|
|
}
|
|
|
|
.map-column::after {
|
|
content: '';
|
|
position: absolute;
|
|
right: -11rpx;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 2rpx;
|
|
background: #e5eaf2;
|
|
}
|
|
|
|
.map-column:last-child::after {
|
|
display: none;
|
|
}
|
|
|
|
.column-header {
|
|
font-size: 22rpx;
|
|
color: #5c6b80;
|
|
margin-bottom: 10rpx;
|
|
padding-left: 4rpx;
|
|
}
|
|
|
|
.column-body {
|
|
background: #ffffff;
|
|
border: 1px solid #e8edf6;
|
|
border-radius: 18rpx;
|
|
padding: 10rpx;
|
|
}
|
|
|
|
.column-body.child {
|
|
background: var(--accent-soft);
|
|
}
|
|
|
|
.column-item {
|
|
height: 72rpx;
|
|
padding: 0 14rpx;
|
|
margin-bottom: 10rpx;
|
|
border-radius: 14rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
background: #f4f7fc;
|
|
border: 1px solid #dde5f3;
|
|
transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease;
|
|
}
|
|
|
|
.column-item:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.column-item.active {
|
|
background: var(--accent-color);
|
|
border-color: var(--accent-color);
|
|
}
|
|
|
|
.column-item.active .item-title {
|
|
color: #ffffff;
|
|
}
|
|
|
|
.item-title {
|
|
font-size: 24rpx;
|
|
color: #33445c;
|
|
max-width: 86%;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.col-slide-enter-active,
|
|
.col-slide-leave-active {
|
|
transition: width 200ms ease, opacity 200ms ease, transform 200ms ease, margin-right 200ms ease;
|
|
}
|
|
|
|
.col-slide-enter,
|
|
.col-slide-leave-to {
|
|
width: 0;
|
|
opacity: 0;
|
|
transform: translateX(20rpx);
|
|
margin-right: 0;
|
|
overflow: hidden;
|
|
}
|
|
</style>
|