Files
2025-11-19 13:02:40 +08:00

391 lines
15 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="expected-station">
<view class="sex-search" v-if="search">
<uni-icons class="iconsearch" type="search" size="20"></uni-icons>
<input class="uni-input searchinput" confirm-type="search" />
</view>
<view class="sex-content">
<scroll-view ref="leftScroll" :show-scrollbar="false" :scroll-y="true" class="sex-content-left">
<view
v-for="item in copyTree"
:key="item.id"
class="left-list-btn"
:class="{ 'left-list-btned': item.id === leftValue.id }"
@click="changeStationLog(item)"
>
{{ item.label }}
<!-- <view class="positionNum" v-show="item.checkednumber">
{{ item.checkednumber }}
</view> -->
</view>
</scroll-view>
<scroll-view
:show-scrollbar="false"
:scroll-top="scrollTop"
@scroll="scrollTopBack"
:scroll-y="true"
class="sex-content-right"
>
<view v-for="item in rightValue" :key="item.id">
<view class="secondary-title">{{ item.label }}</view>
<view class="grid-sex">
<view
v-for="item in item.children"
:key="item.id"
:class="{ 'sex-right-btned': item.checked }"
class="sex-right-btn"
@click="addItem(item)"
>
{{ item.label }}
</view>
<!-- <view class="sex-right-btn sex-right-btned" @click="addItem()">客户经理</view>
<view class="sex-right-btn" @click="addItem()">客户经理</view> -->
</view>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
export default {
name: 'expected-station',
data() {
return {
leftValue: {},
rightValue: [],
stationCateLog: 0,
copyTree: [],
scrollTop: 0,
scrollTimer: null,
lastScrollTime: 0,
};
},
props: {
station: {
type: Array,
default: [],
},
search: {
type: Boolean,
default: true,
},
max: {
type: Number,
default: 5,
},
},
created() {
this.copyTree = this.station;
if (this.copyTree.length) {
this.leftValue = this.copyTree[0];
this.rightValue = this.copyTree[0].children;
}
},
watch: {
station(newVal) {
this.copyTree = this.station;
if (this.copyTree.length) {
this.leftValue = this.copyTree[0];
this.rightValue = this.copyTree[0].children;
}
},
},
beforeDestroy() {
// 组件销毁前清除定时器
if (this.scrollTimer) {
clearTimeout(this.scrollTimer);
this.scrollTimer = null;
}
},
methods: {
changeStationLog(item) {
this.leftValue = item;
this.rightValue = item.children;
this.scrollTop = 0;
// 使用更激进的防抖策略,避免频繁的左侧滚动
if (this.scrollTimer) {
clearTimeout(this.scrollTimer);
}
this.scrollTimer = setTimeout(() => {
// 确保左侧列表滚动到正确位置,让当前选中的标签可见
const index = this.copyTree.findIndex(i => i.id === item.id);
if (index !== -1 && this.$refs.leftScroll) {
const itemHeight = 160; // 每个选项高度
const visibleHeight = 4 * itemHeight; // 可见区域高度
// 计算目标位置
const targetTop = index * itemHeight;
// 获取当前左侧列表的滚动位置
const currentLeftScrollTop = this.$refs.leftScroll.scrollTop || 0;
// 只有当目标位置不在当前可见区域内时才滚动
if (targetTop < currentLeftScrollTop || targetTop > currentLeftScrollTop + visibleHeight - itemHeight) {
let targetScrollTop = targetTop;
// 如果选中的标签在底部,确保它不会滚动出可见区域
if (targetTop > this.$refs.leftScroll.scrollHeight - visibleHeight) {
targetScrollTop = Math.max(0, this.$refs.leftScroll.scrollHeight - visibleHeight);
}
this.$refs.leftScroll.scrollTo({
top: targetScrollTop,
duration: 100 // 进一步减少滚动动画时间
});
}
}
}, 100);
},
scrollTopBack(e) {
const currentTime = Date.now();
// 更严格的防抖处理:如果距离上次滚动时间太短,则跳过处理
if (currentTime - this.lastScrollTime < 80) {
return;
}
this.lastScrollTime = currentTime;
// 清除之前的定时器
if (this.scrollTimer) {
clearTimeout(this.scrollTimer);
}
// 设置新的定时器,延迟处理滚动事件
this.scrollTimer = setTimeout(() => {
this.scrollTop = e.detail.scrollTop;
// 滚动时自动选中对应的左侧标签
const scrollTop = e.detail.scrollTop;
let currentSection = null;
let minDistance = Infinity;
// 遍历所有右侧的二级标题,找到当前最接近顶部的那个
this.rightValue.forEach((section, index) => {
// 更精确地估算每个section的位置考虑标题高度和内容高度
const sectionTop = index * 280; // 增加估算高度,避免过于敏感
const distance = Math.abs(sectionTop - scrollTop);
if (distance < minDistance) {
minDistance = distance;
currentSection = section;
}
});
// 只有当距离足够近时才切换左侧标签,避免过于敏感
if (currentSection && minDistance < 100 && this.leftValue.id !== currentSection.parentId) {
const parentItem = this.copyTree.find(item => item.id === currentSection.parentId);
if (parentItem) {
this.leftValue = parentItem;
// 使用更激进的防抖策略,避免频繁的左侧滚动
if (this.scrollTimer) {
clearTimeout(this.scrollTimer);
}
this.scrollTimer = setTimeout(() => {
// 只在必要时滚动左侧列表,避免频繁滚动导致的抖动
const index = this.copyTree.findIndex(i => i.id === parentItem.id);
if (index !== -1 && this.$refs.leftScroll) {
// 获取当前左侧列表的滚动位置
const currentLeftScrollTop = this.$refs.leftScroll.scrollTop || 0;
const itemHeight = 160; // 每个选项高度
const visibleHeight = 4 * itemHeight; // 可见区域高度
// 计算目标位置
const targetTop = index * itemHeight;
// 只有当目标位置不在当前可见区域内时才滚动
if (targetTop < currentLeftScrollTop || targetTop > currentLeftScrollTop + visibleHeight - itemHeight) {
let targetScrollTop = targetTop;
// 如果选中的标签在底部,确保它不会滚动出可见区域
if (targetTop > this.$refs.leftScroll.scrollHeight - visibleHeight) {
targetScrollTop = Math.max(0, this.$refs.leftScroll.scrollHeight - visibleHeight);
}
this.$refs.leftScroll.scrollTo({
top: targetScrollTop,
duration: 100 // 进一步减少滚动动画时间
});
}
}
}, 150); // 增加延迟,避免滚动冲突
}
}
}, 80); // 增加防抖延迟到80ms
},
addItem(item) {
let titiles = [];
let labels = [];
let count = 0;
// 先统计已选中的职位数量
for (const firstLayer of this.copyTree) {
for (const secondLayer of firstLayer.children) {
for (const thirdLayer of secondLayer.children) {
if (thirdLayer.checked) {
count++;
}
}
}
}
for (const firstLayer of this.copyTree) {
firstLayer.checkednumber = 0; // 初始化当前层级的 checked 计数
for (const secondLayer of firstLayer.children) {
for (const thirdLayer of secondLayer.children) {
// **如果是当前点击的职位**
if (thirdLayer.id === item.id) {
if (!thirdLayer.checked && count >= 5) {
// 如果已经选了 5 个,并且点击的是未选中的职位,则禁止选择
uni.showToast({
title: `最多选择5个职位`,
icon: 'none',
});
continue; // 跳过后续逻辑,继续循环
}
// 切换选中状态
thirdLayer.checked = !thirdLayer.checked;
}
// 统计被选中的第三层节点
if (thirdLayer.checked) {
titiles.push(`${thirdLayer.id}`);
labels.push(`${thirdLayer.label}`);
firstLayer.checkednumber++; // 累加计数器
}
}
}
}
titiles = titiles.join(',');
labels = labels.join(',');
this.$emit('onChange', {
ids: titiles,
labels,
});
},
},
};
</script>
<style lang="stylus" scoped>
.secondary-title{
color: #333333
font-size: 28rpx
padding: 40rpx 0 10rpx 30rpx;
}
.expected-station{
width: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
height: 100%
}
.sex-search
width: calc(100% - 28rpx - 28rpx);
padding: 10rpx 28rpx;
display: grid;
// grid-template-columns: 50rpx auto;
position: relative;
.iconsearch
position: absolute;
left: 40rpx;
top: 20rpx;
.searchinput
border-radius: 10rpx;
background: #FFFFFF;
padding: 10rpx 0 10rpx 58rpx;
.sex-content
background: #FFFFFF;
// border-radius: 20rpx;
width: 100%;
margin-top: 20rpx;
display: flex;
border-bottom: 2px solid #D9D9D9;
overflow: hidden;
height: 100%;
.sex-content-left
width: 198rpx;
padding: 20rpx 0 0 0;
/* 添加硬件加速优化 */
transform: translateZ(0);
-webkit-transform: translateZ(0);
will-change: transform;
.left-list-btn
padding: 0 40rpx 0 24rpx;
display: grid;
place-items: center;
height: 100rpx;
text-align: center;
color: #606060;
font-size: 28rpx;
position: relative
margin-top: 60rpx
/* 优化渲染性能 */
transform: translateZ(0);
-webkit-transform: translateZ(0);
.left-list-btn:first-child
margin-top: 0
// .positionNum
// position: absolute
// right: 0
// top: 50%;
// transform: translate(0, -50%)
// color: #FFFFFF
// background: #4778EC
// border-radius: 50%
// width: 36rpx;
// height: 36rpx;
.left-list-btned
color: #4778EC;
position: relative;
// .left-list-btned::after
// position: absolute;
// left: 20rpx;
// content: '';
// width: 7rpx;
// height: 38rpx;
// background: #4778EC;
// border-radius: 0rpx 0rpx 0rpx 0rpx;
.sex-content-right
// border-left: 2px solid #D9D9D9;
background: #F6F6F6;
flex: 1;
/* 添加硬件加速优化 */
transform: translateZ(0);
-webkit-transform: translateZ(0);
will-change: transform;
.grid-sex
display: grid;
grid-template-columns: 50% 50%;
place-items: center;
padding: 0 20rpx 40rpx 20rpx;
.sex-right-btn
width: 228rpx;
height: 80rpx;
font-size: 28rpx;
line-height: 41rpx;
text-align: center;
display: grid;
place-items: center;
border-radius: 12rpx;
margin-top: 30rpx;
background: #E8EAEE;
color: #606060;
/* 优化渲染性能 */
transform: translateZ(0);
-webkit-transform: translateZ(0);
.sex-right-btned
font-weight: 500
width: 224rpx;
height: 76rpx;
background: rgba(37,107,250,0.06);
border: 2rpx solid #256BFA;
color: #256BFA
/* 优化渲染性能 */
transform: translateZ(0);
-webkit-transform: translateZ(0);
</style>