feat(resettle2): 树结构改为列表展示

This commit is contained in:
woody 2025-10-27 10:12:20 +08:00
parent 703a76a0df
commit e035e591da
1 changed files with 151 additions and 233 deletions

View File

@ -192,48 +192,54 @@
</view> </view>
</u-popup> </u-popup>
<!-- 树形图弹窗 --> <!-- 会员列表弹窗 -->
<u-popup <u-popup
:show="showTreePopup" :show="showMemberListPopup"
mode="center" mode="center"
@close="showTreePopup = false" @close="showMemberListPopup = false"
:closeOnClickOverlay="true" :closeOnClickOverlay="true"
borderRadius="20" borderRadius="20"
width="96" width="90%"
height="80%" height="70%"
> >
<view class="tree-popup"> <view class="member-list-popup">
<view class="tree-popup-header"> <view class="popup-header">
<text class="popup-title">点位详情</text> <text class="popup-title">点位信息</text>
<u-icon <u-icon
name="close" name="close"
@click="showTreePopup = false" @click="showMemberListPopup = false"
size="32rpx" size="32rpx"
></u-icon> ></u-icon>
</view> </view>
<view class="tree-popup-content"> <view class="popup-content">
<view v-if="treeLoading" class="loading-tree"> <view v-if="memberListLoading" class="loading-list">
<u-loading-icon mode="spinner"></u-loading-icon> <u-loading-icon mode="spinner"></u-loading-icon>
<text>加载中...</text> <text>加载中...</text>
</view> </view>
<view v-else class="tree-container"> <scroll-view v-else class="member-scroll-list" scroll-y="true">
<view <view v-if="memberListData.length > 0" class="member-list">
class="tree-scroll-main" <view
ref="treeScrollMain" v-for="(item, index) in memberListData"
@touchstart.prevent="handleTreeTouchStart" :key="index"
@touchmove.prevent="handleTreeTouchMove" class="member-item"
@touchend="handleTreeTouchEnd" >
@dblclick="handleTreeDoubleClick" <view v-if="item.memberCode" class="member-info">
:style="treeContainerStyle" <text class="member-code">{{ item.point }}</text>
> <text class="member-code">{{
<TreeChart `${item.memberCode}-${item.stage}-${item.stageSort}(${item.childNode})`
:size="treeSize" }}</text>
:json="treeData" <text class="creation-time">{{ item.creationTime }}</text>
@click-node="clickTreeNode" </view>
:stageName="stageName" <view v-else class="member-info">
/> <text class="member-code">{{ item.point }}</text>
<text class="member-code">空点位</text>
</view>
</view>
</view> </view>
</view> <view v-else class="empty-list">
<u-empty mode="data" text="暂无会员数据"></u-empty>
</view>
</scroll-view>
</view> </view>
</view> </view>
</u-popup> </u-popup>
@ -323,21 +329,10 @@ export default {
treeLoading: false, treeLoading: false,
selectedTreeItem: {}, selectedTreeItem: {},
// //
treeSize: 0.8, showMemberListPopup: false,
treeIsZooming: false, memberListData: [],
treeIsDragging: false, memberListLoading: false,
treeTouchStartPosition1: { x: 0, y: 0 },
treeTouchStartPosition2: { x: 0, y: 0 },
treeInitialDistance: 0,
treeInitialSize: 0.5,
treeMinSize: 0.2,
treeMaxSize: 3.0,
treeDragStartPosition: { x: 0, y: 0 },
treeContainerTransform: { x: 0, y: 0 },
treeLastTouchTime: 0,
treeVelocity: { x: 0, y: 0 },
treeMaxTransform: { x: 1000, y: 1000 },
} }
}, },
@ -357,19 +352,6 @@ export default {
this.loadMore() this.loadMore()
}, },
computed: {
treeContainerStyle() {
return {
transform: `translate(${this.treeContainerTransform.x}rpx, ${this.treeContainerTransform.y}px) scale(${this.treeSize})`,
transformOrigin: 'center center',
transition:
this.treeIsDragging || this.treeIsZooming
? 'none'
: 'transform 0.1s ease-out',
}
},
},
methods: { methods: {
init() { init() {
this.stageName = this.stageOptions[0][0].label this.stageName = this.stageOptions[0][0].label
@ -584,15 +566,15 @@ export default {
// //
handleMemberCodeClick(item) { handleMemberCodeClick(item) {
this.selectedTreeItem = item this.selectedTreeItem = item
this.showTreePopup = true this.showMemberListPopup = true
this.loadTreeData(item) this.loadMemberListData(item)
}, },
// //
async loadTreeData(item) { async loadMemberListData(item) {
try { try {
this.treeLoading = true this.memberListLoading = true
this.treeData = {} this.memberListData = []
const params = { const params = {
stage: this.queryParams.stage, stage: this.queryParams.stage,
@ -601,190 +583,24 @@ export default {
const res = await arc.getAzFramework(params) const res = await arc.getAzFramework(params)
if (res.code === 200 && res.data && res.data.length > 0) { if (res.code === 200 && res.data) {
this.treeData = res.data[0] this.memberListData = res.data
} else { } else {
uni.showToast({ uni.showToast({
title: res.msg || '获取树形图数据失败', title: res.msg || '获取会员列表数据失败',
icon: 'none', icon: 'none',
}) })
} }
} catch (error) { } catch (error) {
console.error('加载树形图数据失败:', error) console.error('加载会员列表数据失败:', error)
uni.showToast({ uni.showToast({
title: '网络异常,请稍后重试', title: '网络异常,请稍后重试',
icon: 'none', icon: 'none',
}) })
} finally { } finally {
this.treeLoading = false this.memberListLoading = false
} }
}, },
//
clickTreeNode(e) {
//
console.log('树节点点击:', e)
},
//
handleTreeTouchStart(event) {
const touch1 = event.touches[0]
const touch2 = event.touches[1]
const currentTime = Date.now()
if (touch2) {
// -
this.treeIsZooming = true
this.treeIsDragging = false
this.treeInitialSize = this.treeSize
this.treeTouchStartPosition1 = {
x: touch1.clientX,
y: touch1.clientY,
}
this.treeTouchStartPosition2 = {
x: touch2.clientX,
y: touch2.clientY,
}
this.treeInitialDistance = Math.hypot(
touch2.clientX - touch1.clientX,
touch2.clientY - touch1.clientY
)
} else {
// -
this.treeIsZooming = false
this.treeIsDragging = true
this.treeDragStartPosition = {
x: touch1.clientX,
y: touch1.clientY,
}
this.treeLastTouchTime = currentTime
}
},
//
handleTreeTouchMove(event) {
const touch1 = event.touches[0]
const touch2 = event.touches[1]
if (touch2 && this.treeIsZooming && this.treeInitialDistance > 0) {
//
const currentDistance = Math.hypot(
touch2.clientX - touch1.clientX,
touch2.clientY - touch1.clientY
)
const scale = currentDistance / this.treeInitialDistance
let newSize = this.treeInitialSize * scale
//
newSize = Math.max(
this.treeMinSize,
Math.min(this.treeMaxSize, newSize)
)
//
const sizeDiff = newSize - this.treeSize
this.treeSize = this.treeSize + sizeDiff * 0.3
} else if (!touch2 && this.treeIsDragging) {
//
const deltaX = touch1.clientX - this.treeDragStartPosition.x
const deltaY = touch1.clientY - this.treeDragStartPosition.y
//
this.treeVelocity.x = deltaX * 0.1
this.treeVelocity.y = deltaY * 0.1
//
let newX = this.treeContainerTransform.x + deltaX * 0.8
let newY = this.treeContainerTransform.y + deltaY * 0.8
//
const scaledMaxX = this.treeMaxTransform.x * this.treeSize
const scaledMaxY = this.treeMaxTransform.y * this.treeSize
newX = Math.max(-scaledMaxX, Math.min(scaledMaxX, newX))
newY = Math.max(-scaledMaxY, Math.min(scaledMaxY, newY))
this.treeContainerTransform.x = newX
this.treeContainerTransform.y = newY
//
this.treeDragStartPosition.x = touch1.clientX
this.treeDragStartPosition.y = touch1.clientY
}
},
//
handleTreeTouchEnd(event) {
const currentTime = Date.now()
const touchDuration = currentTime - this.treeLastTouchTime
//
if (
this.treeIsDragging &&
(Math.abs(this.treeVelocity.x) > 1 || Math.abs(this.treeVelocity.y) > 1)
) {
this.startTreeInertiaScroll()
}
//
this.treeIsZooming = false
this.treeIsDragging = false
this.treeTouchStartPosition1 = { x: 0, y: 0 }
this.treeTouchStartPosition2 = { x: 0, y: 0 }
this.treeInitialDistance = 0
this.treeInitialSize = this.treeSize
this.treeDragStartPosition = { x: 0, y: 0 }
},
//
startTreeInertiaScroll() {
const friction = 0.95 //
const minVelocity = 0.1 //
const animateInertia = () => {
//
let newX = this.treeContainerTransform.x + this.treeVelocity.x
let newY = this.treeContainerTransform.y + this.treeVelocity.y
//
const scaledMaxX = this.treeMaxTransform.x * this.treeSize
const scaledMaxY = this.treeMaxTransform.y * this.treeSize
newX = Math.max(-scaledMaxX, Math.min(scaledMaxX, newX))
newY = Math.max(-scaledMaxY, Math.min(scaledMaxY, newY))
this.treeContainerTransform.x = newX
this.treeContainerTransform.y = newY
//
this.treeVelocity.x *= friction
this.treeVelocity.y *= friction
//
if (
Math.abs(this.treeVelocity.x) > minVelocity ||
Math.abs(this.treeVelocity.y) > minVelocity
) {
requestAnimationFrame(animateInertia)
}
}
requestAnimationFrame(animateInertia)
},
//
resetTreeZoom() {
this.treeSize = 0.5
this.treeInitialSize = 0.5
this.treeContainerTransform = { x: 0, y: 0 }
},
//
handleTreeDoubleClick() {
this.resetTreeZoom()
},
}, },
} }
</script> </script>
@ -1255,4 +1071,106 @@ export default {
} }
} }
} }
/* 会员列表弹窗样式 */
.member-list-popup {
width: 100%;
height: 100%;
background: #fff;
border-radius: 20rpx;
overflow: hidden;
display: flex;
flex-direction: column;
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
background: #f5f6f8;
border-bottom: 2rpx solid #eee;
flex-shrink: 0;
.popup-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
}
.popup-content {
flex: 1;
position: relative;
overflow: hidden;
.loading-list {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
font-size: 26rpx;
color: #999;
text {
margin-left: 12rpx;
}
}
.member-scroll-list {
width: 100%;
height: 80vh;
}
.member-list {
padding: 20rpx;
width: 70vw;
.member-item {
background: #fff;
border: 2rpx solid #f0f0f0;
border-radius: 16rpx;
padding: 24rpx 20rpx;
margin-bottom: 16rpx;
transition: all 0.2s ease;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
&:hover {
border-color: #005bac;
box-shadow: 0 4rpx 12rpx rgba(0, 91, 172, 0.15);
}
&:last-child {
margin-bottom: 0;
}
.member-info {
display: flex;
flex-direction: column;
gap: 12rpx;
.member-code {
font-size: 28rpx;
font-weight: 600;
color: #005bac;
line-height: 1.4;
}
.creation-time {
font-size: 24rpx;
color: #666;
display: flex;
align-items: center;
}
}
}
}
.empty-list {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
padding: 60rpx 20rpx;
}
}
}
</style> </style>