<template>
|
<view class="tree-node">
|
<view class="tree-row" :class="{ active: isActive }" :style="{ paddingLeft: `${level * 20 + 20}rpx` }" @click="onClick">
|
<uni-icons v-if="hasChildren" :type="isExpanded ? 'down' : 'right'" color="#666" size="24"></uni-icons>
|
<view v-else class="empty-icon"></view>
|
<text class="row-title">{{ node.name }}</text>
|
<text v-if="node.count" class="row-meta">({{ node.count }})</text>
|
<view class="row-actions">
|
<button class="enter-btn" @click="onView">详情</button>
|
</view>
|
</view>
|
<view v-if="isExpanded && hasChildren" class="tree-children">
|
<TreeItem
|
v-for="child in node.children"
|
:key="child.id"
|
:node="child"
|
:level="level + 1"
|
:search-query="searchQuery"
|
:expanded-map="expandedMap"
|
:active-id="activeId"
|
@toggle-expand="forwardToggle"
|
@view-node="forwardView"
|
/>
|
</view>
|
</view>
|
</template>
|
|
<script>
|
import TreeItem from './TreeItem.vue'
|
export default {
|
components: {
|
TreeItem
|
},
|
name: 'TreeItem',
|
props: {
|
node: { type: Object, required: true, default: () => ({}) },
|
level: { type: Number, default: 0 },
|
searchQuery: { type: String, default: '' },
|
expandedMap: { type: Object, default: () => ({}) },
|
activeId: { type: [String, Number], default: '' }
|
},
|
emits: ['toggle-expand', 'view-node'],
|
computed: {
|
hasChildren() {
|
return Array.isArray(this.node?.children) && this.node.children.length > 0
|
},
|
isExpanded() {
|
const q = (this.searchQuery || '').trim()
|
return q ? true : !!this.expandedMap[this.node.id]
|
},
|
isActive() {
|
return String(this.node?.id) === String(this.activeId)
|
}
|
},
|
methods: {
|
onClick() {
|
if (!this.hasChildren) {
|
this.$emit('view-node', this.node)
|
return
|
}
|
this.$emit('toggle-expand', this.node.id)
|
},
|
onView() {
|
const url = `/subpackage/user/project-detail?id=${this.node.id}&name=${encodeURIComponent(this.node.name || '')}`
|
uni.navigateTo({ url })
|
},
|
forwardToggle(id) {
|
this.$emit('toggle-expand', id)
|
},
|
forwardView(node) {
|
this.$emit('view-node', node)
|
}
|
}
|
}
|
</script>
|
|
<style scoped>
|
.tree-node { width: 100%; }
|
.tree-row { display: flex; align-items: center; padding: 20rpx 0; box-sizing: border-box; }
|
.tree-row.active {
|
background: #e8f4fd; /* 浅蓝底色 */
|
border-radius: 8rpx;
|
/* 可选:加轻微边框增强层次感 */
|
border: 1px solid #d1e7fd;
|
}
|
/* 搭配文字变色,高亮更明显 */
|
.tree-row.active .row-title {
|
color: #1E88E5;
|
}
|
.empty-icon { width: 24rpx; height: 24rpx; }
|
.row-title { font-size: 30rpx; color: #333; margin-left: 10rpx; }
|
.row-meta { font-size: 26rpx; color: #999; margin-left: 8rpx; }
|
.row-actions { margin-left: auto; }
|
.enter-btn {
|
height: 56rpx;
|
line-height: 56rpx;
|
padding: 0 20rpx;
|
border-radius: 8rpx;
|
font-size: 24rpx;
|
color: #1E88E5;
|
background: #E3F2FD;
|
border: none;
|
}
|
.tree-children { width: 100%; overflow: visible; margin-top: 8rpx; }
|
</style>
|