Files
MER-2.2_2601/mer_uniapp/components/locationDisplay/index.vue
2026-03-08 20:07:52 +08:00

486 lines
13 KiB
Vue
Raw 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="location-display" :class="{ 'scroll ': isScrolled }" @click="handleLocationClick">
<text v-show="!positioningType" class="iconfont icon-ic_location1"></text>
<view class="location-text">
<!-- 小程序使用CSS动画类 -->
<!-- #ifdef MP -->
<text class="text-content mp-text" :class="animationClass" :style="{transform: `translateX(${textOffset}px)`}">{{ displayText }}</text>
<!-- #endif -->
<!-- 其他平台使用JS动画 -->
<!-- #ifndef MP -->
<text class="text-content" :style="{transform: `translateX(${textOffset}px)`}">{{ displayText }}</text>
<!-- #endif -->
</view>
<text v-show="positioningType" class="iconfont icon-ic_downarrow ml-4"></text>
</view>
</template>
<script>
export default {
name: 'LocationDisplay',
props: {
// 显示的位置文字
text: {
type: String,
default: '定位中...'
},
// 最大显示宽度
maxWidth: {
type: String,
default: '118rpx'
},
// 最大高度
maxHeight: {
type: String,
default: '42rpx'
},
// 是否处于滚动状态 外界用于时候更换颜色
isScrolled: {
type: Boolean,
default: false
},
// 是否启用文字滚动功能
enableScroll: {
type: Boolean,
default: true
},
positioningType:{
type: Boolean,
default: false
}
},
data() {
return {
textOffset: 0, // 文字偏移量
scrollTimer: null, // 滚动定时器
isScrolling: false, // 是否正在滚动
// 小程序CSS动画状态
animationClass: '', // 动画类名
maxDistance: 0, // 最大滚动距离
currentLocationName: '', // 当前位置名称
storageTimer: null, // 位置监听定时器
isDestroyed: false // 组件销毁标记
}
},
computed: {
displayText() {
// 如果外部传入了text优先使用外部text
if (this.text && this.text !== '定位中...') {
return this.text;
}
if(this.currentLocationName){
return this.currentLocationName
} else {
// 否则使用内部管理的位置数据
return this.positioningType ? '选择圈层' : '选择位置';
}
}
},
mounted() {
// 只有在没有外部传入text时才启用内部位置管理
if (!this.text || this.text === '定位中...') {
this.initLocationData();
this.setupStorageListener();
}
// 启动滚动
this.resetAndStartScroll();
},
created() {
// LocationDisplay 组件初始化
},
watch: {
text() {
this.resetAndStartScroll();
},
displayText() {
this.resetAndStartScroll();
},
enableScroll() {
if (!this.enableScroll) {
this.stopScroll();
this.textOffset = 0;
} else {
this.resetAndStartScroll();
}
}
},
beforeDestroy() {
this.isDestroyed = true;
this.stopScroll();
this.stopLocationListener();
},
methods: {
// 重置并开始滚动 - 临时停用
resetAndStartScroll() {
this.stopScroll();
this.textOffset = 0;
this.isScrolling = false;
// 临时停用滚动功能
return;
if (!this.enableScroll) return;
this.$nextTick(() => {
this.startSimpleScroll();
});
},
// 完整的双向滚动逻辑
startSimpleScroll() {
if (this.isScrolling) return;
// 先计算需要滚动的距离
this.$nextTick(() => {
this.calculateScrollDistance();
});
},
// 计算滚动距离
calculateScrollDistance() {
const query = uni.createSelectorQuery().in(this);
query.select('.text-content').boundingClientRect();
query.select('.location-text').boundingClientRect();
query.exec((res) => {
if (res[0] && res[1]) {
const textWidth = res[0].width; // 文字总宽度
const containerWidth = res[1].width; // 容器宽度
let overflowWidth = textWidth - containerWidth; // 溢出宽度
// 重新计算正确的滚动距离
// 目标:让文字末尾刚好显示在容器右边缘,而不是让文字滚动消失
// 正确公式:滚动距离 = 文字宽度 - 容器宽度 (这样文字末尾刚好到达容器右边)
// 但实际上我们不需要滚动这么远,只需要让末尾文字可见即可
// 小程序保守策略:确保文字末尾能显示,但保留开头部分
// #ifdef MP
// 策略:滚动距离 = 溢出宽度 - 10px这样文字末尾显示开头也保留10px可见
overflowWidth = Math.max(0, overflowWidth - 10);
// #endif
if (overflowWidth > 0) {
// 需要滚动,开始双向滚动
this.startTwoWayScroll(overflowWidth);
} else {
// console.log('LocationDisplay无需滚动');
}
}
});
},
// 双向滚动:先向左显示末尾,再向右回到开头
startTwoWayScroll(maxDistance) {
// 延迟1秒开始第一阶段滚动
setTimeout(() => {
if (this.isDestroyed || !this.enableScroll) return;
this.isScrolling = true;
this.textOffset = 0;
// 第一阶段:向左滚动显示文字末尾
this.scrollToEnd(maxDistance);
}, 1000);
},
// 第一阶段:向左滚动到末尾
scrollToEnd(maxDistance) {
// 小程序优化:降低频率,增加步进
// #ifdef MP
const scrollStep = 1; // 小程序用更大步进
const scrollInterval = 30; // 小程序用更长间隔
// #endif
// H5保持丝滑
// #ifdef H5
const scrollStep = 0.5;
const scrollInterval = 20;
// #endif
// APP采用中等参数
// #ifdef APP-PLUS
const scrollStep = 0.8;
const scrollInterval = 25;
// #endif
this.scrollTimer = setInterval(() => {
// 小程序模式:每次更新前检查是否会超出边界
// #ifdef MP
const nextOffset = this.textOffset - scrollStep;
if (nextOffset < -maxDistance) {
// 直接定位到目标位置,不再继续滚动
this.textOffset = -maxDistance;
clearInterval(this.scrollTimer);
setTimeout(() => {
this.scrollToStart();
}, 2000);
return;
}
this.textOffset = nextOffset;
// #endif
// H5和APP正常滚动
// #ifndef MP
this.textOffset -= scrollStep;
// 滚动到末尾位置(文字末尾刚好显示完整)
if (this.textOffset <= -maxDistance) {
this.textOffset = -maxDistance; // 精确定位
clearInterval(this.scrollTimer);
// 停留2秒后开始第二阶段
setTimeout(() => {
this.scrollToStart();
}, 2000);
}
// #endif
}, scrollInterval);
},
// 第二阶段:向右滚动回到开头
scrollToStart() {
// 小程序优化:使用相同的平台参数
// #ifdef MP
const scrollStep = 1;
const scrollInterval = 30;
// #endif
// H5保持丝滑
// #ifdef H5
const scrollStep = 0.5;
const scrollInterval = 20;
// #endif
// APP采用中等参数
// #ifdef APP-PLUS
const scrollStep = 0.8;
const scrollInterval = 25;
// #endif
this.scrollTimer = setInterval(() => {
// 小程序模式:每次更新前检查是否会超出边界
// #ifdef MP
const nextOffset = this.textOffset + scrollStep;
if (nextOffset > 0) {
// 直接定位到起始位置,不再继续滚动
this.textOffset = 0;
this.stopScroll();
return;
}
this.textOffset = nextOffset;
// #endif
// H5和APP正常滚动
// #ifndef MP
this.textOffset += scrollStep;
// 回到初始位置
if (this.textOffset >= 0) {
this.textOffset = 0; // 精确定位到起始位置
this.stopScroll();
}
// #endif
}, scrollInterval);
},
// 停止滚动
stopScroll() {
if (this.scrollTimer) {
clearInterval(this.scrollTimer);
this.scrollTimer = null;
}
this.isScrolling = false;
},
// 点击组件圈层
handleLocationClick() {
this.$emit('click');
},
// ============ 位置管理相关方法 ============
// 从缓存获取位置数据 - 以location_info为准
initLocationData() {
if (this.isDestroyed) return;
try {
// 优先使用location_info用户最新的主动定位选择
const locationInfo = uni.getStorageSync('location_info');
if (locationInfo && (locationInfo.name || locationInfo.address) && !this.positioningType) {
this.currentLocationName = locationInfo.name || locationInfo.address;
return;
}
// 备选使用areas_info圈层选择的位置
const areasInfo = uni.getStorageSync('areas_info');
if (areasInfo && Array.isArray(areasInfo) && areasInfo.length > 0 && this.positioningType) {
// 显示最新选择的圈层
const latestArea = areasInfo[0];
this.currentLocationName = latestArea.name || latestArea.address || '选择圈层';
return;
}
// 默认显示
this.currentLocationName = this.positioningType ? '选择圈层' : '选择位置';
} catch (error) {
this.currentLocationName = this.positioningType ? '选择圈层' : '选择位置';
}
},
// 设置存储监听
setupStorageListener() {
if (this.isDestroyed) return;
// 使用定时器定期检查缓存变化
this.storageTimer = setInterval(() => {
if (!this.isDestroyed) {
this.checkLocationUpdate();
}
}, 1000); // 每秒检查一次
},
// 检查位置更新 - 以location_info为准
checkLocationUpdate() {
if (this.isDestroyed) return;
try {
let newLocationName = this.positioningType ? '选择圈层' : '选择位置';
// 优先使用location_info用户最新的主动定位选择
const locationInfo = uni.getStorageSync('location_info');
if (locationInfo && (locationInfo.name || locationInfo.address)&& !this.positioningType) {
newLocationName = locationInfo.name || locationInfo.address;
} else {
// 备选使用areas_info圈层选择的位置
const areasInfo = uni.getStorageSync('areas_info');
if (areasInfo && Array.isArray(areasInfo) && areasInfo.length > 0 && this.positioningType) {
const latestArea = areasInfo[0];
newLocationName = latestArea.name || latestArea.address || '选择圈层';
}
}
// 如果地址发生变化,更新显示
if (newLocationName !== this.currentLocationName) {
this.currentLocationName = newLocationName;
// displayText计算属性会自动更新触发watch重新计算滚动
}
} catch (error) {
// 位置更新失败,忽略
}
},
// 停止位置监听
stopLocationListener() {
if (this.storageTimer) {
clearInterval(this.storageTimer);
this.storageTimer = null;
}
}
}
}
</script>
<style lang="scss" scoped>
.scroll{
.iconfont {
color: #000 !important;
}
.text-content {
color: #000 !important;
}
}
.location-display {
display: flex;
align-items: center;
cursor: pointer;
height: 42rpx;
max-height: 42rpx;
overflow: hidden;
box-sizing: border-box;
position: relative;
flex-shrink: 0;
margin-right: 8rpx;
width: 154rpx;
.iconfont {
font-size: 26rpx;
color: #fff;
margin-right: 2rpx;
flex-shrink: 0;
height: 24rpx;
line-height: 24rpx;
margin-right: 4rpx;
/* #ifdef H5 */
font-size: 24rpx;
/* #endif */
}
.location-text {
line-height: 32px;
overflow: hidden;
position: relative;
}
.text-content {
/* 基础样式 */
height: 18px;
font-family: PingFang SC, PingFang SC;
font-weight: 400;
font-size: 13px;
color: #FFFFFF;
line-height: 18px;
text-align: left;
font-style: normal;
text-transform: none;
display: inline-block;
/* H5平台特殊处理 */
/* #ifdef H5 */
min-width: 100%;
font-size: 12px;
line-height: 42rpx;
height: 42rpx;
display: flex;
/* 禁用CSS transition让JS动画完全控制 */
transition: none !important;
transform: translateZ(0); /* 启用硬件加速 */
will-change: transform; /* 优化渲染性能 */
/* #endif */
/* 小程序/APP平台 */
/* #ifdef MP || APP-PLUS */
width: auto;
min-width: 500rpx;
/* 禁用transition避免小程序抖动 */
transition: none !important;
/* 小程序强制GPU渲染优化 */
transform: translateZ(0) translate3d(0,0,0);
will-change: transform;
backface-visibility: hidden;
/* #endif */
/* 小程序专用CSS动画 */
/* #ifdef MP */
&.mp-text {
/* 基础优化 */
transform: translateZ(0);
will-change: transform;
/* 动画效果暂时禁用继续使用JS控制 */
/* transition: transform 3s ease-in-out; */
}
&.mp-scroll-to-end {
/* 滚动到末尾的动画 - 暂时不用 */
}
&.mp-scroll-to-start {
/* 滚动回起始位置的动画 - 暂时不用 */
}
/* #endif */
}
}
</style>