ソースを参照

feat:修改相册时间轴功能,对接礼物页面规则接口

wangsisi 3 週間 前
コミット
478a1104e7

+ 40 - 37
components/v-search/v-search.vue

@@ -1,24 +1,51 @@
 <template>
 	<view class="v-search">
 		<view class="search-wrap">
-			<up-search placeholder="搜索品种" v-model="searchVal" @clear="clear" @custom="searchFun" @search="searchFun"></up-search>
+			<up-search 
+				placeholder="搜索品种" 
+				v-model="searchVal" 
+				@clear="clear" 
+				@custom="searchFun" 
+				@search="searchFun"
+				:bgColor="'transparent'"
+				:actionStyle="actionStyle"
+				:inputStyle="inputStyle"
+			></up-search>
 		</view>
 	</view>
 </template>
 
-<script setup>
-	import {
-		ref
-	} from "vue"
+<script setup>
+	import {
+		ref,
+		computed
+	} from "vue"
+	
+	const searchVal = ref('')
+	
+	// 通过 props 传递样式
+	const actionStyle = computed(() => ({
+		textAlign: 'center',
+		lineHeight: '52rpx',
+		borderRadius: '40rpx',
+		fontSize: '28rpx',
+		backgroundColor: '#FFD95E',
+		margin: '6rpx 8rpx',
+		width: '112rpx',
+		height: '52rpx'
+	}))
+	
+	const inputStyle = computed(() => ({
+		backgroundColor: 'transparent'
+	}))
+	
+	const searchFun = (e)=>{
+		emit('searchCallback',e)
+	}
+	const clear = ()=>{
+		emit('clearCallback')
+	}
 	
-	const searchVal = ref('')
-	const searchFun = (e)=>{
-		emit('searchCallback',e)
-	}
-	const clear = ()=>{
-		emit('clearCallback')
-	}
-	
 	const emit = defineEmits(['searchCallback','clearCallback'])
 </script>
 
@@ -32,30 +59,6 @@
 
 		.search-wrap {
 			flex: 1;
-
-			::v-deep {
-				.u-search {
-					.u-search__content {
-						background-color: transparent !important;
-
-						.u-search__content__input {
-							background-color: transparent !important;
-
-						}
-					}
-
-					.u-search__action {
-						text-align: center;
-						line-height: 52rpx;
-						border-radius: 40rpx;
-						font-size: 28rpx;
-						background-color: #FFD95E;
-						margin: 6rpx 8rpx;
-						width: 112rpx;
-						height: 52rpx;
-					}
-				}
-			}
 		}
 
 		.search-btn {

+ 2 - 1
pages/login/index.vue

@@ -91,7 +91,8 @@
 					icon: 'none',
 					duration: 2000,
 					mask: true
-				})
+				})
+				uni.setStorageSync('userInfo', userData.value);
 				uni.reLaunch({
 					url: `/pages/tabBar/home/subPages/gardenMap?enterSelectTree=true`
 				});

+ 11 - 9
pages/tabBar/tree/components/blessingsPopup.vue

@@ -34,7 +34,7 @@
 			<view class="sucess-bg">
 				<image v-if="isFriend" class="img" :src="`${config.BASIC_IMG}img/subTreePage/blessing-sucess.png`">
 				</image>
-				<image v-else class="img"
+				<image v-else class="img" :style="{width:clockinType == 4?'90%':'81%'}"
 					:src="`${config.BASIC_IMG}img/subTreePage/blessing-sucess-${clockinType}.png`"></image>
 			</view>
 			<view class="button-group">
@@ -121,14 +121,16 @@
 			farmBuyId: props.farmBuyId,
 			clockinType: props.clockinType,
 			msg: message.value,
