Переглянути джерело

feat:对接果树相册接口和动态接口

wangsisi 1 місяць тому
батько
коміт
825eaff0e1

+ 1 - 0
api/config.js

@@ -1,6 +1,7 @@
 const config = {
 	BASIC_IMG: "https://birdseye-img.sysuimars.com/youwei-uniapp/", // 图片地址
 	BASE_URL: 'https://feiniaotech-dev.sysuimars.cn/', //开发环境
+	BASE_IMG_URL : "https://birdseye-img-ali-cdn.sysuimars.com/",
 	BASE_URL_MINI: 'https://feiniaotech-dev.sysuimars.cn/mini/',
 	BASE_URL_PRO: 'https://birdseye-api.feiniaotech.sysuimars.cn/',//生产环境
 	H5_SRC: "https://feiniao-mini-h5-dev.sysuimars.cn/index.html#/"

+ 32 - 12
api/tree.js

@@ -1,14 +1,14 @@
-import http from '@/utils/http'
-import config from './config'
-
-export default {
-	//获取自己果树数据
-	getBySampleId(data) {
-		return http.get('mini/z_farm_buy/getBySampleId', data)
-	},
-	// 获取能量记录
-	getEnergyRecords(data) {
-		return http.get(`mini/z_farm_buy/getEnergyRecords/${data.page}/${data.limit}`, data)
+import http from '@/utils/http'
+import config from './config'
+
+export default {
+	//获取自己果树数据
+	getBySampleId(data) {
+		return http.get('mini/z_farm_buy/getBySampleId', data)
+	},
+	// 获取能量记录
+	getEnergyRecords(data) {
+		return http.get(`mini/z_farm_buy/getEnergyRecords/${data.page}/${data.limit}`, data)
 	},
 	//获取果树海报
 	getPoster(data) {
@@ -29,5 +29,25 @@ export default {
 	//添加订阅
 	addSubscribe(data) {
 		return http.get('mini/z_farm_buy/addSubscribe', data)
-	},
+	},
+	//修改树名称
+	updateTreeName(data) {
+		return http.get('mini/z_farm_buy/updateTreeName', data)
+	},
+	//签到
+	sign(data) {
+		return http.post('mini/buy_sign_in/sign', data)
+	},
+	//规则
+	ruleList(data) {
+		return http.get('mini/buy_sign_in/ruleList', data)
+	},
+	//图片日期,用于判断
+	findHasImagesDate(data) {
+		return http.get('mini/image/findHasImagesDate', data)
+	},
+	//图片列表
+	treeImageList(data) {
+		return http.post('mini/lz_tree_image/treeImageList', data)
+	},
 }

+ 205 - 0
components/checkinPopup/checkinPopup.vue

@@ -0,0 +1,205 @@
+<template>
+	<up-popup class="check-popup" :show="showPopup" mode="center" @close="closeCheckPopup" bgColor="transparent" overlayOpacity="0.8">
+		<view class="title">
+			<image class="left-image" :src="`${config.BASIC_IMG}img/treePage/two-star.png`" alt="" />
+			<text>恭喜你,登录成功</text>
+			<image class="right-image" :src="`${config.BASIC_IMG}img/treePage/two-star.png`" alt="" />
+		</view>
+		<view class="check-wrap">
+			<!-- First row with 3 items -->
+			<view class="check-row">
+				<view v-for="(item, index) in firstRowItems" :key="index"
+					:class="['check-day', { active: runningDays == index + 1 },{gary:runningDays > index + 1}]">
+					<view class="leaf-wrap">
+						<image class="leaf"
+							:src="`${config.BASIC_IMG}img/treePage/${runningDays == index + 1 ? 'white-leaf' : runningDays > index + 1 ?'gary-leaf':'leaf-icon'}.png`"
+							alt="leaf icon" />
+						<text>x{{ item.energy }}</text>
+					</view>
+					<view v-if="runningDays > index + 1"><text>已领取</text></view>
+					<view v-else><text v-if="runningDays == index + 1">累计</text>{{ item.days }}天</view>
+				</view>
+			</view>
+
+			<!-- Second row with 4 items -->
+			<view class="check-row">
+				<view v-for="(item, index) in secondRowItems" :key="index"
+					:class="['check-day', { active: runningDays == index + 4 },{gary:runningDays > index + 4}]">
+					<view class="leaf-wrap">
+						<image class="leaf"
+							:src="`${config.BASIC_IMG}img/treePage/${runningDays == index + 4 ? 'white-leaf' : runningDays > index + 1 ?'gary-leaf':'leaf-icon'}.png`"
+							alt="leaf icon" />
+						<text>x{{ item.energy }}</text>
+					</view>
+					<view v-if="runningDays > index + 4"><text>已领取</text></view>
+					<view v-else><text v-if="runningDays == index + 4">累计</text>{{ item.days }}天</view>
+				</view>
+			</view>
+		</view>
+		<view class="button" @click="showPopup = false">开心收下</view>
+		<view class="close">
+			<up-icon name="close-circle-fill" size="30" @click="showPopup = false"
+				color="rgba(255, 255, 255, 0.7)"></up-icon>
+		</view>
+	</up-popup>
+</template>
+
+<script setup>
+	import config from "@/api/config.js"
+	import TREE from '@/api/tree.js'
+	import {
+		onMounted,
+		ref,
+	} from "vue";
+
+	const firstRowItems = ref([]);
+	const secondRowItems = ref([]);
+	const showPopup = ref(false);
+
+	const getRules = () => {
+		TREE.ruleList().then((res) => {
+			firstRowItems.value = res.data.slice(0, 3);
+			secondRowItems.value = res.data.slice(3);
+		});
+	};
+
+	const runningDays = ref(0)
+	const checkIn = () => {
+		TREE.sign().then((res) => {
+			if (res.code === 0) {
+				runningDays.value = res.data.runningDays
+				getRules();
+				showPopup.value = true;
+			} else {
+				closeCheckPopup()
+			}
+		});
+	};
+
+	onMounted(() => {
+		checkIn();
+	});
+
+	function closeCheckPopup() {
+		emit('closedCheckPopup');
+	}
+
+	const emit = defineEmits(['closedCheckPopup']);
+</script>
+
+<style lang="scss" scoped>
+	@import "@/static/style/mixin.scss";
+
+	.check-popup {
+		width: 100%;
+		background: transparent;
+		text-align: center;
+
+		.title {
+			font-size: 56rpx;
+			letter-spacing: 2rpx;
+			font-family: "PangMenZhengDao";
+			background: linear-gradient(to bottom, #FFFFFF, #FFD23C);
+			-webkit-background-clip: text;
+			background-clip: text;
+			-webkit-text-fill-color: transparent;
+			color: transparent;
+			position: relative;
+
+			image {
+				width: 48rpx;
+				height: 48rpx;
+				position: absolute;
+			}
+
+			.left-image {
+				top: 0;
+				left: -14rpx;
+			}
+
+			.right-image {
+				right: -16rpx;
+				bottom: 0;
+			}
+
+			&::before {
+				content: '';
+				position: absolute;
+				width: 600rpx;
+				height: 104rpx;
+				bottom: -44rpx;
+				left: -16rpx;
+				@include ossBg("treePage/check-title-bg.png");
+			}
+		}
+
+		.check-wrap {
+			margin: 60rpx 0 100rpx 0;
+			display: flex;
+			flex-direction: column;
+			align-items: center;
+			min-height: 322rpx;
+
+			.check-row {
+				margin-top: 14rpx;
+				display: flex;
+
+				.check-day {
+					width: 124rpx;
+					box-sizing: border-box;
+					padding: 28rpx 0;
+					font-size: 24rpx;
+					color: #f3b242;
+					background: rgba(255, 255, 255, 0.32);
+					border-radius: 10rpx;
+					text-align: center;
+
+					&.active {
+						background-image: linear-gradient(120deg, #ffd887, #ed9e1e);
+						color: #fff;
+						border: 2rpx solid #fff;
+					}
+
+					&.gary {
+						color: rgba(255, 255, 255, 0.6);
+					}
+
+					.leaf-wrap {
+						display: flex;
+						justify-content: center;
+						font-size: 28rpx;
+						align-items: baseline;
+
+						.leaf {
+							width: 50rpx;
+							height: 46rpx;
+							margin-bottom: 12rpx;
+							margin-right: 6rpx;
+						}
+					}
+				}
+
+				.check-day+.check-day {
+					margin-left: 10rpx;
+				}
+			}
+		}
+
+		.button {
+			margin: auto;
+			width: 312rpx;
+			font-size: 44rpx;
+			padding: 20rpx 0;
+			border-radius: 50rpx;
+			font-family: "PangMenZhengDao";
+			color: #954600;
+			background: linear-gradient(120deg, #ffe6b2, #ffc339);
+			text-align: center;
+		}
+		.close {
+			margin-top: 40rpx;
+			display: flex;
+			justify-content: center;
+		}
+	}
+</style>

+ 13 - 6
components/editNamePopup/editNamePopup.vue

@@ -6,7 +6,7 @@
                 <view class="board-img">
                     <view class="tag-img">
                         <view class="tag-content">
-                            <view class="tag-name">【{{ editForm?.treeName || '茜茜荔' }}】</view>
+                            <view class="tag-name">【{{ editForm?.treeName }}】</view>
                         </view>
                     </view>
                 </view>
@@ -37,6 +37,7 @@
 
 <script setup>
 import { ref, reactive } from "vue";
+import TREE from '@/api/tree.js'
 
 const showBoardPopup = ref(false);
 
@@ -54,14 +55,20 @@ const editForm = reactive({
     treeName: "",
     nickname: "",
     showName: 1,
-    createDate: "2025.06.18"
+    createDate: ""
 })
 
 function onSubmit() {
-    VE_API.guard_tree.updateTreeName(editForm).then(({ code }) => {
-        // code === 0 && ElMessage.success("编辑成功");
-        showBoardPopup.value = false
-        emit("editEnd")
+    TREE.updateTreeName(editForm).then(({ code }) => {
+		if(code === 0){
+			uni.showToast({
+				title: '编辑成功',
+				icon:'none',
+				duration: 2000
+			});
+			showBoardPopup.value = false
+			emit("editEnd")
+		}
     });
 }
 const emit = defineEmits(["editEnd"])

+ 3 - 1
pages.json

@@ -20,7 +20,9 @@
 		{
 			"path": "pages/tabBar/tree/subPages/dynamic",
 			"style": {
-				"navigationBarTitleText": "动态消息"
+				"navigationBarTitleText": "动态消息",
+				"enablePullDownRefresh": true,
+				"onReachBottomDistance": 50
 			}
 		},
 		{

+ 2 - 2
pages/tabBar/tree/components/memberLevel.vue

@@ -108,12 +108,12 @@
 				.avatar {
 					border: 2rpx solid #fff;
 					border-radius: 50%;
-					margin-right: 20rpx;
 				}
 
 				.level-wrap {
 					line-height: 32rpx;
-					width: 54%;
+					width: 54%;
+					margin-left: 20rpx;
 
 					.name {
 						color: #fff;

+ 238 - 126
pages/tabBar/tree/components/treeAlbumPopup.vue

@@ -1,140 +1,252 @@
-<template>
-	<up-popup :show="showPopup" mode="center" round="20" @close="handleClose">
-		<view class="album-popup">
-			<view class="album-title">
-				<view class="name">
-					果树相册
-					<view class="sub-name">无人机实时监测生长情况</view>
-				</view>
-				<image class="icon" :src="`${config.BASIC_IMG}img/treePage/drone-icon-popup.png`"></image>
-			</view>
-			<view class="album-cont">
-				<view class="time-line">
-					<view class="time-item" v-for="(item,index) in 6" :key="index">
-						<text :style="{color:item==6?'#2199F8':'#777777'}">07/28</text>
-						<view class="dot" :style="{background:item==6?'#2199F8':'#777777'}"></view>
-						<text class="today" v-if="item==6">今</text>
-					</view>
+<template>
+	<up-popup :show="showPopup" mode="center" round="20" @close="handleClose">
+		<view class="album-popup">
+			<view class="album-title">
+				<view class="name">
+					果树相册
+					<view class="sub-name">无人机实时监测生长情况</view>
 				</view>
+				<image class="icon" :src="`${config.BASIC_IMG}img/treePage/drone-icon-popup.png`"></image>
+			</view>
+            <view class="album-cont">
+                <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>
+                    </view>
+                </scroll-view>
 				<view class="swiper-wrap">
-					<swiper class="swiper">
-						<swiper-item v-for="(item,index) in list1" :key="index">
-							<image class="img" :src="item"></image>
+					<swiper class="swiper">
+						<swiper-item v-for="(item,index) in photoList" :key="index">
+							<image class="img" :src="getImageUrl(item.filename)"></image>
 						</swiper-item>
 					</swiper>
-					<view class="arrow left">
+					<!-- <view class="arrow left">
 						<up-icon name="arrow-left" bold color="#F0D09C" size="22"></up-icon>
-					</view>
+					</view>
 					<view class="arrow right">
 						<up-icon name="arrow-right" bold color="#F0D09C" size="22"></up-icon>
-					</view>
-				</view>
-			</view>
-		</view>
-	</up-popup>
-</template>
-
-<script setup>
-	import config from "@/api/config.js"
-	import {
-		ref,
-		watch
-	} from "vue";
-
-	const props = defineProps({
-		show: {
-			type: Boolean,
-			defalut: false,
-		},
+					</view> -->
+				</view>
+			</view>
+		</view>
+	</up-popup>
+</template>
+
+<script setup>
+    import config from "@/api/config.js"
+    import TREE from '@/api/tree.js'
+	import {
+		ref,
+		watch,
+		nextTick
+	} from "vue";
+	const resize = "?x-oss-process=image/resize,w_1000";
+
+	const props = defineProps({
+		show: {
+			type: Boolean,
+			defalut: false,
+		},
+        farmBuyId: {
+            type: [String, Number],
+            default: ''
+        },
+        treeId: {
+            type: [String, Number],
+            default: 110939
+        }
 	});
 	
 	const showPopup = ref(false);
 	const handleClose = ()=>{
 		showPopup.value = false
-	}
-	const list1 = ref([
-		'https://cdn.uviewui.com/uview/album/1.jpg',
-		'https://cdn.uviewui.com/uview/album/2.jpg',
-		'https://cdn.uviewui.com/uview/album/3.jpg',
-	]);
+	}
+    const dateList = ref([])
+    const scrollIntoViewId = ref('')
+    const selectedDateStr = ref('')
+
+    const formatToDisplay = (dateStr)=>{
+        // dateStr: YYYY-MM-DD
+        const [y,m,d] = dateStr.split('-')
+        return `${m}/${d}`
+    }
+    const getTodayStr = ()=>{
+        const d = new Date()
+        const mm = `${d.getMonth()+1}`.padStart(2,'0')
+        const dd = `${d.getDate()}`.padStart(2,'0')
+        return `${d.getFullYear()}-${mm}-${dd}`
+    }
+    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){
+            // 静默失败
+        }
+    }
+    const fetchTreeImages = async (dateStr)=>{
+        try{
+            const params = {
+                page: 1,
+                limit: 1,
+                treeId: Number(props.treeId) || 110939,
+                date: dateStr,
+            }
+            const {data} = await TREE.treeImageList(params)
+			photoList.value = data || []
+        }catch(err){
+            console.log('treeImageList error:', err)
+        }
+    }
+	const getImageUrl = (filename) => {
+	    if (filename.startsWith("https")) {
+	        return filename; // 直接使用完整 URL
+	    } else {
+	        return config.BASE_IMG_URL + filename + resize; // 拼接基础 URL
+	    }
+	};
 	
-	watch(
-		() => props.show,
-		() => {
-			showPopup.value = true;
-		}
-	);
-</script>
-
-<style lang="scss" scoped>
-	.album-popup {
-		width: 92vw;
-
-		.album-title {
-			padding: 30rpx 0 46rpx 30rpx;
-			position: relative;
-			background-image: linear-gradient(120deg, #79C4FF 13%, #FFFFFF 66%);
-			border-radius: 40rpx 40rpx 0 0;
-			line-height: 44rpx;
-
-			.name {
-				color: #004275;
-				font-size: 48rpx;
-				font-family: 'PangMenZhengDao';
-
-				.sub-name {
-					font-size: 24rpx;
-				}
-			}
-
-			.icon {
-				position: absolute;
-				top: -100rpx;
-				right: -26rpx;
-				width: 364rpx;
-				height: 300rpx;
-			}
-		}
-
-		.album-cont {
-			position: relative;
-			z-index: 2;
-			margin-top: -34rpx;
-			padding: 32rpx 0 20rpx;
-			background: #fff;
-			border-radius: 40rpx;
-			.time-line{
-				margin-bottom: 16rpx;
-				display: flex;
-				.time-item{
-					flex: 1;
+    const handleDateClick = (dateStr)=>{
+        selectedDateStr.value = dateStr
+        scrollIntoViewId.value = `d-${dateStr}`
+        fetchTreeImages(dateStr)
+    }
+	const photoList = ref([]);
+	
+    watch(
+        () => props.show,
+        async (val) => {
+            showPopup.value = val;
+            if(val){
+                await fetchHasImageDates()
+                const today = getTodayStr()
+                fetchTreeImages(today)
+            }
+        }
+    );
+    watch(
+        () => props.farmBuyId,
+        (val) => {
+            if(showPopup.value && val){
+                fetchHasImageDates()
+            }
+        }
+    )
+</script>
+
+<style lang="scss" scoped>
+	.album-popup {
+		width: 92vw;
+
+		.album-title {
+			padding: 30rpx 0 46rpx 30rpx;
+			position: relative;
+			background-image: linear-gradient(120deg, #79C4FF 13%, #FFFFFF 66%);
+			border-radius: 40rpx 40rpx 0 0;
+			line-height: 44rpx;
+
+			.name {
+				color: #004275;
+				font-size: 48rpx;
+				font-family: 'PangMenZhengDao';
+
+				.sub-name {
 					font-size: 24rpx;
-					color: #777777;
-					display: flex;
-					flex-direction: column;
-					align-items: center;
-					position: relative;
-					&::before{
-						content: '';
-						position: absolute;
-						top: 44%;
-						width: 100%;
-						height: 2rpx;
-						background: rgba(136, 136, 136, 0.1);
-					}
-					
-					.dot{
-						width: 14rpx;
-						height: 14rpx;
-						background: #777777;
-						border-radius: 50%;
-					}
-					.today{
-						color: #2199F8;
-						margin-top: 8rpx;
-					}
 				}
-			}
+			}
+
+			.icon {
+				position: absolute;
+				top: -100rpx;
+				right: -26rpx;
+				width: 364rpx;
+				height: 300rpx;
+			}
+		}
+
+        .album-cont {
+			position: relative;
+			z-index: 2;
+			margin-top: -34rpx;
+			padding: 32rpx 0 20rpx;
+			background: #fff;
+			border-radius: 40rpx;
+            .time-line-scroll{
+                margin-bottom: 16rpx;
+                width: 100%;
+                white-space: nowrap;
+            }
+            .time-line{
+                display: flex;
+                padding: 0 20rpx;
+            }
+            .time-item{
+                min-width: 100rpx;
+                font-size: 24rpx;
+                color: #777777;
+                display: flex;
+                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);
+                }
+                .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;
@@ -147,7 +259,7 @@
 						height: 100%;
 						border-radius: 16rpx;
 					}
-				}
+				}
 				.arrow{
 					position: absolute;
 					top: calc(50% - 40rpx);
@@ -165,7 +277,7 @@
 				.right{
 					right: 32rpx;
 				}
-			}
-		}
-	}
+			}
+		}
+	}
 </style>

+ 107 - 51
pages/tabBar/tree/subPages/dynamic.vue

@@ -2,8 +2,8 @@
 	<view class="sub-base-container">
 		<view class="messgae-list" v-for="(item,index) in messageList" :key="index">
 			<text class="date">{{item.date}}</text>
-			<view class="message-item" v-for="(ele,idx) in item.list" :key="idx">
-				<view class="messgae-info">
+			<view class="message-item" v-for="(ele,idx) in item.list" :key="idx">
+				<view class="messgae-info">
 					<view class="info-text">
 						<up-image class="avatar" :fade="false" :src="ele.icon" width="56rpx" height="56rpx"
 							shape="circle"></up-image>
@@ -13,11 +13,12 @@
 						<!-- <view><text class="my">励志人生</text> 向 <text class="name">茜茜荔</text> 留言 </view> -->
 					</view>
 					<!-- <text class="time">{{ele.createTime}}</text> -->
-				</view>
+				</view>
 				<view class="time">{{ele.createTime}}</view>
-				<text class="message-text">{{ele.desc}}</text>
+				<text class="message-text">{{ele.desc}}</text>
 			</view>
 		</view>
+		<up-loadmore :status="loadStatus" />
 	</view>
 </template>
 
@@ -27,20 +28,24 @@
 	} from 'vue';
 	import TREE from '@/api/tree.js'
 	import {
-		onLoad
+		onLoad,
+		onReachBottom,
+		onPullDownRefresh
 	} from '@dcloudio/uni-app'
 
-	const messageList = ref([{
-		date: '今天',
-		list: []
-	}])
+	const messageList = ref([])
 	const farmBuyIdData = ref('')
+	const page = ref(1)
+	const limit = 10
+	const isLoading = ref(false)
+	const hasMore = ref(true)
+	const loadStatus = ref('loadmore') // loading | loadmore | nomore
 
 	onLoad(({
 		farmBuyId
 	}) => {
 		farmBuyIdData.value = farmBuyId
-		getMessageList()
+		fetchMessageList({ refresh: true })
 	})
 
 	const clockinTypeStr = {
@@ -49,47 +54,98 @@
 		"3": "每日祝福"
 	}
 
-	const getMessageList = () => {
+	const fetchMessageList = ({ refresh = false } = {}) => {
+		if (isLoading.value) return
+		if (refresh) {
+			page.value = 1
+			hasMore.value = true
+		}
+		if (!hasMore.value) return
+		isLoading.value = true
+		loadStatus.value = 'loading'
 		const params = {
-			page: 1,
-			limit: 10,
+			page: page.value,
+			limit,
 			farmBuyId: farmBuyIdData.value
 		}
-		TREE.getEnergyRecords(params).then(res => {
-			messageList.value[0].list = res.data || []
+		TREE.getEnergyRecords(params)
+			.then(res => {
+				const list = (res?.data || []).map((it) => {
+					const label = formatDateLabel(it)
+					return { ...it, dateClass: label }
+				})
+				const grouped = groupByDateLabel(list)
+				if (refresh) {
+					messageList.value = grouped
+				} else {
+					mergeGroups(messageList.value, grouped)
+				}
+				if (list.length < limit) {
+					hasMore.value = false
+					loadStatus.value = 'nomore'
+				} else {
+					page.value += 1
+					loadStatus.value = 'loadmore'
+				}
+			})
+			.finally(() => {
+				isLoading.value = false
+				if (refresh) {
+					uni.stopPullDownRefresh()
+				}
+			})
+	}
+
+	const groupByDateLabel = (records) => {
+		const map = new Map()
+		records.forEach((rec) => {
+			const key = rec.dateClass
+			if (!map.has(key)) map.set(key, [])
+			map.get(key).push(rec)
+		})
+		return Array.from(map.entries()).map(([date, list]) => ({ date, list }))
+	}
+
+	const mergeGroups = (targetGroups, incomingGroups) => {
+		incomingGroups.forEach((grp) => {
+			const exist = targetGroups.find((g) => g.date === grp.date)
+			if (exist) {
+				exist.list = exist.list.concat(grp.list)
+			} else {
+				targetGroups.push({ date: grp.date, list: grp.list })
+			}
 		})
 	}
-	// const messageList = [{
-	// 		date: '今天',
-	// 		list: [{
-	// 				name: '茜茜荔',
-	// 				date: '11:08',
-	// 				desc: '记住,每一次挫折都是成功的垫脚石记住,每一次挫折都是成功的垫脚石'
-	// 			},
-	// 			{
-	// 				name: '茜茜荔',
-	// 				date: '11:08',
-	// 				desc: '记住,每一次挫折都是成功的垫脚石记住,每一次挫折都是成功的垫脚石'
-	// 			}
-	// 		]
-	// 	},
-	// 	{
-	// 		date: '昨天',
-	// 		list: [{
-	// 			name: '茜茜荔',
-	// 			date: '11:08',
-	// 			desc: '记住,每一次挫折都是成功的垫脚石记住,每一次挫折都是成功的垫脚石'
-	// 		}]
-	// 	},
-	// 	{
-	// 		date: '07/30',
-	// 		list: [{
-	// 			name: '茜茜荔',
-	// 			date: '11:08',
-	// 			desc: '记住,每一次挫折都是成功的垫脚石记住,每一次挫折都是成功的垫脚石'
-	// 		}]
-	// 	}
-	// ]
+
+	const formatDateLabel = (item) => {
+		const src = item?.createTime || item?.dateClass || ''
+		const d = parseToDate(src)
+		if (!d) return (typeof item?.dateClass === 'string' && item.dateClass) ? item.dateClass : ''
+		const today = new Date()
+		const startOfToday = new Date(today.getFullYear(), today.getMonth(), today.getDate())
+		const startOfThatDay = new Date(d.getFullYear(), d.getMonth(), d.getDate())
+		const diffDays = Math.round((startOfToday - startOfThatDay) / 86400000)
+		if (diffDays === 0) return '今天'
+		if (diffDays === 1) return '昨天'
+		return `${pad2(d.getMonth() + 1)}/${pad2(d.getDate())}`
+	}
+
+	const parseToDate = (str) => {
+		if (!str || typeof str !== 'string') return null
+		const normalized = str.replace(/-/g, '/').replace(/\.\d{3}.*/, '')
+		const d = new Date(normalized)
+		return isNaN(d.getTime()) ? null : d
+	}
+
+	const pad2 = (n) => (n < 10 ? `0${n}` : `${n}`)
+
+	onReachBottom(() => {
+		fetchMessageList()
+	})
+
+	onPullDownRefresh(() => {
+		fetchMessageList({ refresh: true })
+	})
 </script>
 
 <style lang="scss" scoped>
@@ -103,11 +159,11 @@
 				margin-top: 24rpx;
 				background: #fff;
 				border-radius: 16rpx;
-				padding: 24rpx 32rpx;
-				.time {
-					font-size: 28rpx;
-					color: #999999;
-					margin-left: 70rpx;
+				padding: 24rpx 32rpx;
+				.time {
+					font-size: 28rpx;
+					color: #999999;
+					margin-left: 70rpx;
 				}
 
 				.messgae-info {

+ 180 - 143
pages/tabBar/tree/tree.vue

@@ -1,10 +1,15 @@
 <template>
-	<view class="base-container">
+	<view>
+		<view class="base-container">
 		<member-level :treeData="treeData">
 			<view class="toogle" @click="handleShow">切换 {{name}}<up-icon class="icon" name="arrow-down" color="#fff"
-					size="12"></up-icon></view>
-		</member-level>
-		<view class="tree-cont">
+					size="12"></up-icon></view>
+		</member-level>
+		<view class="tree-cont">
+			<view class="tree-name">
+				<view>{{treeName}}</view>
+				<image @click="handleEditName" class="edit-icon" :src="`${config.BASIC_IMG}img/edit-icon.png`"></image>
+			</view>
 			<image class="drone-icon" :src="`${config.BASIC_IMG}img/treePage/drone-icon.png`"></image>
 			<view class="tool-wrap">
 				<view class="tool-left">
@@ -24,40 +29,43 @@
 			</view>
 		</view>
 		<view class="tree-footer">
-			<view class="footer-item" v-for="(item,index) in footerList" :key="index" @click="handleItem(index)">
-				<view @click="requestSubscribe">
-					<button class="share-btn" open-type="share" v-if="index === 2">
-						<image class="icon" :src="`${config.BASIC_IMG}img/treePage/b-tree-icon-${index+1}.png`"></image>
-					</button>
-					<image v-else class="icon" :src="`${config.BASIC_IMG}img/treePage/b-tree-icon-${index+1}.png`"></image>
-					<view class="name">{{item}}</view>
+			<view class="footer-item" v-for="(item,index) in footerList" :key="index" @click="handleItem(index)">
+				<view @click="requestSubscribe">
+					<button class="share-btn" open-type="share" v-if="index === 2">
+						<image class="icon" :src="`${config.BASIC_IMG}img/treePage/b-tree-icon-${index+1}.png`"></image>
+					</button>
+					<image v-else class="icon" :src="`${config.BASIC_IMG}img/treePage/b-tree-icon-${index+1}.png`"></image>
+					<view class="name">{{item}}</view>
 				</view>
 			</view>
 		</view>
-	</view>
+		</view>
 	<!-- 切换 -->
 	<up-picker :show="showPicker" :columns="columns" :defaultIndex="[0]" @cancel="handleCancel"
 		@confirm="handleConfirm"></up-picker>
+	<!-- 签到打卡 -->
+	<checkinPopup></checkinPopup>
 	<!-- 编辑树名称 -->
-	<editNamePopup></editNamePopup>
-	<!-- 海报弹窗 -->
-	<posterPopup :showPoster="showPoster" :farmBuyId="farmBuyId" :treeName="treeName"></posterPopup>
-	<!-- 果树成功弹窗 -->
-	<guardSuccessPopup :show="showGuardSuccess"></guardSuccessPopup>
-	<!-- 果树相册弹窗 -->
-	<tree-album-popup :show="showAlbum"></tree-album-popup>
-	<!-- 等级升级成功弹窗 -->
-	<levelSuccessPopup></levelSuccessPopup>
-	<!-- 祝福弹窗 -->
+	<editNamePopup ref="editNameRef" @editEnd="getBySampleId"></editNamePopup>
+	<!-- 海报弹窗 -->
+	<posterPopup :showPoster="showPoster" :farmBuyId="farmBuyId" :treeName="treeName"></posterPopup>
+	<!-- 果树成功弹窗 -->
+	<guardSuccessPopup :show="showGuardSuccess"></guardSuccessPopup>
+    <!-- 果树相册弹窗 -->
+    <tree-album-popup :show="showAlbum" :farmBuyId="farmBuyId"></tree-album-popup>
+	<!-- 等级升级成功弹窗 -->
+	<levelSuccessPopup></levelSuccessPopup>
+	<!-- 祝福弹窗 -->
 	<blessingsPopup :show="showBlessingsPopup" :showSuccess="showSuccess" :farmBuyId="farmBuyId" :clockinType="clockinType" @clockinCallback="getBySampleId"></blessingsPopup>
+	</view>
 </template>
 
 <script setup>
-	import config from "@/api/config.js"
+	import config from "@/api/config.js"
 	import { onLoad ,onShareAppMessage} from '@dcloudio/uni-app'
-	import memberLevel from "./components/memberLevel.vue"
-	import treeAlbumPopup from "./components/treeAlbumPopup.vue"
-	import blessingsPopup from "./components/blessingsPopup.vue"
+	import memberLevel from "./components/memberLevel.vue"
+	import treeAlbumPopup from "./components/treeAlbumPopup.vue"
+	import blessingsPopup from "./components/blessingsPopup.vue"
 	import TREE from '@/api/tree.js'
 	import {
 		ref,
@@ -78,19 +86,19 @@
 	const handleConfirm = (e) => {
 		name.value = e.value[0]
 		handleCancel()
-	}
+	}
 
 	const toolLeftList = [{
-			name: "相册",
+			name: "相册",
 			clickName:'album'
 		},
 		{
-			name: "日记",
+			name: "日记",
 			path: 'diary'
 		},
 		{
 			name: "海报",
-			className: 'blue',
+			className: 'blue',
 			clickName:'poster'
 		},
 		{
@@ -113,123 +121,137 @@
 		{
 			name: "动态",
 			className: 'dynamic',
-			path: 'dynamic',
+			path: 'dynamic',
 			params:'farmBuyId'
 		}
-	]
-	
-	const showGuardSuccess = ref(false)
-	
-	onLoad(({successTree})=>{
-		if(successTree){
-			showGuardSuccess.value = true
-		}
-		getBySampleId()
-	})
-	
-	const treeData = ref({})
-	const farmBuyId = ref('')
-	const treeName = ref('')
-	const userInfo = uni.getStorageSync('userInfo')
-	const getBySampleId = () =>{
-		TREE.getBySampleId({sampleId:172055}).then(({data}) =>{
-			treeData.value = data || {}
-			if(userInfo.tel){
-				treeName.value = data.buyList[0].treeName || (data.buyList[0].nickname.length?data.buyList[0].nickname.slice(0, 3)+"荔": data.buyList[0].owner.slice(0, 3)+"荔")
-			}else{
-				treeName.value = '飞鸟守护'
-			}
-			farmBuyId.value = data.buyList[0].id
-		})
+	]
+	
+	const showGuardSuccess = ref(false)
+	
+	const editNameRef = ref(null)
+	const formatDate = (dateStr) => {
+	    return dateStr.split(" ")[0].replace(/-/g, ".");
+	};
+	const handleEditName = () =>{
+		editNameRef.value.showPopup({
+			id: farmBuyId.value,
+			treeName: treeName.value,
+			nickname: treeData.value.buyList[0].nickname,
+			showName: treeData.value.buyList[0].showName,
+			createDate: formatDate(treeData.value.buyList[0].createDate),
+		})
+	}
+	
+	onLoad(({successTree})=>{
+		if(successTree){
+			showGuardSuccess.value = true
+		}
+		getBySampleId()
+	})
+	
+	const treeData = ref({})
+	const farmBuyId = ref('')
+	const treeName = ref('')
+	const userInfo = uni.getStorageSync('userInfo')
+	const getBySampleId = () =>{
+		TREE.getBySampleId({sampleId:172055}).then(({data}) =>{
+			treeData.value = data || {}
+			if(userInfo.tel){
+				treeName.value = data.buyList[0].treeName || (data.buyList[0].nickname.length?data.buyList[0].nickname.slice(0, 3)+"荔": data.buyList[0].owner.slice(0, 3)+"荔")
+			}else{
+				treeName.value = '飞鸟守护'
+			}
+			farmBuyId.value = data.buyList[0].id
+		})
 	}
-	
-	const showPoster = ref(false)
+	
+	const showPoster = ref(false)
 	const showAlbum = ref(false)
 	const handleToolItem = ({
 		path,clickName,params
-	}) => {
-		if(clickName === 'album'){
-			showAlbum.value = !showAlbum.value
-		}else if(clickName === 'poster'){
-			showPoster.value = !showPoster.value
-		}else{
+	}) => {
+		if(clickName === 'album'){
+			showAlbum.value = !showAlbum.value
+		}else if(clickName === 'poster'){
+			showPoster.value = !showPoster.value
+		}else{
 			uni.navigateTo({
 				url: `/pages/tabBar/tree/subPages/${path}?farmBuyId=${farmBuyId.value}`
 			});
 		}
 	}
-	const footerList = ["每日阳光", "送ta祝福", "分享转发", "水果订购"]
-	const showBlessingsPopup = ref(false)
-	const showSuccess = ref(false)
-	const clockinType = ref('1')
-	const handleItem = (index) =>{
-		if(index === 0){
-			clockinType.value = 1
-			if(treeData.value.buyList[0].level.clockinMap['1']){
-				uni.showToast({
-					title: '今日已守护',
-					icon:'none',
-					duration: 2000
-				});
-			}else{
-				showSuccess.value = !showSuccess.value
-			}
-		}else if(index === 1){
-			clockinType.value = 3
-			if(treeData.value.buyList[0].level.clockinMap['3']){
-				uni.showToast({
-					title: '今日已送过祝福',
-					icon:'none',
-					duration: 2000
-				});
-			}else{
-				showBlessingsPopup.value = !showBlessingsPopup.value
-			}
-		}else if(index === 2){
-			clockinType.value = 2
-			if(!treeData.value.buyList[0].level.clockinMap['2']){
-				showSuccess.value = !showSuccess.value
-			}
-		}else{
-			console.log('123')
-		}
-	}
-	
-	function requestSubscribe() {
-		// #ifdef MP-WEIXIN
-	    //订阅模板
-	    TREE.getSubscribeTemplate({id:1}).then(({data}) =>{
-	      uni.requestSubscribeMessage({
-	        tmplIds: [data.templateId], // 模板ID
-	        success(res) {
-	          if (res[data.templateId] === 'accept') {
-	            TREE.addSubscribe({templateId:1})
-	          } else if (res[data.templateId] === 'reject') {
-	            console.log('用户拒绝订阅模板');
-	          }
-	        },
-	        fail(err) {
-	          uni.openSetting({
-	            withSubscriptions: true, // 显示订阅消息开关
-	          });
-	        },
-	      });
-	    })
-		// #endif
-	}
-	
-	onShareAppMessage((res) => {
-	  if (res.from === 'button') {
-		  const params = {
-		    sampleId:treeData.value.buyList[0].sampleId,
-		    farmId:treeData.value.buyList[0].farmId,
-		  }
-		  return {
-		    title: '我分享了我的果树,快来查看吧~',
-		    path: `/pages/tabBar/tree/subPages/friendTree?params=${JSON.stringify(params)}`, // 分享的小程序页面路径  
-		    imageUrl:`http://birdseye-api.feiniaotech.sysuimars.cn/mini/z_farm_buy/genImage/${farmBuyId.value}?key=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9&x1=25&y1=220&fontSize1=40&x2=55&y2=250&fontSize2=16&timestamp=${Date.now()}`,
-		  }
-	  }
+	const footerList = ["每日阳光", "送ta祝福", "分享转发", "水果订购"]
+	const showBlessingsPopup = ref(false)
+	const showSuccess = ref(false)
+	const clockinType = ref('1')
+	const handleItem = (index) =>{
+		if(index === 0){
+			clockinType.value = 1
+			if(treeData.value.buyList[0].level.clockinMap['1']){
+				uni.showToast({
+					title: '今日已守护',
+					icon:'none',
+					duration: 2000
+				});
+			}else{
+				showSuccess.value = !showSuccess.value
+			}
+		}else if(index === 1){
+			clockinType.value = 3
+			if(treeData.value.buyList[0].level.clockinMap['3']){
+				uni.showToast({
+					title: '今日已送过祝福',
+					icon:'none',
+					duration: 2000
+				});
+			}else{
+				showBlessingsPopup.value = !showBlessingsPopup.value
+			}
+		}else if(index === 2){
+			clockinType.value = 2
+			if(!treeData.value.buyList[0].level.clockinMap['2']){
+				showSuccess.value = !showSuccess.value
+			}
+		}else{
+			console.log('123')
+		}
+	}
+	
+	function requestSubscribe() {
+		// #ifdef MP-WEIXIN
+	    //订阅模板
+	    TREE.getSubscribeTemplate({id:1}).then(({data}) =>{
+	      uni.requestSubscribeMessage({
+	        tmplIds: [data.templateId], // 模板ID
+	        success(res) {
+	          if (res[data.templateId] === 'accept') {
+	            TREE.addSubscribe({templateId:1})
+	          } else if (res[data.templateId] === 'reject') {
+	            console.log('用户拒绝订阅模板');
+	          }
+	        },
+	        fail(err) {
+	          uni.openSetting({
+	            withSubscriptions: true, // 显示订阅消息开关
+	          });
+	        },
+	      });
+	    })
+		// #endif
+	}
+	
+	onShareAppMessage((res) => {
+	  if (res.from === 'button') {
+		  const params = {
+		    sampleId:treeData.value.buyList[0].sampleId,
+		    farmId:treeData.value.buyList[0].farmId,
+		  }
+		  return {
+		    title: '我分享了我的果树,快来查看吧~',
+		    path: `/pages/tabBar/tree/subPages/friendTree?params=${JSON.stringify(params)}`, // 分享的小程序页面路径  
+		    imageUrl:`http://birdseye-api.feiniaotech.sysuimars.cn/mini/z_farm_buy/genImage/${farmBuyId.value}?key=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9&x1=25&y1=220&fontSize1=40&x2=55&y2=250&fontSize2=16&timestamp=${Date.now()}`,
+		  }
+	  }
 	})
 </script>
 
@@ -260,6 +282,21 @@
 		.tree-cont {
 			width: 100%;
 			margin-top: 10rpx;
+			.tree-name{
+				position: absolute;
+				z-index: 2;
+				top: 60.45%;
+				left: calc(50% - 32rpx);
+				font-size: 22rpx;
+				font-family: 'SweiSpringCJKtc';
+				text-align: center;
+				width: 88rpx;
+				.edit-icon{
+					width: 44rpx;
+					height: 44rpx;
+					margin-top: 14rpx;
+				}
+			}
 
 			.drone-icon {
 				width: 376rpx;
@@ -356,13 +393,13 @@
 
 			.footer-item {
 				width: 18%;
-				text-align: center;
-				.share-btn{
-					background: transparent;
-					display: inline-flex;
-					&::after{
-						border: none;
-					}
+				text-align: center;
+				.share-btn{
+					background: transparent;
+					display: inline-flex;
+					&::after{
+						border: none;
+					}
 				}
 
 				.icon {