web-zk-h5/pages/architecture/resettleArchite/resettle1.vue

857 lines
22 KiB
Vue
Raw Normal View History

2025-03-23 09:29:40 +08:00
<template>
<view>
<view class="seach">
<view class="neibox"> 子点位 </view>
<view class="seach_i">
<view class="inputbox" @click="childNodeListVisible = true">
<view class="">
{{ selectedChildNodeName ? selectedChildNodeName : '请选择子点位' }}
</view>
<u-icon name="arrow-right" size="24rpx" color="#005bac"></u-icon>
</view>
</view>
<view class="neibox" @click="popShow = true"> 筛选 </view>
</view>
<view class="mainbox">
<view class="main_bottom">
<view
class="scoll_main"
ref="scrollMain"
@touchstart.prevent="handleTouchStart"
@touchmove.prevent="handleTouchMove"
@touchend="handleTouchEnd"
@dblclick="handleDoubleClick"
:style="containerStyle"
>
<TreeChart
:size="size"
:json="data"
:class="{ landscape: landscape.length }"
@click-node="clickNode"
/>
</view>
</view>
</view>
<u-popup
:show="popShow"
mode="right"
@close="popShow = false"
:closeOnClickOverlay="false"
>
<view class="rightPopup">
<view class="popup_top">
<view
@click="
() => {
getDataList(), (popShow = false)
}
"
>筛选</view
>
<view class="top_red" @click="popShow = false">{{ '返回' }}</view>
</view>
<view class="typesBox">
<view class="typeTitle" @click="stageListVisible = true"> 阶段 </view>
<view class="choiceBox">
<view class="inputbox" @click="stageListVisible = true">
<view class="">
{{ stageName ? stageName : '请选择' }}
</view>
<u-icon name="arrow-right" size="24rpx" color="#090000"></u-icon>
</view>
</view>
</view>
<view class="typesBox">
<view class="typeTitle" @click="statusListVisible = true">
状态
</view>
<view class="choiceBox">
<view class="inputbox" @click="statusListVisible = true">
<view class="">
{{ statusName ? statusName : '请选择' }}
</view>
<u-icon name="arrow-right" size="24rpx" color="#090000"></u-icon>
</view>
</view>
</view>
<view class="popup_bottom">
<view class="bottom_btn thebtn1" @click="clearAll">清空筛选条件</view>
<view
class="bottom_btn thebtn2"
@click="
() => {
handleSearch(), (popShow = false)
}
"
>{{ '确定' }}
</view>
</view>
</view>
<u-picker
@cancel="stageListVisible = false"
:show="stageListVisible"
ref="uPicker"
:columns="stageList"
@confirm="stagePickerHandleConfirm"
keyName="label"
></u-picker>
<u-picker
@cancel="statusListVisible = false"
:show="statusListVisible"
ref="uPicker"
:columns="statusList"
@confirm="statusPickerHandleConfirm"
keyName="label"
></u-picker>
</u-popup>
<!-- 子点位选择picker -->
<u-picker
@cancel="childNodeListVisible = false"
:show="childNodeListVisible"
ref="childNodePicker"
:columns="childNodeColumns"
@confirm="childNodePickerHandleConfirm"
keyName="childNode"
></u-picker>
<Eposter
width="750"
height="1334"
:list="list"
backgroundColor="rgb(255, 255, 255)"
@on-success="onSuccess"
ref="Eposter"
>
</Eposter>
</view>
2025-03-23 09:29:40 +08:00
</template>
<script>
import html2canvas from 'html2canvas'
import TreeChart from '@/components/architectures/resettleSO.vue'
import Eposter from '@/components/architectures/Poster.vue'
import * as arc from '@/config/architecture.js'
export default {
components: {
TreeChart,
Eposter,
},
data() {
return {
childNodeList: [],
childNodeColumns: [[]],
childNodeListVisible: false,
selectedChildNodeName: '',
treeData: [],
queryParams: {
childNode: '',
stage: 1,
stageStatus: null,
},
stageList: [
[
{ value: 1, label: '阶段一' },
{ value: 2, label: '阶段二' },
{ value: 3, label: '阶段三' },
],
], //期数
statusList: [
[
{ value: 0, label: '已完成' },
{ value: 1, label: '未完成' },
],
], //期数
popShow: false,
stageListVisible: false,
statusListVisible: false,
stageName: '',
statusName: '',
data: {},
size: 0.5,
landscape: [],
popMould: {},
isPop: false,
touchStartPosition1: {
x: 0,
y: 0,
},
touchStartPosition2: {
x: 0,
y: 0,
},
initialDistance: 0,
initialSize: 0.4, // 记录缩放开始时的初始尺寸
minSize: 0.2, // 最小缩放值
maxSize: 3.0, // 最大缩放值
isZooming: false, // 是否正在缩放
isDragging: false, // 是否正在拖动
dragStartPosition: { x: 0, y: 0 }, // 拖动开始位置
containerTransform: { x: 0, y: 0 }, // 容器变换位置
lastTouchTime: 0, // 上次触摸时间,用于区分点击和拖动
lastTouchPosition: { x: 0, y: 0 }, // 上次触摸位置,用于计算速度
velocity: { x: 0, y: 0 }, // 拖动速度,用于惯性滚动
maxTransform: { x: 1000, y: 1000 }, // 最大变换距离限制
list: [],
}
},
onLoad() {
this.init()
this.getChildList().then(() => {
if (this.queryParams.childNode) {
this.getDataList()
}
})
},
mounted() {
// 初始化容器变换
this.$nextTick(() => {
console.log('组件已挂载')
})
},
computed: {
containerStyle() {
return {
transform: `translate(${this.containerTransform.x}px, ${this.containerTransform.y}px) scale(${this.size})`,
transformOrigin: 'center center',
transition:
this.isDragging || this.isZooming
? 'none'
: 'transform 0.1s ease-out',
}
},
},
methods: {
init() {
this.stageName = this.stageList[0][0].label
// this.statusName = this.statusList[0][0].label
this.queryParams.stage = this.stageList[0][0].value
},
//复制文字
copyText() {
let self = this
let md = self.popMould
let text = `会员编号:${md.memberCode} \n会员姓名${md.name}支付时间:${md.payDate} \n业绩  左区  右区 \n真实新增  ${md.leftRealNewPv}  ${md.rightRealNewPv} \n首购新增  ${md.leftFirstPurchaseAdd}  ${md.rightFirstPurchaseAdd} \n复购新增 ${md.leftRepeatPurchaseSurplus}  ${md.rightRepeatPurchaseSurplus} \n真实累计 ${md.leftRealTotal}  ${md.rightRealTotal} \n首购累计 ${md.leftFirstTotal}  ${md.rightFirstTotal} \n复购累计 ${md.leftRepeatPurchaseTotal}  ${md.rightRepeatPurchaseTotal} \n首购结余 ${md.leftFirstSurplus}  ${md.rightFirstSurplus} \n复购结余 ${md.leftRepeatPurchaseSurplus}  ${md.rightRepeatPurchaseSurplus} \n`
uni.setClipboardData({
data: text,
success: function (res) {
uni.getClipboardData({
success: function (res) {
uni.showToast({
title: '复制成功',
})
},
})
},
})
},
//下载图片
downImage(elClass) {
this.$refs.Eposter.createForElRect(elClass, false)
},
downloadImg() {
let self = this
let element = document.querySelector('.Poster1')
uni.showLoading({
title: '图片保存中',
})
html2canvas(element)
.then(function (canvas) {
let dataURL = canvas.toDataURL('image/jpeg')
let link = document.createElement('a')
link.style.display = 'none'
link.href = dataURL
link.download = 'image.jpg'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
uni.showToast({
icon: 'none',
title: '保存成功',
duration: 2000,
})
uni.hideLoading()
})
.catch(function (error) {
uni.hideLoading()
uni.showModal({
title: '保存失败',
})
})
},
onSuccess(val) {
// console.log('🌈val',val)
this.posterImg = val
this.downloadImg(this.posterImg)
},
handleTouchStart(event) {
console.log('handleTouchStart triggered', event.touches.length)
const touch1 = event.touches[0]
const touch2 = event.touches[1]
const currentTime = Date.now()
if (touch2) {
// 双指触摸 - 缩放模式
this.isZooming = true
this.isDragging = false
this.initialSize = this.size
this.touchStartPosition1 = {
x: touch1.clientX,
y: touch1.clientY,
}
this.touchStartPosition2 = {
x: touch2.clientX,
y: touch2.clientY,
}
this.initialDistance = Math.hypot(
touch2.clientX - touch1.clientX,
touch2.clientY - touch1.clientY
)
} else {
// 单指触摸 - 拖动模式
this.isZooming = false
this.isDragging = true
this.dragStartPosition = {
x: touch1.clientX,
y: touch1.clientY,
}
this.lastTouchTime = currentTime
}
},
handleTouchMove(event) {
const touch1 = event.touches[0]
const touch2 = event.touches[1]
if (touch2 && this.isZooming && this.initialDistance > 0) {
// 双指缩放处理
const currentDistance = Math.hypot(
touch2.clientX - touch1.clientX,
touch2.clientY - touch1.clientY
)
const scale = currentDistance / this.initialDistance
let newSize = this.initialSize * scale
// 应用边界限制
newSize = Math.max(this.minSize, Math.min(this.maxSize, newSize))
// 平滑缩放
const sizeDiff = newSize - this.size
this.size = this.size + sizeDiff * 0.3
} else if (!touch2 && this.isDragging) {
// 单指拖动处理
const deltaX = touch1.clientX - this.dragStartPosition.x
const deltaY = touch1.clientY - this.dragStartPosition.y
console.log('拖动中', {
deltaX,
deltaY,
touch: { x: touch1.clientX, y: touch1.clientY },
dragStart: this.dragStartPosition,
containerTransform: this.containerTransform,
})
// 计算拖动速度
this.velocity.x = deltaX * 0.1
this.velocity.y = deltaY * 0.1
// 更新容器变换位置(添加阻尼效果)
let newX = this.containerTransform.x + deltaX * 0.8
let newY = this.containerTransform.y + deltaY * 0.8
// 应用边界限制(根据缩放调整边界)
const scaledMaxX = this.maxTransform.x * this.size
const scaledMaxY = this.maxTransform.y * this.size
newX = Math.max(-scaledMaxX, Math.min(scaledMaxX, newX))
newY = Math.max(-scaledMaxY, Math.min(scaledMaxY, newY))
this.containerTransform.x = newX
this.containerTransform.y = newY
// 更新拖动起始位置
this.dragStartPosition.x = touch1.clientX
this.dragStartPosition.y = touch1.clientY
// 应用变换到DOM元素
this.updateContainerTransform()
}
},
handleTouchEnd(event) {
const currentTime = Date.now()
const touchDuration = currentTime - this.lastTouchTime
// 如果是拖动结束,启动惯性滚动
if (
this.isDragging &&
(Math.abs(this.velocity.x) > 1 || Math.abs(this.velocity.y) > 1)
) {
this.startInertiaScroll()
}
// 重置触摸状态
this.isZooming = false
this.isDragging = false
this.touchStartPosition1 = { x: 0, y: 0 }
this.touchStartPosition2 = { x: 0, y: 0 }
this.initialDistance = 0
this.initialSize = this.size
this.dragStartPosition = { x: 0, y: 0 }
},
// 惯性滚动
startInertiaScroll() {
const friction = 0.95 // 摩擦系数
const minVelocity = 0.1 // 最小速度阈值
const animateInertia = () => {
// 应用速度到位置
let newX = this.containerTransform.x + this.velocity.x
let newY = this.containerTransform.y + this.velocity.y
// 应用边界限制
const scaledMaxX = this.maxTransform.x * this.size
const scaledMaxY = this.maxTransform.y * this.size
newX = Math.max(-scaledMaxX, Math.min(scaledMaxX, newX))
newY = Math.max(-scaledMaxY, Math.min(scaledMaxY, newY))
this.containerTransform.x = newX
this.containerTransform.y = newY
// 减少速度
this.velocity.x *= friction
this.velocity.y *= friction
// 更新DOM
this.updateContainerTransform()
// 继续动画或停止
if (
Math.abs(this.velocity.x) > minVelocity ||
Math.abs(this.velocity.y) > minVelocity
) {
requestAnimationFrame(animateInertia)
}
}
requestAnimationFrame(animateInertia)
},
// 更新容器变换 - 现在使用响应式样式,这个方法可以简化
updateContainerTransform() {
// 使用响应式计算属性无需手动更新DOM
console.log('容器变换更新', this.containerTransform, this.size)
},
// 重置缩放和位置到默认状态
resetZoom() {
this.size = 0.4
this.initialSize = 0.4
this.containerTransform = { x: 0, y: 0 }
this.updateContainerTransform()
},
// 双击重置缩放和位置
handleDoubleClick() {
this.resetZoom()
},
clickNode(e) {
return
},
stagePickerHandleConfirm(e) {
this.queryParams.memberSettlePeriodId = e.value[0].value
this.stageName = e.value[0].label
this.stageListVisible = false
},
statusPickerHandleConfirm(e) {
this.queryParams.stageStatus = e.value[0].value
this.statusName = e.value[0].label
this.statusListVisible = false
},
childNodePickerHandleConfirm(e) {
this.queryParams.childNode = e.value[0].childNode
this.selectedChildNodeName = e.value[0].childNode
this.childNodeListVisible = false
// 选择子点位后自动获取数据
this.getDataList()
},
getChildList() {
return new Promise((resolve, reject) => {
arc.getChildList(this.queryParams).then(res => {
if (res.code === 200) {
this.childNodeList = res.data
// 格式化为picker需要的数据格式
this.childNodeColumns = [res.data]
this.queryParams.childNode = res.data[0]?.childNode
this.selectedChildNodeName = res.data[0]?.childNode
resolve()
}
})
})
},
getDataList() {
arc.getAzFramework(this.queryParams).then(res => {
this.data = res.data[0]
})
},
handleSearch() {
this.getChildList().then(() => {
if (this.childNodeList.length > 0) {
this.queryParams.childNode = this.childNodeList[0].childNode
this.getDataList()
} else {
uni.showToast({
title: '当前筛选条件无数据',
icon: 'none',
})
this.data = {}
}
})
},
clearAll() {
this.popShow = false
this.init()
this.queryParams = {
stage: this.stageList[0][0].value,
stageStatus: this.statusList[0][0].value,
}
// this.handleSearch()
},
},
}
2025-03-23 09:29:40 +08:00
</script>
<style lang="scss" scoped>
.ispop_box {
padding: 27rpx;
padding-top: 60rpx;
width: 670rpx;
font-size: 24rpx;
color: #333333;
.pop_top {
display: flex;
justify-content: space-between;
align-items: center;
.poster1 {
height: 88rpx;
width: 88rpx;
}
.poster2 {
width: 88rpx;
height: 54rpx;
border-radius: 10rpx;
margin-left: 20rpx;
}
.top_right {
display: flex;
flex-direction: column;
align-items: center;
}
}
.pop_center {
.center_flex {
display: flex;
align-items: center;
margin-top: 20rpx;
.c1 {
width: 20%;
}
.c2 {
width: 80%;
display: flex;
justify-content: center;
align-items: center;
background: #f3f3f3;
border: 2rpx solid #eeeeee;
border-radius: 8rpx;
padding: 10rpx 0;
}
}
}
.pop_bottom {
margin: 20rpx 0;
border-top: 2rpx solid #eeeeee;
border-top: 2rpx solid #eeeeee;
.b_flex {
display: flex;
.bt1 {
display: flex;
justify-content: center;
align-items: center;
margin-top: 22rpx;
flex: 1;
}
.bt2 {
display: flex;
justify-content: center;
align-items: center;
margin-top: 22rpx;
flex: 1;
background: #f3f3f3;
border: 2rpx solid #eeeeee;
border-radius: 8rpx;
padding: 12rpx 0;
margin: 11rpx 18rpx;
}
}
}
.btn_box {
display: flex;
align-items: center;
justify-content: space-between;
.small-btn {
width: 312rpx;
height: 72rpx;
background: #005bac;
border-radius: 34rpx;
font-size: 28rpx;
font-weight: 400;
color: #ffffff;
display: flex;
justify-content: center;
align-items: center;
}
.small-text-btn {
width: 312rpx;
height: 72rpx;
border: 1px solid #005bac;
border-radius: 34rpx;
display: flex;
justify-content: center;
align-items: center;
font-size: 28rpx;
color: #005bac;
}
}
}
.operate-btnboxs {
padding: 29rpx 26rpx;
background-color: #ffffff;
.d-c-c {
display: flex;
align-items: center;
justify-content: center;
.operate-btn {
width: 64rpx;
height: 64rpx;
margin: 5rpx;
// background: #F2F2F2;
border-radius: 5rpx;
padding: 0;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
}
}
}
.rightPopup {
width: 645rpx;
height: 100%;
overflow: auto;
.popup_top {
padding: 25rpx;
background-color: rgba(176, 196, 222, 0.45);
display: flex;
justify-content: space-between;
align-items: center;
font-size: 28rpx;
font-family: Source Han Sans CN;
font-weight: 400;
color: #333333;
.top_red {
color: #005bac;
}
}
.popup_bottom {
display: flex;
align-items: center;
position: absolute;
left: 0;
bottom: 0;
width: 100%;
.bottom_btn {
flex: 1;
padding: 20rpx 0;
text-align: center;
font-size: 28rpx;
}
.thebtn1 {
background-color: rgba(176, 196, 222, 0.45);
color: #333333;
}
.thebtn2 {
background-color: #005bac;
color: #ffffff;
}
}
.typesBox {
margin-top: 40rpx;
.typeTitle {
padding: 0 24rpx;
font-size: 30rpx;
font-family: Source Han Sans CN;
font-weight: bold;
color: #333333;
}
.choiceBox {
padding: 0 12rpx;
display: flex;
margin-top: 17rpx;
align-items: center;
flex-wrap: wrap;
.inputbox {
font-size: 26rpx;
width: 100%;
padding: 20rpx 32rpx;
border-radius: 20rpx;
background-color: rgba(176, 196, 222, 0.45);
display: flex;
justify-content: space-between;
align-items: center;
}
.flex_btn {
background-color: rgba(176, 196, 222, 0.45);
display: flex;
align-items: center;
justify-content: center;
padding: 14rpx 48rpx;
border-radius: 26rpx;
font-size: 24rpx;
font-family: Source Han Sans CN;
font-weight: 400;
color: #333333;
margin: 17rpx 5rpx;
}
.timesbtn {
flex: 1;
}
.selectbtn {
background-color: #005bac;
color: #ffffff;
}
}
}
}
.seach {
background: #fff;
overflow: hidden;
padding: 20rpx 23rpx;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
border-bottom: 2rpx solid #eee;
.these {
border: none;
padding: 10rpx 0 !important;
}
.seach_i {
padding: 0 20rpx;
border-radius: 34rpx;
background: #fff;
flex: 1;
background: #f5f6f8;
margin: 0 20rpx;
.inputbox {
font-size: 26rpx;
width: 100%;
padding: 15rpx 20rpx;
border-radius: 34rpx;
background-color: #f5f6f8;
display: flex;
justify-content: space-between;
align-items: center;
color: #333333;
&:active {
background-color: #e8e9eb;
}
}
}
.neibox {
display: flex;
align-items: center;
font-size: 26rpx;
font-family: PingFang SC;
font-weight: 400;
color: #999999;
}
.seatch_r {
background: #005bac;
border-radius: 50%;
padding: 8rpx;
}
}
.mainbox {
padding: 26rpx 22rpx;
height: 100%;
.main_bottom {
width: 100%;
height: 100%;
margin-top: 25rpx;
background-color: #ffffff;
padding: 38rpx 0;
border-radius: 20rpx;
}
.scoll_main {
width: 700rpx;
transform-origin: center center; // 设置变换原点为中心
cursor: grab; // 显示可拖动光标
&:active {
cursor: grabbing; // 拖动时的光标
}
}
}
</style>