This commit is contained in:
woody 2025-10-22 16:10:15 +08:00
commit 9d00ba690b
4 changed files with 379 additions and 11 deletions

View File

@ -15,18 +15,20 @@
treeData.extend, treeData.extend,
}" }"
> >
<view :class="{ node: true }"> <view class="node">
<view <view
class="person" class="person"
:class="Array.isArray(treeData.class) ? treeData.class : []" :class="Array.isArray(treeData.class) ? treeData.class : []"
> >
<view class="frame-text"> <view class="frame-text">
<view style="font-weight: bold">{{ <!-- <view style="font-weight: bold">{{
treeData.nodeCode === '0-root' ? '' : treeData.nodeCode treeData.nodeCode === '0-root' ? '' : treeData.nodeCode
}}</view> }}</view> -->
<view v-if="treeData.memberCode && treeData.memberName"> <view v-if="treeData.memberCode && treeData.memberName">
<view> {{ treeData.memberCode }}</view> <view> {{ treeData.memberCode }}</view>
<view>{{ treeData.memberName }}</view> <view>{{ stageName }}</view>
<!-- <view>{{ treeData.memberName }}</view> -->
<view>{{ treeData.creationTime }}</view>
</view> </view>
<view v-else> 空点位 </view> <view v-else> 空点位 </view>
</view> </view>
@ -54,6 +56,7 @@
:top="0" :top="0"
@click-node="clickNode" @click-node="clickNode"
@click-top="clickTop" @click-top="clickTop"
:stageName="stageName"
/> />
</view> </view>
</view> </view>
@ -63,7 +66,7 @@
<script> <script>
export default { export default {
name: 'TreeChart', name: 'TreeChart',
props: ['json', 'size'], props: ['json', 'size', 'stageName'],
data() { data() {
return { return {
treeData: {}, treeData: {},
@ -291,6 +294,7 @@ export default {
border: 2rpx solid #e9ecef; border: 2rpx solid #e9ecef;
transition: all 0.3s ease; transition: all 0.3s ease;
cursor: pointer; cursor: pointer;
min-width: 120rpx;
} }
.node .person:hover { .node .person:hover {

View File

@ -42,7 +42,7 @@
<!-- 基本信息 --> <!-- 基本信息 -->
<view class="item-header"> <view class="item-header">
<view class="member-info"> <view class="member-info">
<text class="member-code">{{ <text class="member-code" @click="handleMemberCodeClick(item)">{{
`${currentUser.memberCode}-${item.childNode}` `${currentUser.memberCode}-${item.childNode}`
}}</text> }}</text>
</view> </view>
@ -192,6 +192,52 @@
</view> </view>
</u-popup> </u-popup>
<!-- 树形图弹窗 -->
<u-popup
:show="showTreePopup"
mode="center"
@close="showTreePopup = false"
:closeOnClickOverlay="true"
borderRadius="20"
width="96"
height="80%"
>
<view class="tree-popup">
<view class="tree-popup-header">
<text class="popup-title">点位详情</text>
<u-icon
name="close"
@click="showTreePopup = false"
size="32rpx"
></u-icon>
</view>
<view class="tree-popup-content">
<view v-if="treeLoading" class="loading-tree">
<u-loading-icon mode="spinner"></u-loading-icon>
<text>加载中...</text>
</view>
<view v-else class="tree-container">
<view
class="tree-scroll-main"
ref="treeScrollMain"
@touchstart.prevent="handleTreeTouchStart"
@touchmove.prevent="handleTreeTouchMove"
@touchend="handleTreeTouchEnd"
@dblclick="handleTreeDoubleClick"
:style="treeContainerStyle"
>
<TreeChart
:size="treeSize"
:json="treeData"
@click-node="clickTreeNode"
:stageName="stageName"
/>
</view>
</view>
</view>
</view>
</u-popup>
<!-- 选择器 --> <!-- 选择器 -->
<u-picker <u-picker
:show="stageListVisible" :show="stageListVisible"
@ -213,8 +259,12 @@
<script> <script>
import * as arc from '@/config/architecture.js' import * as arc from '@/config/architecture.js'
import TreeChart from '@/components/architectures/resettleSO.vue'
export default { export default {
components: {
TreeChart,
},
data() { data() {
return { return {
// //
@ -266,6 +316,28 @@ export default {
pointDetail: {}, pointDetail: {},
pointDetailLoading: false, pointDetailLoading: false,
currentUser: {}, currentUser: {},
//
showTreePopup: false,
treeData: {},
treeLoading: false,
selectedTreeItem: {},
//
treeSize: 0.8,
treeIsZooming: false,
treeIsDragging: 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 },
} }
}, },
@ -285,12 +357,25 @@ 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
this.queryParams.stage = this.stageOptions[0][0].value this.queryParams.stage = this.stageOptions[0][0].value
// "" // ""
const statusIndex = 1 const statusIndex = 2
this.selectedStatusValue = this.statusOptions[0][statusIndex].value this.selectedStatusValue = this.statusOptions[0][statusIndex].value
this.statusName = this.statusOptions[0][statusIndex].label this.statusName = this.statusOptions[0][statusIndex].label
this.queryParams.stageStatus = this.selectedStatusValue this.queryParams.stageStatus = this.selectedStatusValue
@ -495,6 +580,211 @@ export default {
const date = new Date(dateStr) const date = new Date(dateStr)
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}` return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
}, },
//
handleMemberCodeClick(item) {
this.selectedTreeItem = item
this.showTreePopup = true
this.loadTreeData(item)
},
//
async loadTreeData(item) {
try {
this.treeLoading = true
this.treeData = {}
const params = {
stage: this.queryParams.stage,
childNode: item.childNode,
}
const res = await arc.getAzFramework(params)
if (res.code === 200 && res.data && res.data.length > 0) {
this.treeData = res.data[0]
} else {
uni.showToast({
title: res.msg || '获取树形图数据失败',
icon: 'none',
})
}
} catch (error) {
console.error('加载树形图数据失败:', error)
uni.showToast({
title: '网络异常,请稍后重试',
icon: 'none',
})
} finally {
this.treeLoading = 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>
@ -639,9 +929,15 @@ export default {
.member-code { .member-code {
font-size: 24rpx; font-size: 24rpx;
font-weight: bold; font-weight: bold;
color: #333; color: #005bac;
flex-shrink: 0; flex-shrink: 0;
margin-right: 30rpx; margin-right: 30rpx;
cursor: pointer;
text-decoration: underline;
&:active {
color: #003d7a;
}
} }
.status-badge { .status-badge {
@ -890,4 +1186,72 @@ export default {
} }
} }
} }
/* 树形图弹窗样式 */
.tree-popup {
width: 95vw;
height: 80vh;
background: #fff;
border-radius: 20rpx;
overflow: hidden;
display: flex;
flex-direction: column;
.tree-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;
}
}
.tree-popup-content {
flex: 1;
position: relative;
overflow: hidden;
.loading-tree {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
font-size: 26rpx;
color: #999;
text {
margin-left: 12rpx;
}
}
.tree-container {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
background: #fff;
.tree-scroll-main {
width: 100%;
height: 100%;
transform-origin: center center;
cursor: grab;
display: flex;
align-items: center;
justify-content: center;
&:active {
cursor: grabbing;
}
}
}
}
}
</style> </style>

View File

@ -15,8 +15,8 @@
<text class="value">{{ item.settleGradeVal }}</text> <text class="value">{{ item.settleGradeVal }}</text>
</view> </view>
<view class="item-row"> <view class="item-row">
<text class="label">会员昵称</text> <text class="label">会员姓名</text>
<text class="value">{{ item.nickName }}</text> <text class="value">{{ item.memberName }}</text>
</view> </view>
<view class="item-row"> <view class="item-row">
<text class="label">创建时间</text> <text class="label">创建时间</text>

View File

@ -305,7 +305,7 @@ export default {
}, },
{ {
url: '/pages/mine/directPush/index', url: '/pages/mine/directPush/index',
name: '直推列表', name: '服务列表',
imgurl: '../../static/images/mark5.png', imgurl: '../../static/images/mark5.png',
menuKey: 'directPush', menuKey: 'directPush',
ifshow: false, ifshow: false,