-		};
-		TREE.clockin(params).then((res) => {
-			if (res.code === 0) {
-				handleCancel();
-				sucessPopup.value = true;
-				emit('clockinCallback')
-			}
-		});
+		};
+		if(props.clockinType!=4){
+			TREE.clockin(params).then((res) => {
+				if (res.code === 0) {
+					handleCancel();
+					sucessPopup.value = true;
+					emit('clockinCallback')
+				}
+			});
+		}
 	};
 
 	onMounted(() => {

+ 753 - 0
pages/tabBar/tree/components/timeScale.vue

@@ -0,0 +1,753 @@
+<!--
+时间刻度尺组件 (TimeScale)
+
+功能:
+- 显示水平时间轴,包含月份标记和小刻度
+- 支持用户滑动选择时间
+- 每两天显示一个小刻度
+- 中间固定竖线,显示当前选中的时间
+- 时间格式:YYYY-MM-DD
+
+Props:
+- currentDate: String - 当前选中的时间 (YYYY-MM-DD格式)
+
+Events:
+- timeChange: (date: String) - 时间变化时触发,返回YYYY-MM-DD格式的日期
+
+使用示例:
+<time-scale 
+    :current-date="selectedDate" 
+    @time-change="handleTimeChange"
+></time-scale>
+-->
+
+<template>
+	<view class="time-scale-container">
+		<view class="time-scale-scroll">
+			<scroll-view 
+				class="time-scale-wrapper" 
+				scroll-x 
+				:scroll-left="scrollLeft"
+				scroll-with-animation
+				@scroll="handleScroll"
+				@scrolltolower="handleScrollToLower"
+				@scrolltoupper="handleScrollToUpper"
+				ref="scrollViewRef"
+			>
+				<view class="time-scale">
+					<!-- 月份标签 -->
+					<view class="month-labels">
+						<view 
+							class="month-label" 
+							v-for="(month, index) in monthLabels" 
+							:key="`month-${index}`"
+							:style="{ left: month.left + 'rpx' }"
+						>
+							{{ month.label }}
+						</view>
+					</view>
+					
+					<!-- 时间轴 -->
+					<view class="timeline">
+						<!-- 月份大点 -->
+						<view 
+							class="month-dot" 
+							v-for="(month, index) in monthLabels" 
+							:key="`dot-${index}`"
+							:style="{ left: month.left + 'rpx' }"
+						></view>
+						
+						<!-- 小刻度 -->
+						<view 
+							class="tick-mark" 
+							v-for="(tick, index) in tickMarks" 
+							:key="`tick-${index}`"
+							:style="{ left: tick.left + 'rpx' }"
+						></view>
+					</view>
+					
+
+				</view>
+			</scroll-view>
+			
+			<!-- 中间竖线 -->
+			<view class="center-line"></view>
+		</view>
+		
+		<!-- 当前时间显示 -->
+		<view class="current-time" v-if="currentTime">
+			{{ formatDisplayTime(currentTime) }}
+		</view>
+	</view>
+</template>
+
+<script setup>
+import { ref, computed, onMounted, watch, nextTick } from 'vue'
+
+const props = defineProps({
+	// 当前选中的时间
+	currentDate: {
+		type: String,
+		default: ''
+	}
+})
+
+const emit = defineEmits(['timeChange'])
+
+// 响应式数据
+const currentTime = ref('')
+let scrollTimer = null // 防抖定时器
+const scrollViewRef = ref(null) // 滚动视图引用
+const scrollLeft = ref(0) // 滚动位置
+
+// 计算属性:生成月份标签
+const monthLabels = computed(() => {
+	const labels = []
+	const now = new Date()
+	const currentYear = now.getFullYear()
+	const currentMonth = now.getMonth()
+	
+	// 从当前月份往前一年的月份,从右到左排列
+	for (let i = 0; i <= 11; i++) {
+		const date = new Date(currentYear, currentMonth - i, 1)
+		const year = date.getFullYear()
+		const month = date.getMonth() + 1
+		
+		labels.push({
+			label: `${month}月`,
+			year: year,
+			month: month,
+			left: (11 - i) * 200 + 100, // 从右到左排列,当前月份在最右边
+			monthOffset: i // 添加月份偏移信息
+		})
+	}
+	
+	return labels
+})
+
+// 计算属性:生成小刻度
+const tickMarks = computed(() => {
+	const ticks = []
+	const now = new Date()
+	const currentYear = now.getFullYear()
+	const currentMonth = now.getMonth()
+	
+	// 从当前月份往前一年的刻度,从右到左排列
+	for (let monthOffset = 0; monthOffset <= 11; monthOffset++) {
+		const date = new Date(currentYear, currentMonth - monthOffset, 1)
+		const year = date.getFullYear()
+		const month = date.getMonth() + 1
+		const daysInMonth = new Date(year, month, 0).getDate()
+		
+		// 每隔一天一个刻度,从第2天开始
+		for (let day = 2; day <= daysInMonth; day++) {
+			const monthLeft = (11 - monthOffset) * 200 + 100
+			const dayOffset = ((day - 1) / daysInMonth) * 200
+			ticks.push({
+				left: monthLeft + dayOffset,
+				date: `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`
+			})
+		}
+	}
+	
+	return ticks
+})
+
+// 格式化显示时间
+const formatDisplayTime = (dateStr) => {
+	const [year, month, day] = dateStr.split('-')
+	return `${year}年${month}月${day}日`
+}
+
+// 根据时间计算位置
+const calculatePositionByDate = (dateStr) => {
+	const [year, month, day] = dateStr.split('-').map(Number)
+	const now = new Date()
+	const currentYear = now.getFullYear()
+	const currentMonth = now.getMonth()
+	
+	// 计算月份偏移(从当前月份往前)- 使用与月份标签相同的逻辑
+	// 月份标签: new Date(currentYear, currentMonth - i, 1) 其中 i 从 0 到 11
+	// 所以对于目标日期,我们需要找到对应的 i 值
+	let monthOffset = -1
+	for (let i = 0; i <= 11; i++) {
+		const testDate = new Date(currentYear, currentMonth - i, 1)
+		if (testDate.getFullYear() === year && testDate.getMonth() + 1 === month) {
+			monthOffset = i
+			break
+		}
+	}
+	
+	if (monthOffset === -1 || monthOffset > 11) return 0
+	
+	// 计算月份位置(从右到左)
+	const monthLeft = (11 - monthOffset) * 200 + 100
+	
+	// 计算在月份内的位置,使用与calculateDateByPosition相同的计算逻辑
+	const daysInMonth = new Date(year, month, 0).getDate()
+	const dayRatio = (day - 1) / daysInMonth
+	const dayOffset = dayRatio * 200
+	
+	return monthLeft + dayOffset
+}
+
+// 根据位置计算时间
+const calculateDateByPosition = (left) => {
+	// 计算月份索引(从右到左)
+	const monthIndex = Math.floor((left - 100) / 200)
+	const monthOffset = 11 - monthIndex
+	
+	if (monthOffset < 0 || monthOffset > 11) return ''
+	
+	const now = new Date()
+	const currentYear = now.getFullYear()
+	const currentMonth = now.getMonth()
+	
+	// 计算目标月份 - 使用与月份标签完全相同的逻辑
+	// 月份标签: new Date(currentYear, currentMonth - i, 1) 其中 i 从 0 到 11
+	// 这里 monthOffset 对应月份标签中的 i
+	const targetDate = new Date(currentYear, currentMonth - monthOffset, 1)
+	const year = targetDate.getFullYear()
+	const month = targetDate.getMonth() + 1
+	const daysInMonth = new Date(year, month, 0).getDate()
+	
+	// 计算在月份内的位置
+	const monthLeft = monthIndex * 200 + 100
+	const dayOffset = left - monthLeft
+	
+	// 确保dayOffset在有效范围内
+	if (dayOffset < 0 || dayOffset > 200) return ''
+	
+	// 计算日期比例(0-1),使用更精确的计算方法
+	const dayRatio = dayOffset / 200
+	
+	// 计算日期(1-31),使用四舍五入而不是向下取整
+	let day = Math.round(dayRatio * daysInMonth) + 1
+	
+	// 确保日期在有效范围内
+	day = Math.max(1, Math.min(daysInMonth, day))
+	
+	return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`
+}
+
+// 调试函数:验证时间计算
+const debugTimeCalculation = (left) => {
+	const date = calculateDateByPosition(left)
+	const position = calculatePositionByDate(date)
+	const difference = Math.abs(left - position)
+	console.log(`位置: ${left}, 计算时间: ${date}, 反向计算位置: ${position}, 误差: ${difference}`)
+	return date
+}
+
+// 精确调试函数:测试特定位置
+const preciseDebug = (left) => {
+	const date = calculateDateByPosition(left)
+	const [year, month, day] = date.split('-').map(Number)
+	const monthIndex = Math.floor((left - 100) / 200)
+	const monthOffset = 11 - monthIndex
+	const monthLeft = monthIndex * 200 + 100
+	const dayOffset = left - monthLeft
+	const dayRatio = dayOffset / 200
+	const daysInMonth = new Date(year, month, 0).getDate()
+	
+	console.log(`=== 精确调试 ===`)
+	console.log(`输入位置: ${left}`)
+	console.log(`月份索引: ${monthIndex}, 月份偏移: ${monthOffset}`)
+	console.log(`月份左边界: ${monthLeft}, 日期偏移: ${dayOffset}`)
+	console.log(`日期比例: ${dayRatio}, 月份天数: ${daysInMonth}`)
+	console.log(`计算日期: ${day}, 最终时间: ${date}`)
+	
+	// 添加反向验证
+	const reversePosition = calculatePositionByDate(date)
+	const reverseDate = calculateDateByPosition(reversePosition)
+	console.log(`反向验证: 位置 ${reversePosition} -> 时间 ${reverseDate}`)
+	console.log(`一致性检查: ${date === reverseDate ? '✅' : '❌'}`)
+	
+	return date
+}
+
+// 时间差分析函数
+const analyzeTimeDifference = (left) => {
+	console.log(`=== 时间差分析 ===`)
+	console.log(`分析位置: ${left}`)
+	
+	// 计算当前时间
+	const date = calculateDateByPosition(left)
+	console.log(`计算时间: ${date}`)
+	
+	// 计算反向位置
+	const reversePosition = calculatePositionByDate(date)
+	console.log(`反向位置: ${reversePosition}`)
+	
+	// 计算位置差
+	const positionDiff = Math.abs(left - reversePosition)
+	console.log(`位置差: ${positionDiff}`)
+	
+	// 分析可能的原因
+	if (positionDiff > 5) {
+		console.log(`⚠️ 位置差较大,可能原因:`)
+		console.log(`1. 日期计算精度问题`)
+		console.log(`2. 月份边界处理问题`)
+		console.log(`3. 四舍五入误差`)
+	}
+	
+	return { date, reversePosition, positionDiff }
+}
+
+// 处理滚动事件
+const handleScroll = (e) => {
+	// 清除之前的定时器
+	if (scrollTimer) {
+		clearTimeout(scrollTimer)
+	}
+	
+	// 设置新的定时器,防抖处理
+	scrollTimer = setTimeout(() => {
+		const scrollLeft = e.detail.scrollLeft
+		const containerWidth = 750 // 假设容器宽度为750rpx
+		const centerPosition = containerWidth / 2
+		
+		// 计算当前中心位置对应的时间
+		const absoluteLeft = scrollLeft + centerPosition
+		const date = calculateDateByPosition(absoluteLeft)
+		
+		if (date && date !== currentTime.value) {
+			currentTime.value = date
+			emit('timeChange', date)
+			console.log('当前选中时间:', date) // 打印格式为YYYY-MM-DD
+			
+			// 详细调试信息
+			console.log(`滚动位置: ${scrollLeft}, 中心位置: ${centerPosition}, 绝对位置: ${absoluteLeft}`)
+			preciseDebug(absoluteLeft)
+			
+			// 时间差分析
+			analyzeTimeDifference(absoluteLeft)
+		}
+	}, 50) // 减少防抖时间,提高响应性
+}
+
+// 处理滚动到底部
+const handleScrollToLower = () => {
+	console.log('滚动到底部')
+}
+
+// 处理滚动到顶部
+const handleScrollToUpper = () => {
+	console.log('滚动到顶部')
+}
+
+// 监听props变化
+watch(() => props.currentDate, (newDate) => {
+	if (newDate && newDate !== currentTime.value) {
+		currentTime.value = newDate
+	}
+}, { immediate: true })
+
+// 验证月份标签和位置计算的对应关系
+const validateMonthPositionMapping = () => {
+	console.log('=== 月份位置映射验证 ===')
+	
+	monthLabels.value.forEach((label, index) => {
+		const expectedDate = `${label.year}-${label.month.toString().padStart(2, '0')}-01`
+		const calculatedPosition = calculatePositionByDate(expectedDate)
+		const reverseDate = calculateDateByPosition(calculatedPosition)
+		
+		console.log(`月份${index + 1}: ${label.label}`)
+		console.log(`  标签位置: ${label.left}`)
+		console.log(`  期望日期: ${expectedDate}`)
+		console.log(`  计算位置: ${calculatedPosition}`)
+		console.log(`  反向日期: ${reverseDate}`)
+		console.log(`  位置一致: ${Math.abs(label.left - calculatedPosition) < 1 ? '✅' : '❌'}`)
+		console.log(`  日期一致: ${expectedDate === reverseDate ? '✅' : '❌'}`)
+		console.log('---')
+	})
+}
+
+// 验证月份标签和位置计算的一致性
+const validateMonthConsistency = () => {
+	console.log('=== 月份一致性验证 ===')
+	monthLabels.value.forEach((label, index) => {
+		const expectedDate = `${label.year}-${label.month.toString().padStart(2, '0')}-01`
+		const calculatedPosition = calculatePositionByDate(expectedDate)
+		const reverseDate = calculateDateByPosition(calculatedPosition)
+		const difference = Math.abs(label.left - calculatedPosition)
+		
+		console.log(`月份${index + 1}: ${label.label} (${expectedDate})`)
+		console.log(`  标签位置: ${label.left}, 计算位置: ${calculatedPosition}, 误差: ${difference}`)
+		console.log(`  反向验证: ${reverseDate} vs ${expectedDate}`)
+		console.log(`  一致性: ${reverseDate === expectedDate ? '✅' : '❌'}`)
+		console.log('---')
+	})
+}
+
+// 验证月份标签位置
+const validateMonthLabels = () => {
+	console.log('=== 月份标签验证 ===')
+	monthLabels.value.forEach((label, index) => {
+		const expectedDate = `${label.year}-${label.month.toString().padStart(2, '0')}-01`
+		const calculatedPosition = calculatePositionByDate(expectedDate)
+		const difference = Math.abs(label.left - calculatedPosition)
+		console.log(`月份${index + 1}: ${label.label} (${expectedDate}) -> 标签位置: ${label.left}, 计算位置: ${calculatedPosition}, 误差: ${difference}`)
+	})
+}
+
+// 测试函数:验证时间计算逻辑
+const testTimeCalculation = () => {
+	console.log('=== 时间计算测试 ===')
+	
+	// 先验证月份位置映射
+	validateMonthPositionMapping()
+	
+	// 再验证月份一致性
+	validateMonthConsistency()
+	
+	// 再验证月份标签
+	validateMonthLabels()
+	
+	// 测试几个关键位置(从右到左)
+	const testPositions = [
+		2300, // 最右边(当前月份第一天)
+		2100, // 当前月份中间
+		1900, // 上个月第一天
+		1700, // 上上个月第一天
+		1500, // 三个月前第一天
+		1300, // 四个月前第一天
+		1100, // 五个月前第一天
+		900,  // 六个月前第一天
+		700,  // 七个月前第一天
+		500,  // 八个月前第一天
+		300,  // 九个月前第一天
+		100   // 最左边(一年前)
+	]
+	
+	testPositions.forEach((pos, index) => {
+		const date = calculateDateByPosition(pos)
+		const reversePos = calculatePositionByDate(date)
+		const difference = Math.abs(pos - reversePos)
+		console.log(`测试${index + 1}: 位置 ${pos} -> 时间 ${date} -> 反向位置 ${reversePos}, 误差: ${difference}`)
+		
+		// 如果误差太大,输出详细调试信息
+		if (difference > 10) {
+			console.log('--- 误差过大,详细调试 ---')
+			preciseDebug(pos)
+		}
+	})
+}
+
+// 显示月份对比信息
+const showMonthComparison = () => {
+	console.log('=== 月份对比信息 ===')
+	
+	const now = new Date()
+	const currentYear = now.getFullYear()
+	const currentMonth = now.getMonth() + 1
+	
+	console.log(`当前时间: ${currentYear}年${currentMonth}月`)
+	console.log(`月份标签数量: ${monthLabels.value.length}`)
+	
+	// 显示所有月份标签
+	monthLabels.value.forEach((label, index) => {
+		console.log(`标签${index + 1}: ${label.label} (${label.year}-${label.month}) 位置: ${label.left}`)
+		
+		// 计算该月份第一天对应的位置
+		const firstDayDate = `${label.year}-${label.month.toString().padStart(2, '0')}-01`
+		const calculatedPosition = calculatePositionByDate(firstDayDate)
+		const reverseDate = calculateDateByPosition(calculatedPosition)
+		
+		console.log(`  计算位置: ${calculatedPosition}, 反向日期: ${reverseDate}`)
+		console.log(`  位置一致: ${Math.abs(label.left - calculatedPosition) < 1 ? '✅' : '❌'}`)
+		console.log(`  日期一致: ${firstDayDate === reverseDate ? '✅' : '❌'}`)
+	})
+}
+
+// 显示当前月份信息
+const showCurrentMonthInfo = () => {
+	const now = new Date()
+	const currentYear = now.getFullYear()
+	const currentMonth = now.getMonth() + 1
+	
+	console.log('=== 当前月份信息 ===')
+	console.log(`当前年份: ${currentYear}, 当前月份: ${currentMonth}`)
+	console.log(`月份标签数量: ${monthLabels.value.length}`)
+	
+	// 显示所有月份标签
+	monthLabels.value.forEach((label, index) => {
+		console.log(`标签${index + 1}: ${label.label} (${label.year}-${label.month}) 位置: ${label.left}`)
+	})
+	
+	return { year: currentYear, month: currentMonth }
+}
+
+// 显示当前日期信息
+const showCurrentDateInfo = () => {
+	const now = new Date()
+	const currentYear = now.getFullYear()
+	const currentMonth = now.getMonth() + 1
+	const currentDay = now.getDate()
+	
+	console.log('=== 当前日期信息 ===')
+	console.log(`当前日期: ${currentYear}-${currentMonth.toString().padStart(2, '0')}-${currentDay.toString().padStart(2, '0')}`)
+	console.log(`当前年份: ${currentYear}, 当前月份: ${currentMonth}, 当前日期: ${currentDay}`)
+	
+	// 计算当前日期在时间轴上的位置
+	const currentDateStr = `${currentYear}-${currentMonth.toString().padStart(2, '0')}-${currentDay.toString().padStart(2, '0')}`
+	const currentPosition = calculatePositionByDate(currentDateStr)
+	console.log(`当前日期在时间轴上的位置: ${currentPosition}`)
+	
+	return currentDateStr
+}
+
+// 测试每天刻度的准确性
+const testDailyTicks = () => {
+	console.log('=== 每天刻度测试 ===')
+	
+	const now = new Date()
+	const currentYear = now.getFullYear()
+	const currentMonth = now.getMonth()
+	
+	// 测试当前月份的几个关键日期
+	const testDays = [1, 5, 10, 15, 20, 25, 30]
+	
+	testDays.forEach(day => {
+		const testDate = new Date(currentYear, currentMonth, day)
+		const year = testDate.getFullYear()
+		const month = testDate.getMonth() + 1
+		const daysInMonth = new Date(year, month, 0).getDate()
+		
+		// 只测试有效的日期
+		if (day <= daysInMonth) {
+			const dateStr = `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`
+			const position = calculatePositionByDate(dateStr)
+			const reverseDate = calculateDateByPosition(position)
+			const isAccurate = dateStr === reverseDate
+			
+			console.log(`日期: ${dateStr} -> 位置: ${position} -> 反向: ${reverseDate} ${isAccurate ? '✅' : '❌'}`)
+		}
+	})
+}
+
+// 简单测试:验证月份对应关系
+const simpleTest = () => {
+	console.log('=== 简单测试 ===')
+	
+	// 测试当前月份
+	const now = new Date()
+	const currentYear = now.getFullYear()
+	const currentMonth = now.getMonth() + 1
+	const currentDate = `${currentYear}-${currentMonth.toString().padStart(2, '0')}-01`
+	
+	console.log(`当前月份: ${currentMonth}月`)
+	console.log(`当前日期: ${currentDate}`)
+	
+	// 计算位置
+	const position = calculatePositionByDate(currentDate)
+	console.log(`计算位置: ${position}`)
+	
+	// 反向计算时间
+	const reverseDate = calculateDateByPosition(position)
+	console.log(`反向计算: ${reverseDate}`)
+	
+	// 验证一致性
+	const isConsistent = currentDate === reverseDate
+	console.log(`一致性: ${isConsistent ? '✅' : '❌'}`)
+	
+	return isConsistent
+}
+
+// 测试3个月差问题
+const testThreeMonthDifference = () => {
+	console.log('=== 3个月差问题测试 ===')
+	
+	const now = new Date()
+	const currentYear = now.getFullYear()
+	const currentMonth = now.getMonth() + 1
+	
+	console.log(`当前时间: ${currentYear}年${currentMonth}月`)
+	
+	// 测试当前月份的位置
+	const currentMonthDate = `${currentYear}-${currentMonth.toString().padStart(2, '0')}-01`
+	const currentPosition = calculatePositionByDate(currentMonthDate)
+	const currentReverseDate = calculateDateByPosition(currentPosition)
+	
+	console.log(`当前月份: ${currentMonthDate}`)
+	console.log(`计算位置: ${currentPosition}`)
+	console.log(`反向计算: ${currentReverseDate}`)
+	console.log(`当前月份一致性: ${currentMonthDate === currentReverseDate ? '✅' : '❌'}`)
+	
+	// 测试3个月前的月份
+	const threeMonthsAgo = new Date(currentYear, currentMonth - 4, 1) // 减4是因为getMonth()返回0-11
+	const threeMonthsAgoYear = threeMonthsAgo.getFullYear()
+	const threeMonthsAgoMonth = threeMonthsAgo.getMonth() + 1
+	const threeMonthsAgoDate = `${threeMonthsAgoYear}-${threeMonthsAgoMonth.toString().padStart(2, '0')}-01`
+	
+	const threeMonthsPosition = calculatePositionByDate(threeMonthsAgoDate)
+	const threeMonthsReverseDate = calculateDateByPosition(threeMonthsPosition)
+	
+	console.log(`3个月前: ${threeMonthsAgoDate}`)
+	console.log(`计算位置: ${threeMonthsPosition}`)
+	console.log(`反向计算: ${threeMonthsReverseDate}`)
+	console.log(`3个月前一致性: ${threeMonthsAgoDate === threeMonthsReverseDate ? '✅' : '❌'}`)
+	
+	// 检查月份标签
+	console.log('月份标签检查:')
+	monthLabels.value.forEach((label, index) => {
+		if (label.year === threeMonthsAgoYear && label.month === threeMonthsAgoMonth) {
+			console.log(`找到3个月前标签: ${label.label} 位置: ${label.left}`)
+			console.log(`位置差: ${Math.abs(label.left - threeMonthsPosition)}`)
+		}
+	})
+}
+
+// 组件挂载后初始化
+onMounted(() => {
+	// 运行简单测试
+	simpleTest()
+	
+	// 测试每天刻度
+	testDailyTicks()
+	
+	// 测试3个月差问题
+	testThreeMonthDifference()
+	
+	// 显示月份对比信息
+	showMonthComparison()
+	
+	// 显示当前月份信息
+	showCurrentMonthInfo()
+	
+	// 显示当前日期信息
+	showCurrentDateInfo()
+	
+	// 运行测试
+	testTimeCalculation()
+	
+	// 如果没有传入当前日期,使用今天
+	if (!props.currentDate) {
+		const today = new Date()
+		const year = today.getFullYear()
+		const month = (today.getMonth() + 1).toString().padStart(2, '0')
+		const day = today.getDate().toString().padStart(2, '0')
+		const todayStr = `${year}-${month}-${day}`
+		
+		currentTime.value = todayStr
+		emit('timeChange', todayStr)
+		
+		// 设置默认滚动到最右边(当前日期位置)
+		nextTick(() => {
+			// 计算需要滚动的距离,让最右边(当前月份)显示在屏幕中间
+			// 总宽度2400rpx,屏幕宽度750rpx,要让最右边显示在中间
+			scrollLeft.value = 2400 - 750
+		})
+	} else {
+		// 如果有传入日期,初始化时间
+		currentTime.value = props.currentDate
+		
+		// 设置默认滚动到传入日期位置
+		nextTick(() => {
+			const targetPosition = calculatePositionByDate(props.currentDate)
+			// 计算需要滚动的距离,让目标日期显示在屏幕中间
+			scrollLeft.value = targetPosition - 375
+		})
+	}
+})
+</script>
+
+<style lang="scss" scoped>
+.time-scale-container {
+	width: 100%;
+	position: relative;
+	margin-bottom: 20rpx;
+	padding: 0 20rpx;
+}
+
+.time-scale-scroll {
+	position: relative;
+	width: 100%;
+	height: 120rpx;
+}
+
+.time-scale-wrapper {
+	width: 100%;
+	height: 100%;
+	white-space: nowrap;
+}
+
+.time-scale {
+	display: inline-block;
+	min-width: 2400rpx; // 12个月 * 200rpx
+	height: 100%;
+	position: relative;
+	padding: 0 375rpx; // 左右各留一半屏幕宽度,确保最左和最右都在中间
+}
+
+.month-labels {
+	position: relative;
+	height: 40rpx;
+}
+
+.month-label {
+	position: absolute;
+	top: 0;
+	transform: translateX(-50%);
+	font-size: 24rpx;
+	color: #777777;
+	font-weight: 500;
+}
+
+.timeline {
+	position: relative;
+	height: 80rpx;
+	margin-top: 20rpx;
+	
+	&::before {
+		content: '';
+		position: absolute;
+		top: 50%;
+		left: 0;
+		right: 0;
+		height: 2rpx;
+		background: rgba(136, 136, 136, 0.1);
+		transform: translateY(-50%);
+	}
+}
+
+.month-dot {
+	position: absolute;
+	top: 50%;
+	width: 14rpx;
+	height: 14rpx;
+	background: #777777;
+	border-radius: 50%;
+	transform: translate(-50%, -50%);
+	z-index: 5;
+}
+
+.tick-mark {
+	position: absolute;
+	top: 50%;
+	width: 2rpx;
+	height: 8rpx;
+	background: rgba(136, 136, 136, 0.3);
+	transform: translate(-50%, -50%);
+}
+
+.center-line {
+	position: absolute;
+	left: 50%;
+	top: 0;
+	bottom: 0;
+	width: 2rpx;
+	background: #2199F8;
+	z-index: 15;
+	transform: translateX(-50%);
+	pointer-events: none;
+}
+
+.current-time {
+	text-align: center;
+	margin-top: 16rpx;
+	font-size: 28rpx;
+	color: #2199F8;
+	font-weight: 500;
+}
+</style>

+ 134 - 187
pages/tabBar/tree/components/treeAlbumPopup.vue

@@ -9,41 +9,34 @@
 				<image class="icon" :src="`${config.BASIC_IMG}img/treePage/drone-icon-popup.png`"></image>
 			</view>
             <view class="album-cont">
-                <view class="time-line-scroll">
-                    <scroll-view class="time-line-container" scroll-x :scroll-into-view="scrollIntoViewId" scroll-with-animation @scroll="handleScroll" @scrollend="handleScrollEnd">
-                        <view class="time-line">
-                            <!-- 月份标记 -->
-                            <view class="month-markers">
-                                <view class="month-item" v-for="(month, index) in monthList" :key="index">
-                                    <text class="month-text">{{ month.label }}</text>
-                                    <view class="month-dot" :class="{'active': month.isCurrent}"></view>
-                                </view>
-                            </view>
-                            <!-- 日期刻度 -->
-                            <view class="date-markers">
-                                <view class="date-item" v-for="(date, index) in dateList" :key="index" :class="{'current-date': date.isCurrent}">
-                                    <text class="date-text" :class="{'current': date.isCurrent}">{{ date.display }}</text>
-                                    <view class="date-dot" :class="{'current': date.isCurrent}"></view>
-                                </view>
-                            </view>
+                <scroll-view class="time-line-scroll" scroll-x :scroll-into-view="scrollIntoViewId" scroll-with-animation>
+                    <view class="time-line">
+                        <view class="time-item" v-for="(item,index) in dateList" :key="index" :id="`d-${item.dateStr}`" @click="handleDateClick(item.dateStr)">
+                            <text v-if="item.showYear" class="year-flag">{{ item.year }}</text>
+                            <text :style="{color:(selectedDateStr===item.dateStr)?'#2199F8':'#777777'}">{{item.display}}</text>
+                            <view class="dot" :style="{background:(selectedDateStr===item.dateStr)?'#2199F8':'#777777'}"></view>
+                            <text class="today" v-if="item.isToday">今</text>
                         </view>
-                    </scroll-view>
-                    <!-- 中间竖线标志 -->
-                    <view class="center-line"></view>
-                </view>
+                    </view>
+                </scroll-view>
 				<view class="swiper-wrap">
 					<swiper class="swiper">
 						<swiper-item v-for="(item,index) in photoList" :key="index">
-							<image class="img" :src="getImageUrl(item.filename)"></image>
+							<image class="img" :src="getImageUrl(item.filename)" mode="aspectFill"></image>
+							<!-- <view class="text-wrap">
+								<view class="date">{{item.uploadDate}}</view>
+								<view class="code">{{item.treeCode}}</view>
+							</view> -->
 						</swiper-item>
 					</swiper>
-					<!-- <view class="arrow left">
+					<view class="arrow left" @click="handlePrevDay">
 						<up-icon name="arrow-left" bold color="#F0D09C" size="22"></up-icon>
 					</view>
-					<view class="arrow right">
+					<view class="arrow right" @click="handleNextDay">
 						<up-icon name="arrow-right" bold color="#F0D09C" size="22"></up-icon>
-					</view> -->
+					</view>
 				</view>
+				<!-- <time-scale></time-scale> -->
 			</view>
 		</view>
 	</up-popup>
@@ -57,6 +50,7 @@
 		watch,
 		nextTick
 	} from "vue";
+	import timeScale from './timeScale.vue'
 	const resize = "?x-oss-process=image/resize,w_1000";
 
 	const props = defineProps({
@@ -78,16 +72,14 @@
 	const handleClose = ()=>{
 		showPopup.value = false
 	}
-    const monthList = ref([])
     const dateList = ref([])
     const scrollIntoViewId = ref('')
     const selectedDateStr = ref('')
-    const centerDate = ref('')
 
     const formatToDisplay = (dateStr)=>{
         // dateStr: YYYY-MM-DD
         const [y,m,d] = dateStr.split('-')
-        return `${m}月${d}日`
+        return `${m}/${d}`
     }
     const getTodayStr = ()=>{
         const d = new Date()
@@ -95,59 +87,48 @@
         const dd = `${d.getDate()}`.padStart(2,'0')
         return `${d.getFullYear()}-${mm}-${dd}`
     }
-    
-    const generateTimeLineData = () => {
-        const today = new Date()
-        const currentYear = today.getFullYear()
-        const currentMonth = today.getMonth() + 1
-        const currentDay = today.getDate()
-        
-        // 生成月份列表(往前一年)
-        const months = []
-        for(let i = 11; i >= 0; i--) {
-            const date = new Date(currentYear, currentMonth - 1 - i, 1)
-            const month = date.getMonth() + 1
-            const year = date.getFullYear()
-            const isCurrent = month === currentMonth && year === currentYear
-            
-            months.push({
-                year,
-                month,
-                label: `${month}月`,
-                isCurrent,
-                dateStr: `${year}-${month.toString().padStart(2,'0')}-01`
-            })
-        }
-        monthList.value = months
-        
-        // 生成当前月份的日期刻度(每两天一个)
-        const dates = []
-        const daysInMonth = new Date(currentYear, currentMonth, 0).getDate()
-        
-        for(let day = 1; day <= daysInMonth; day += 2) {
-            const dateStr = `${currentYear}-${currentMonth.toString().padStart(2,'0')}-${day.toString().padStart(2,'0')}`
-            const isCurrent = day === currentDay
-            
-            dates.push({
-                dateStr,
-                display: `${currentMonth}月${day}日`,
-                isCurrent,
-                day
-            })
+    const fetchHasImageDates = async ()=>{
+        if(!props.farmBuyId) return
+        try{
+            const {data} = await TREE.findHasImagesDate({farmBuyId: props.farmBuyId})
+            const today = getTodayStr()
+            const arr = Array.isArray(data) ? data : []
+            const mapped = arr.map(ds=>{
+                const dateStr = (typeof ds === 'string') ? ds.split(' ')[0] : ''
+                const isToday = dateStr === today
+                return {
+                    dateStr,
+                    display: dateStr ? formatToDisplay(dateStr) : '',
+                    isToday,
+                    year: dateStr ? dateStr.slice(0,4) : ''
+                }
+            }).filter(i=>i.dateStr)
+            mapped.sort((a,b)=> a.dateStr.localeCompare(b.dateStr))
+            for(let i=0;i<mapped.length;i++){
+                mapped[i].showYear = i===0 || mapped[i].year !== mapped[i-1].year
+            }
+            dateList.value = mapped
+            if(dateList.value.length){
+                const last = dateList.value[dateList.value.length-1]
+                selectedDateStr.value = last.dateStr
+                // 强制等渲染后再滚动到最右端
+                scrollIntoViewId.value = ''
+                await nextTick()
+                scrollIntoViewId.value = `d-${last.dateStr}`
+            }else{
+                selectedDateStr.value = ''
+                scrollIntoViewId.value = ''
+            }
+        }catch(e){
+            // 静默失败
         }
-        dateList.value = dates
-        
-        // 设置当前日期为选中状态
-        selectedDateStr.value = getTodayStr()
-        centerDate.value = getTodayStr()
     }
-    
     const fetchTreeImages = async (dateStr)=>{
         try{
             const params = {
                 page: 1,
                 limit: 1,
-                treeId: 110939,
+                treeId: Number(props.sampleId) || 110939,
                 date: dateStr,
             }
             const {data} = await TREE.treeImageList(params)
@@ -164,43 +145,49 @@
 	    }
 	};
 	
-    const handleScroll = (e) => {
-        // 滚动中实时计算中间位置
-        const scrollLeft = e.detail.scrollLeft
-        const viewWidth = e.detail.scrollWidth - e.detail.scrollLeft
+    const handleDateClick = (dateStr)=>{
+        selectedDateStr.value = dateStr
+        scrollIntoViewId.value = `d-${dateStr}`
+        fetchTreeImages(dateStr)
+    }
+    
+    const handlePrevDay = () => {
+        if(!selectedDateStr.value) return
         
-        // 计算中间位置对应的日期
-        const centerPosition = scrollLeft + viewWidth / 2
-        const itemWidth = 120 // 每个时间项的宽度
-        const estimatedIndex = Math.floor(centerPosition / itemWidth)
+        const currentDate = new Date(selectedDateStr.value)
+        const prevDate = new Date(currentDate)
+        prevDate.setDate(currentDate.getDate() - 1)
         
-        if(dateList.value[estimatedIndex]){
-            centerDate.value = dateList.value[estimatedIndex].dateStr
-        }
+        const prevDateStr = prevDate.toISOString().split('T')[0]
+        selectedDateStr.value = prevDateStr
+        scrollIntoViewId.value = `d-${prevDateStr}`
+        fetchTreeImages(prevDateStr)
     }
     
-    const handleScrollEnd = (e) => {
-        // 滚动停止时获取中间位置的日期并调用接口
-        const scrollLeft = e.detail.scrollLeft
-        const viewWidth = e.detail.scrollWidth - e.detail.scrollLeft
+    const handleNextDay = () => {
+        if(!selectedDateStr.value) return
         
-        const centerPosition = scrollLeft + viewWidth / 2
-        const itemWidth = 120
-        const estimatedIndex = Math.floor(centerPosition / itemWidth)
+        const currentDate = new Date(selectedDateStr.value)
+        const nextDate = new Date(currentDate)
+        nextDate.setDate(currentDate.getDate() + 1)
         
-        if(dateList.value[estimatedIndex]){
-            const finalDate = dateList.value[estimatedIndex].dateStr
-            centerDate.value = finalDate
-            selectedDateStr.value = finalDate
-            console.log('滑动停止,中间位置日期:', finalDate)
-            fetchTreeImages(finalDate)
+        const today = new Date()
+        const todayStr = today.toISOString().split('T')[0]
+        
+        // 不能超过当前日期
+        if(nextDate.toISOString().split('T')[0] > todayStr) {
+            uni.showToast({
+                title: '不能超过当前日期',
+                icon: 'none',
+                duration: 2000
+            })
+            return
         }
-    }
-    
-    const handleDateClick = (dateStr)=>{
-        selectedDateStr.value = dateStr
-        centerDate.value = dateStr
-        fetchTreeImages(dateStr)
+        
+        const nextDateStr = nextDate.toISOString().split('T')[0]
+        selectedDateStr.value = nextDateStr
+        scrollIntoViewId.value = `d-${nextDateStr}`
+        fetchTreeImages(nextDateStr)
     }
 	const photoList = ref([]);
 	
@@ -209,7 +196,7 @@
         async (val) => {
             showPopup.value = val;
             if(val){
-                generateTimeLineData()
+                await fetchHasImageDates()
                 const today = getTodayStr()
                 fetchTreeImages(today)
             }
@@ -219,7 +206,7 @@
         () => props.farmBuyId,
         (val) => {
             if(showPopup.value && val){
-                generateTimeLineData()
+                fetchHasImageDates()
             }
         }
     )
@@ -265,97 +252,46 @@
             .time-line-scroll{
                 margin-bottom: 16rpx;
                 width: 100%;
-                position: relative;
-            }
-            .time-line-container{
-                width: 100%;
                 white-space: nowrap;
             }
             .time-line{
                 display: flex;
-                flex-direction: column;
                 padding: 0 20rpx;
-                position: relative;
-            }
-            .center-line{
-                position: absolute;
-                left: 50%;
-                top: 0;
-                bottom: 0;
-                width: 2rpx;
-                background: #2199F8;
-                z-index: 10;
-                transform: translateX(-50%);
-                pointer-events: none;
             }
-            
-            .month-markers{
+            .time-item{
+                min-width: 100rpx;
+                font-size: 24rpx;
+                color: #777777;
                 display: flex;
-                align-items: flex-end;
-                margin-bottom: 20rpx;
-                
-                .month-item{
-                    display: flex;
-                    flex-direction: column;
-                    align-items: center;
-                    min-width: 120rpx;
-                    padding: 0 12rpx;
-                    
-                    .month-text{
-                        font-size: 28rpx;
-                        color: #777777;
-                        margin-bottom: 8rpx;
-                    }
-                    
-                    .month-dot{
-                        width: 16rpx;
-                        height: 16rpx;
-                        background: #777777;
-                        border-radius: 50%;
-                        
-                        &.active{
-                            background: #2199F8;
-                        }
-                    }
+                flex-direction: column;
+                align-items: center;
+                position: relative;
+                padding: 0 12rpx;
+                &::before{
+                    content: '';
+                    position: absolute;
+                    top: 44%;
+                    left: 0;
+                    right: 0;
+                    height: 2rpx;
+                    background: rgba(136, 136, 136, 0.1);
                 }
-            }
-            
-            .date-markers{
-                display: flex;
-                align-items: flex-end;
-                
-                .date-item{
-                    display: flex;
-                    flex-direction: column;
-                    align-items: center;
-                    min-width: 120rpx;
-                    padding: 0 12rpx;
-                    
-                    .date-text{
-                        font-size: 24rpx;
-                        color: #777777;
-                        margin-bottom: 8rpx;
-                        
-                        &.current{
-                            color: #2199F8;
-                        }
-                    }
-                    
-                    .date-dot{
-                        width: 8rpx;
-                        height: 8rpx;
-                        background: #ccc;
-                        border-radius: 50%;
-                        
-                        &.current{
-                            background: #2199F8;
-                            width: 16rpx;
-                            height: 16rpx;
-                        }
-                    }
+                .year-flag{
+                    font-size: 20rpx;
+                    color: #999;
+                    margin-bottom: 6rpx;
+                }
+                .dot{
+                    width: 14rpx;
+                    height: 14rpx;
+                    background: #777777;
+                    border-radius: 50%;
+                }
+                .today{
+                    color: #2199F8;
+                    margin-top: 8rpx;
                 }
             }
-            
 			.swiper-wrap{
 				position: relative;
 				padding: 0 20rpx;
@@ -368,6 +304,17 @@
 						height: 100%;
 						border-radius: 16rpx;
 					}
+					.text-wrap{
+						position: absolute;
+						bottom: 20rpx;
+						left: 20rpx;
+						font-size: 20rpx;
+						color: #fff;
+						.date{
+							font-size: 40rpx;
+							font-family: 'SMILEYSANS';
+						}
+					}
 				}
 				.arrow{
 					position: absolute;

+ 28 - 9
pages/tabBar/tree/subPages/gift.vue

@@ -20,10 +20,10 @@
 						<image class="icon"
 							:src="`${config.BASIC_IMG}img/subTreePage/${item.check?'gray-leaf':'leaf-icon'}.png`" alt="">
 						</image>
-						<text>x20</text>
+						<text>x{{item.energy}}</text>
 					</view>
 					<view v-if="item.check">已领取</view>
-					<view v-else>第{{item.day}}天</view>
+					<view v-else>第{{item.days}}天</view>
 				</view>
 			</view>
 		</view>
@@ -47,9 +47,28 @@
 </template>
 
 <script setup>
-	import config from "@/api/config.js"
+	import config from "@/api/config.js"
+	import TREE from '@/api/tree.js'
+	import {
+		ref
+	} from 'vue';
+	import {
+		onLoad
+	} from '@dcloudio/uni-app'
+	
+	const getRules = () => {
+		TREE.ruleList().then((res) => {
+			checkDay.value = res.data
+			// firstRowItems.value = res.data.slice(0, 3);
+			// secondRowItems.value = res.data.slice(3);
+		});
+	};
+	
+	onLoad(()=>{
+		getRules()
+	})
 
-	const checkDay = [{
+	const checkDay = ref([{
 			day: 1,
 			check: true
 		},
@@ -77,7 +96,7 @@
 			day: 7,
 			check: false
 		}
-	]
+	])
 
 	const tableList = [{
 			level: '平民',
@@ -86,22 +105,22 @@
 		},
 		{
 			level: '青铜',
-			text: '达到300能量',
+			text: '达到200能量',
 			gift: '一份曲奇'
 		},
 		{
 			level: '白银',
-			text: '达到600能量',
+			text: '达到1200能量',
 			gift: '一份精美水果'
 		},
 		{
 			level: '赤金',
-			text: '达到900能量',
+			text: '达到5500能量',
 			gift: '一份荔枝'
 		},
 		{
 			level: '星勋',
-			text: '达到1200能量',
+			text: '达到9800能量',
 			gift: '一份荔枝'
 		}
 	]