| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- <template>
- <view class="tree-node" :style="{ paddingLeft: paddingLeft }">
- <!-- 节点主体:头像、昵称、层级、消费额、我得CF,点击展开/折叠 -->
- <view class="node-main" @click="toggle">
- <view class="avatar-wrap">
- <image v-if="node.avatar" class="node-avatar" :src="node.avatar" mode="aspectFill" lazy-load />
- <text v-else class="node-avatar-text">{{ avatarInitial }}</text>
- </view>
- <view class="node-body">
- <view class="node-header-row">
- <text class="node-name">{{ node.nickname || '微信用户' }}</text>
- <text class="node-tag" :class="'node-tag-L' + nodeLevel">L{{ nodeLevel }}</text>
- </view>
- <view class="node-stats">
- <text class="node-stat consumption">消费 ¥{{ consumptionText }}</text>
- <text class="node-stat my-cf">我得CF ¥{{ cfText }}</text>
- </view>
- </view>
- <view class="node-toggle" v-if="hasChildren">
- <text class="toggle-icon">{{ toggleIcon }}</text>
- </view>
- </view>
- <!-- 子节点:父容器为 column-reverse,子节点在上方渲染,形成“从下往上生长”的层级树 -->
- <view class="node-children" v-if="expanded && hasChildren">
- <TreeNode
- v-for="child in node.children"
- :key="child.userId"
- :node="child"
- :level="childLevel"
- :maxLevel="maxLevel"
- />
- </view>
- </view>
- </template>
- <script>
- import TreeNode from './TreeNode'
- export default {
- name: 'TreeNode',
- components: { TreeNode },
- props: {
- node: {
- type: Object,
- default: function() {
- return {}
- }
- },
- level: {
- type: Number,
- default: 1
- },
- maxLevel: {
- type: Number,
- default: 3
- }
- },
- data: function() {
- return {
- expanded: false
- }
- },
- computed: {
- childLevel: function() {
- return this.level + 1
- },
- nodeLevel: function() {
- return this.node && this.node.level ? this.node.level : this.level
- },
- avatarInitial: function() {
- var name = this.node && this.node.nickname ? String(this.node.nickname) : ''
- return name.charAt(0) || '?'
- },
- hasChildren: function() {
- var children = this.node && this.node.children
- return Array.isArray(children) && children.length > 0
- },
- toggleIcon: function() {
- return this.expanded ? '▲' : '▼'
- },
- consumptionText: function() {
- var fen = this.node && this.node.memberConsumption != null ? this.node.memberConsumption : 0
- return (fen / 100).toFixed(2)
- },
- cfText: function() {
- var fen = this.node && this.node.myCfEarnedFromMember != null ? this.node.myCfEarnedFromMember : 0
- return (fen / 100).toFixed(2)
- },
- paddingLeft: function() {
- return this.level > 1 ? (this.level - 1) * 24 + 'rpx' : '0rpx'
- }
- },
- methods: {
- toggle: function() {
- this.expanded = !this.expanded
- }
- }
- }
- </script>
- <style scoped lang="scss">
- .tree-node {
- margin-bottom: 16rpx;
- }
- .node-main {
- display: flex;
- align-items: center;
- gap: 16rpx;
- background: $app-surface;
- border-radius: 16rpx;
- padding: 20rpx;
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
- }
- .avatar-wrap {
- width: 60rpx;
- height: 60rpx;
- border-radius: 50%;
- background: $status-warning-soft;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-right: 0;
- overflow: hidden;
- flex-shrink: 0;
- }
- .node-avatar {
- width: 60rpx;
- height: 60rpx;
- border-radius: 50%;
- }
- .node-avatar-text {
- width: 60rpx;
- height: 60rpx;
- border-radius: 50%;
- background: $status-warning-soft;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 24rpx;
- color: $status-warning-deep;
- flex-shrink: 0;
- }
- .node-body {
- flex: 1;
- min-width: 0;
- }
- .node-header-row {
- display: flex;
- align-items: center;
- gap: 10rpx;
- margin-bottom: 6rpx;
- }
- .node-name {
- flex: 1;
- min-width: 0;
- font-size: 26rpx;
- color: $app-text;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- .node-tag {
- font-size: 20rpx;
- padding: 2rpx 8rpx;
- border-radius: 8rpx;
- flex-shrink: 0;
- }
- .node-tag-L1 {
- color: $brand-primary-deep;
- background: $status-warning-soft;
- }
- .node-tag-L2 {
- color: $status-success-deep;
- background: $status-success-tint;
- }
- .node-tag-L3 {
- color: $brand-primary-deep;
- background: $brand-primary-tint-2;
- }
- .node-stats {
- flex-shrink: 0;
- text-align: right;
- margin-right: 8rpx;
- line-height: 1.5;
- }
- .node-stat {
- font-size: 20rpx;
- display: block;
- }
- .node-stat.consumption {
- color: $status-warning-deep;
- }
- .node-stat.my-cf {
- color: $status-success-strong;
- }
- .node-toggle {
- font-size: 20rpx;
- color: $app-text-subtle;
- flex-shrink: 0;
- }
- .node-children {
- margin-top: 12rpx;
- padding-left: 30rpx;
- border-left: 1rpx dashed $brand-primary-light-2;
- }
- </style>
|