Browse Source

接口联调

htc 3 weeks ago
parent
commit
2689a50775

+ 8 - 0
App.vue

@@ -78,5 +78,13 @@
 	  flex-direction: column;
 	  align-items: center;
 	  justify-content: space-between;
+	}
+	
+	.dataEmpty{
+		flex: 1;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: center;
 	}
 </style>

+ 3 - 3
common/api/baseApi.js

@@ -1,6 +1,6 @@
-// const BaseApi = 'https://transcend.ringzle.com/chuangheng-app/app' //线上
-const BaseApi = 'https://wxapp.transcend-intl.cn/chuangheng-app/app' //生产
-// const BaseApi = 'http://192.168.2.19:9023/chuangheng-app/app' //李勇
+// const BaseApi = 'https://xxx/happytree-app/app' //线上
+// const BaseApi = 'https://xxx/happytree-app/app' //生产
+const BaseApi = 'http://192.168.2.19:9013/happytree-app/app' //李勇
 
 export {
 	BaseApi

+ 37 - 28
common/stores/user.js

@@ -1,8 +1,9 @@
-import { defineStore } from 'pinia';
+import { defineStore } from 'pinia';
+import api from '@/common/api/index.js'
 
 export const useUserStore = defineStore('user', {
 	state: () => ({
-		isRegister: false,     // 是否注册成功
+		isRegister: true,     // 是否注册成功
 		isLogin: false,        // 用户是否已登录
 		showLoginModal: false, // 是否显示登录弹框
 		userInfo: null,        // 用户信息
@@ -21,41 +22,49 @@ export const useUserStore = defineStore('user', {
 
 		// 关闭登录弹框
 		closeLoginModal() {
-			this.isRegister = false;
 			this.showLoginModal = false;
 		},
 		
 		async register(userFrom) {
-			await new Promise(resolve => setTimeout(resolve, 1000));
-			this.isRegister = true;
-			
-			uni.showToast({
-				title: '注册成功',
-				icon: 'success'
-			});
+			await new Promise(resolve => {
+				api.post('/wx/register',userFrom).then(({data:res})=>{
+					if(res.code !== 0){
+						return uni.showToast({
+							title: res.msg
+						})
+					}  
+					
+					this.showLoginModal = false;
+					this.isRegister = true;
+					uni.showToast({
+						title: '注册成功',
+						icon: 'success'
+					});
+				})
+			});
 		},
 
-		// 模拟登录操作
 		async login(loginDto) {
-			console.log('正在登录...', loginDto);
-			
-			// --- 这里应该是调用你的后端 API ---
-			// 模拟一个异步请求
-			await new Promise(resolve => setTimeout(resolve, 1000));
-			
-			// 模拟登录成功
-			this.isLogin = true;
-			this.userInfo = { name: '张三', token: 'fake-token-string' };
-			
-			// 登录成功后关闭弹框
-			this.closeLoginModal();
-			
-			uni.showToast({
-				title: '登录成功',
-				icon: 'success'
+			await new Promise(resolve => {
+				api.get('/wx/login',loginDto).then(({data:res})=>{
+					if(res.code !== 0){
+						return uni.showToast({
+							title: res.msg
+						})
+					}  
+					
+					this.showLoginModal = false;
+					this.isLogin = true;
+					this.userInfo = res.data;
+					uni.setStorageSync('token',this.userInfo?.token)
+					uni.setStorageSync('userInfo',JSON.stringify(this.userInfo))
+					uni.showToast({
+						title: '登录成功',
+						icon: 'success'
+					});
+				})
 			});
 		},
-
 		// 登出操作
 		logout() {
 			this.isRegister = false;

+ 5 - 2
components/CusTabbar/index.vue

@@ -8,7 +8,9 @@
 </template>
 
 <script setup>
-	import { ref, onMounted, watch } from 'vue';
+	import { ref, onMounted, watch } from 'vue';
+	import { useGlobalShare } from '@/common/composables/useGlobalShare';
+	const { isLogin } = useGlobalShare();
 
 	const props = defineProps({
 		tabbarIndex: {
@@ -47,7 +49,8 @@
 		tabbarValue.value = props.tabbarIndex;
 	});
 
-	const changeTabbar = (e) => {
+	const changeTabbar = (e) => {
+		if(e===1&&!isLogin()) return
 		tabbarValue.value = e;
 		uni.reLaunch({
 			url: list.value[e].path

+ 12 - 27
components/pageEmpty/index.vue

@@ -1,39 +1,24 @@
 <template>
-	<view class="page-empty" :style="{ 'height': height, 'margin-top': marginTop }">
-		<u-empty 
-			text="暂无数据" 
-			textSize="26rpx" 
-			width="332rpx" 
-			height="332rpx" 
-			mode="order" 
-			:icon="''"
-		></u-empty>
-	</view>
+	<u-empty 
+		:text="text" 
+		textSize="26rpx" 
+		width="332rpx" 
+		height="332rpx" 
+		mode="order" 
+		:icon="iconUrl"
+	></u-empty>
 </template>
 
 <script setup>
 	// 定义组件属性
 	const props = defineProps({
-		height: {
+		text: {
 			type: String,
-			default: '100vh'
+			default: '暂无数据'
 		},
-		marginTop: {
-			type: String,
-			default: '0px'
-		},
-		imgBase: {
+		iconUrl: {
 			type: String,
 			default: ''
 		}
 	})
-</script>
-
-<style scoped lang="scss">
-	.page-empty {
-		width: 100%;
-		display: flex;
-		align-items: center;
-		justify-content: center;
-	}
-</style>
+</script>

+ 45 - 18
components/pages/loginRegister/index.vue

@@ -17,17 +17,28 @@
 						<up-input v-model="register.phone" border="none" placeholder="请输入手机号码"></up-input>
 					</div>
 					<div class="lr-box-form-item-right">
-						<button class="yjsq" open-type="getPhoneNumber" @getphonenumber="decryptPhoneNumber">一键授权</button>
+						<button class="yjsq" open-type="getPhoneNumber" @getphonenumber="decryptPhoneNumber" privacy-desc="用于获取您的手机号码,方便您快速注册。">一键授权</button>
 					</div>
 				</div>
 				<div class="lr-box-form-item adfacjb">
 					<div class="lr-box-form-item-left adfac">
 						<div class="text"><span style="color: #F4657A;">*</span>头像</div>
-						<image src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/09/12/a2944f51-2c7b-41e7-8206-8a3be1f76d11.png" v-if="!register.avatar"></image>
-						<image :src="register.avatar"></image>
+						<!-- <image src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/09/12/a2944f51-2c7b-41e7-8206-8a3be1f76d11.png" v-if="!register.avatarPath"></image>
+						<image :src="register.avatarPath"></image> -->
+						<up-upload
+							:fileList="fileList"
+							@afterRead="afterRead"
+							@delete="deletePic"
+							:maxCount="1"
+							width="120rpx"
+							height="120rpx"
+						>
+							<image src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/09/12/a2944f51-2c7b-41e7-8206-8a3be1f76d11.png" 
+							mode="widthFix" style="width: 120rpx;height: 120rpx;"></image>
+						</up-upload>
 					</div>
 					<div class="lr-box-form-item-right">
-						<button class="yjsq" open-type="chooseAvatar" @bindchooseavatar="onChooseAvatar">设置</button>
+						<button class="yjsq" open-type="chooseAvatar" @chooseavatar="onChooseAvatar">设置</button>
 					</div>
 				</div>
 				<div class="lr-box-form-item adfacjb">
@@ -35,7 +46,7 @@
 						<div class="text"><span style="color: #F4657A;">*</span>用户名</div>
 					</div>
 					<div class="lr-box-form-item-right">
-						<up-input v-model="register.userName" border="none" inputAlign="right" placeholder="请输入真实姓名"></up-input>
+						<up-input v-model="register.nickName" border="none" inputAlign="right" placeholder="请输入真实姓名"></up-input>
 					</div>
 				</div>
 				<div class="lr-box-form-item adfacjb">
@@ -43,7 +54,7 @@
 						<div class="text">家庭公益名称</div>
 					</div>
 					<div class="lr-box-form-item-right">
-						<up-input v-model="register.nonprofitName" border="none" inputAlign="right" placeholder="请输入至少三个字"></up-input>
+						<up-input v-model="register.welfareName" border="none" inputAlign="right" placeholder="请输入至少三个字"></up-input>
 					</div>
 				</div>
 				<div class="lr-box-form-item adfacjb">
@@ -51,7 +62,7 @@
 						<div class="text">家庭公益口号</div>
 					</div>
 					<div class="lr-box-form-item-right">
-						<up-input v-model="register.nonprofitNumber" border="none" inputAlign="right" placeholder="家庭公益口号"></up-input>
+						<up-input v-model="register.welfareSlogan" border="none" inputAlign="right" placeholder="家庭公益口号"></up-input>
 					</div>
 				</div>
 			</div>
@@ -73,7 +84,7 @@
 				<div class="text">我已阅读并同意<span>《善行少年小程序隐私政策》</span>及<span>《善行少年服务协议》</span></div>
 			</div>
 			<div class="lr-box-login">
-				<button class="phone-login" open-type="getPhoneNumber" @getphonenumber="decryptPhoneNumberLogin">手机号登录</button>
+				<button class="phone-login" open-type="getPhoneNumber" @getphonenumber="decryptPhoneNumberLogin" privacy-desc="用于获取您的手机号码,方便您进行登录。">手机号登录</button>
 			</div>
 		</div>
 	</view>
@@ -87,12 +98,13 @@
 	
 	const register = ref({
 		phone:null,
-		avatar:null,
-		userName:null,
-		nonprofitName:null,
-		nonprofitNumber:null,
+		avatarPath:null,
+		nickName:null,
+		welfareName:null,
+		welfareSlogan:null,
 	})
 	const agree = ref(false)
+	const fileList = ref([])
 	
 	const close = () => {
 		userStore.closeLoginModal();
@@ -103,19 +115,22 @@
 	}
 	
 	const onChooseAvatar = e => {
-		
+		register.value.avatarPath = e.detail.avatarUrl;
 	}
 	
 	const getPhone = code => {
-		
+		proxy.$api.get(`/wx/getPhone/${code}`).then(({data:res})=>{
+			if(res.code !== 0) return proxy.$showToast(res.msg)
+			register.value.phone = res.data;
+		})
 	}
 	
 	const toRegister = () => {
 		if(!proxy.$reg.mobile(register.value.phone)) return proxy.$showToast('请输入正确的手机号码')
-		if(!register.value.avatar) return proxy.$showToast('请设置头像')
-		if(!register.value.userName) return proxy.$showToast('请输入真实姓名')
+		if(!register.value.avatarPath) return proxy.$showToast('请设置头像')
+		if(!register.value.nickName) return proxy.$showToast('请输入真实姓名')
 		
-		userStore.register();
+		userStore.register(register.value);
 	}
 	
 	const readLogin = () => {
@@ -126,6 +141,13 @@
 		agree.value = !agree.value;
 	}
 	
+	const afterRead = e => {
+		
+	}
+	const deletePic = event => {
+		fileList.value.splice(event.index, 1);
+		register.value.avatarPath = '';
+	}
 	
 	const decryptPhoneNumberLogin = e => {
 		if(e.detail.code) toPhoneLogin(e.detail.code);
@@ -133,7 +155,11 @@
 	
 	const toPhoneLogin = code => {
 		if(!agree.value) return proxy.$showToast('请勾选隐私政策和服务协议')
-		userStore.login({});
+		wx.login({
+			success(res){
+				userStore.login({code:res.code,phoneCode:code});
+			}
+		})
 	}
 </script>
 
@@ -223,6 +249,7 @@
 						image{
 							width: 60rpx;
 							height: 60rpx;
+							border-radius: 50%;
 							margin-left: 28rpx;
 						}
 					}

+ 15 - 7
components/pages/nonprofitActivety/index.vue

@@ -1,36 +1,44 @@
 <template>
-	<view class="nonprofit-activety" @tap.self="handleDetail({})">
+	<view class="nonprofit-activety" @tap.self="handleDetail({})" v-if="item">
 		<div class="na-top adf">
 			<div class="na-top-left">
-				<image src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/09/11/d3c53597-a848-4a33-8deb-ab256f028baa.png"></image>
+				<image :src="item.coverFile"></image>
 			</div>
 			<div class="na-top-right">
-				<p>{{'《环保知识知多少》让孩子成为大自然的守护者!'}}</p>
+				<p>{{item.activityName||''}}</p>
 				<div class="tip adf">
 					<div class="tip-left adfac">
 						<image src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/09/11/201a4250-24a4-412d-9ec9-fc58071d10ea.png"></image>
 						<text>截止报名:</text>
 					</div>
-					<div class="tip-right">{{"还有5天12小时34分钟"}}</div>
+					<!-- <div class="tip-right">{{item.endTimeText}}</div> -->
+					<div class="tip-right">{{item.signupEndTime||'暂无'}}</div>
 				</div>
 				<div class="tip adf">
 					<div class="tip-left adfac">
 						<image src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/09/11/e9025f86-a59e-4f82-92f0-9d22e846193c.png"></image>
 						<text>活动地点:</text>
 					</div>
-					<div class="tip-right">{{"广东省深圳市南山区"}}</div>
+					<div class="tip-right">{{item.provinceName||''}}{{item.cityName||''}}</div>
 				</div>
 			</div>
 		</div>
 		<div class="na-bottom adfacjb">
-			<div class="na-bottom-left adf">已报名&nbsp;&nbsp;<strong>{{234}}</strong>/{{300}}&nbsp;&nbsp;人</div>
+			<div class="na-bottom-left adf">
+				已报名&nbsp;&nbsp;<strong>{{item.recruitmentNow}}</strong>/{{item.recruitmentMax===0?'无限制':(item.recruitmentMax+'&nbsp;&nbsp;人')}}
+			</div>
 			<!-- <div class="na-bottom-right" @tap.stop="toApply">立即报名</div> -->
 		</div>
-		<login-register></login-register>
 	</view>
 </template>
 
 <script setup name="nonprofitActivety">
+	defineProps({
+		item:{
+			typeof:Object,
+			default:null
+		}
+	})
 	import { ref } from 'vue'
 	import { useUserStore } from '@/common/stores/user';
 	const userStore = useUserStore();

+ 6 - 4
manifest.json

@@ -50,12 +50,14 @@
     "quickapp" : {},
     /* 小程序特有相关 */
     "mp-weixin" : {
-        "appid" : "",
+        "appid" : "wxe6afb089695f90e7",
         "setting" : {
-            "urlCheck" : false
+            "urlCheck" : false,
+            "minified" : true
         },
-        "usingComponents" : true,
-		"mergeVirtualHostAttributes" : true
+        "__usePrivacyCheck__" : true,
+        "usingComponents" : true,
+        "mergeVirtualHostAttributes" : true
     },
     "mp-alipay" : {
         "usingComponents" : true

+ 65 - 18
pages/home.vue

@@ -1,5 +1,5 @@
 <template>
-	<view class="tab_page" :style="{'height':h+'px', 'padding-top':mt+'px'}">
+	<view class="tab_page adffc" :style="{'height':h+'px', 'padding-top':mt+'px'}">
 		<up-navbar title=" " bgColor="transparent">
             <template #left>
 				<view class="u-nav-slot" slot="left" style="display: flex;background-color: transparent;">
@@ -38,11 +38,16 @@
 					</view>
 				</scroll-view>
 			</div>
-			<div class="c-box-list">
-				<NonprofitActivety></NonprofitActivety>
-				<NonprofitActivety></NonprofitActivety>
+			<div class="c-box-list" v-if="activityList.length">
+				<template v-for="item in activityList" :key="item.id">
+					<NonprofitActivety :item="item"></NonprofitActivety>
+				</template>
+			</div>
+			<div class="dataEmpty" v-else>
+				<page-empty text="暂无公益活动"></page-empty>
 			</div>
 		</div>
+		<login-register></login-register>
 		<CusTabbar :tabbarIndex="0"></CusTabbar>
 	</view>
 </template>
@@ -50,26 +55,17 @@
 <script setup name="">
 	import CusTabbar from '@/components/CusTabbar/index.vue'
 	import NonprofitActivety from '@/components/pages/nonprofitActivety/index.vue'
+	import PageEmpty from '@/components/pageEmpty/index.vue'
 	import { ref, getCurrentInstance, onMounted } from 'vue'
 	const { proxy } = getCurrentInstance()
 	
-	const bannarList = ref([
-		'https://transcend.ringzle.com/xiaozhi-app/profile/2025/09/11/337c22f0-2583-4556-84b6-eb21fdde6e82.png',
-		'https://transcend.ringzle.com/xiaozhi-app/profile/2025/09/11/337c22f0-2583-4556-84b6-eb21fdde6e82.png',
-		'https://transcend.ringzle.com/xiaozhi-app/profile/2025/09/11/337c22f0-2583-4556-84b6-eb21fdde6e82.png'
-	])
+	const bannarList = ref([])
+	const bannarOrigin = ref([])
 	const current = ref(0)
-	const typeList = ref([
-		{id:'',name:'全部'},
-		{id:1,name:'儿童关爱'},
-		{id:2,name:'老人关爱'},
-		{id:3,name:'社区发展'},
-		{id:4,name:'社会服务'},
-		{id:5,name:'健康行动'},
-		{id:6,name:'减肥运动'}
-	])
+	const typeList = ref([])
 	const tlIndex = ref(0)
 	const scrollLeft = ref(0)
+	const activityList = ref([])
 	
 	const changeType = (item,index) => {
 		tlIndex.value = index;
@@ -79,6 +75,7 @@
 				scrollLeft.value = (index-2)*172/2;
 			}
 		}
+		getActivityList(item.id)
 	}
 	
 	const toTurnPage = (url,needLogin) => {
@@ -86,6 +83,56 @@
 			uni.navigateTo({ url })
 		}
 	}
+	
+	const getSwiperList = () => {
+		proxy.$api.get('/core/advertisement/manage/page',{page:1,limit:-1}).then(({data:res})=>{
+			if(res.code!==0) return this.$showToast(res.msg)
+			bannarOrigin.value = res.data.list;
+			bannarList.value = res.data.list.map(l=>l.fileUrl);
+		})
+	}
+	
+	const getTypeList = () => {
+		proxy.$api.get('/core/activity/category/list').then(({data:res})=>{
+			if(res.code!==0) return this.$showToast(res.msg)
+			typeList.value = [{id:'',name:'全部'},...res.data.map(d=>({id:d.id,name:d.categoryName}))];
+		})
+	}
+	
+	const getActivityList = (categoryId) => {
+		proxy.$api.get('/core/activity/page',{page:1,limit:2,categoryId}).then(({data:res})=>{
+			if(res.code!==0) return this.$showToast(res.msg)
+			activityList.value = res.data.list;
+			activityList.value.forEach(a=>{
+				let cc = calculateCountdown(a?.signupEndTime);
+				a.endTimeText = cc===-1?a?.signupEndTime:(`还有${cc.days}天${cc.hours}小时${cc.minutes}分钟`)
+			})
+		})
+	}
+	
+	const calculateCountdown = (datetime) => {
+		  if(!datetime) return -1;
+		  
+		  const targetDate = new Date(datetime);
+		  const now = new Date();
+		  const difference = targetDate.getTime() - now.getTime();
+		  if (difference <= 0) return -1;
+		  
+		  const days = Math.floor(difference / (1000 * 60 * 60 * 24));
+		  const hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
+		  const minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60));
+		  return {
+		    days,
+		    hours,
+		    minutes
+		  };
+	}
+	
+	onMounted(()=>{
+		getSwiperList()
+		getTypeList()
+		getActivityList('')
+	})
 </script>
 
 <style scoped lang="scss">

+ 362 - 9
pagesHome/activityDetail.vue

@@ -138,28 +138,52 @@
 				<div class="sbox-title">分享给好友</div>
 				<image class="close" src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/10/10/7d1c7cf4-199a-4008-8114-ee0e1a8f0cc3.png" @click="share=false"></image>
 				<div class="sbox-items adfac">
-					<div class="sbox-items-pre adffcac">
+					<div class="sbox-items-pre adffcac" @click="shareToFriend">
 						<image src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/10/10/a993c721-a4c7-4f5e-95cc-6451a50bfdce.png"></image>
 						<text>分享页面</text>
 					</div>
-					<div class="sbox-items-pre adffcac">
+					<div class="sbox-items-pre adffcac" @click="generatePoster">
 						<image src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/10/10/0c87d8a1-d7c5-466c-84aa-87ec5f163955.png"></image>
 						<text>生成海报</text>
 					</div>
-					<div class="sbox-items-pre adffcac">
+					<div class="sbox-items-pre adffcac" @click="shareToTimeline">
 						<image src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/10/10/732ac5fd-af58-44d8-9625-aaf96c24fed0.png"></image>
 						<text>分享到朋友圈</text>
 					</div>
 				</div>
 			</div>
 		</div>
+		
+		<!-- 添加海报生成的canvas -->
+		<canvas 
+			canvas-id="posterCanvas" 
+			:style="{width: canvasWidth + 'px', height: canvasHeight + 'px'}"
+			style="position: fixed; top: -9999px; left: -9999px;"
+		></canvas>
+		
+		<!-- 海报预览弹窗 -->
+		<div class="poster-preview" v-if="showPoster">
+			<div class="poster-box">
+				<div class="poster-header">
+					<text>生成海报</text>
+					<image class="close" src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/10/10/7d1c7cf4-199a-4008-8114-ee0e1a8f0cc3.png" @click="showPoster=false"></image>
+				</div>
+				<div class="poster-content">
+					<image :src="posterUrl" mode="widthFix" class="poster-img"></image>
+				</div>
+				<div class="poster-actions">
+					<button class="save-btn" @click="savePoster">保存到相册</button>
+					<button class="share-btn" @click="sharePoster">分享给好友</button>
+				</div>
+			</div>
+		</div>
 	</view>
 </template>
 
 <script setup name="">
 	import CusHeader from '@/components/CusHeader/index.vue'
-	import { onShareAppMessage, onLoad } from '@dcloudio/uni-app';
-	import { ref } from 'vue'
+	import { onShareAppMessage, onShareTimeline, onLoad } from '@dcloudio/uni-app';
+	import { ref, reactive } from 'vue'
 	
 	const id = ref('')
 	const imgList = ref(['https://transcend.ringzle.com/xiaozhi-app/profile/2025/10/10/5378bfeb-32f5-4325-aaef-5270e4a55a3a.png'])
@@ -173,6 +197,16 @@
 	])
 	const fail = ref(false)
 	const share = ref(false)
+	const showPoster = ref(false)
+	const posterUrl = ref('')
+	const canvasWidth = ref(375)
+	const canvasHeight = ref(667)
+	const activityData = reactive({
+		title: '《环保知识知多少》让孩子成为大自然的守护者!',
+		time: '06.01-12:00 ~ 06.02-12:00',
+		address: '深圳市南山区南山街道丰潭路',
+		organizer: '锦鲤俱乐部'
+	})
 	
 	const handleReviewMembers = () => {
 		uni.navigateTo({
@@ -188,15 +222,256 @@
 		})
 	}
 	
+	// 分享给好友
+	const shareToFriend = () => {
+		share.value = false
+		uni.showShareMenu({
+			withShareTicket: true,
+			menus: ['shareAppMessage']
+		})
+	}
+	
+	// 分享到朋友圈
+	const shareToTimeline = () => {
+		share.value = false
+		// 朋友圈分享会触发 onShareTimeline
+		uni.showShareMenu({
+			withShareTicket: true,
+			menus: ['shareTimeline']
+		})
+	}
+	
+	// 生成海报
+	const generatePoster = async () => {
+		share.value = false
+		uni.showLoading({
+			title: '正在生成海报...'
+		})
+		
+		try {
+			await createPoster()
+			uni.hideLoading()
+			showPoster.value = true
+		} catch (error) {
+			uni.hideLoading()
+			uni.showToast({
+				title: '海报生成失败',
+				icon: 'none'
+			})
+		}
+	}
+	
+	// 创建海报
+	const createPoster = () => {
+		return new Promise((resolve, reject) => {
+			const ctx = uni.createCanvasContext('posterCanvas')
+			
+			// 设置画布尺寸
+			const width = canvasWidth.value
+			const height = canvasHeight.value
+			
+			// 绘制背景
+			const gradient = ctx.createLinearGradient(0, 0, 0, height)
+			gradient.addColorStop(0, '#87CEEB')
+			gradient.addColorStop(1, '#98FB98')
+			ctx.fillStyle = gradient
+			ctx.fillRect(0, 0, width, height)
+			
+			// 加载并绘制背景图片(山峰)
+			const bgImg = 'https://transcend.ringzle.com/xiaozhi-app/profile/2025/10/10/5378bfeb-32f5-4325-aaef-5270e4a55a3a.png'
+			
+			uni.getImageInfo({
+				src: bgImg,
+				success: (res) => {
+					// 绘制背景图
+					ctx.drawImage(res.path, 0, 0, width, height * 0.6)
+					
+					// 绘制绿色标签
+					ctx.fillStyle = '#B7F358'
+					ctx.fillRect(20, 30, 120, 30)
+					
+					// 绘制标签文字
+					ctx.fillStyle = '#000000'
+					ctx.font = 'bold 14px PingFang-SC'
+					ctx.fillText('节能低碳小妙招', 30, 50)
+					
+					// 绘制主标题
+					ctx.fillStyle = '#FFFFFF'
+					ctx.font = 'bold 24px PingFang-SC'
+					ctx.fillText('环保知识', 50, 120)
+					ctx.fillText('知多少', 50, 150)
+					
+					// 绘制白色信息区域背景
+					ctx.fillStyle = '#FFFFFF'
+					ctx.fillRect(20, height * 0.6 + 20, width - 40, height * 0.35)
+					
+					// 绘制活动信息
+					ctx.fillStyle = '#000000'
+					ctx.font = '16px PingFang-SC'
+					const startY = height * 0.6 + 50
+					
+					// 活动标题
+					ctx.font = 'bold 16px PingFang-SC'
+					ctx.fillText(activityData.title.substring(0, 20), 30, startY)
+					if (activityData.title.length > 20) {
+						ctx.fillText(activityData.title.substring(20), 30, startY + 25)
+					}
+					
+					// 时间图标和文字
+					ctx.font = '14px PingFang-SC'
+					ctx.fillStyle = '#666666'
+					ctx.fillText('🕐', 30, startY + 60)
+					ctx.fillText(activityData.time, 50, startY + 60)
+					
+					// 地址图标和文字
+					ctx.fillText('📍', 30, startY + 85)
+					ctx.fillText(activityData.address, 50, startY + 85)
+					
+					// 组织方
+					ctx.fillText('👥', 30, startY + 110)
+					ctx.fillText(activityData.organizer, 50, startY + 110)
+					
+					// 绘制小程序码区域
+					ctx.fillStyle = '#F5F5F5'
+					ctx.fillRect(width - 120, height - 140, 100, 100)
+					
+					ctx.fillStyle = '#000000'
+					ctx.font = '12px PingFang-SC'
+					ctx.fillText('微信扫一扫', width - 110, height - 25)
+					
+					// 执行绘制
+					ctx.draw(false, () => {
+						// 导出图片
+						uni.canvasToTempFilePath({
+							canvasId: 'posterCanvas',
+							width: width,
+							height: height,
+							destWidth: width * 2,
+							destHeight: height * 2,
+							success: (res) => {
+								posterUrl.value = res.tempFilePath
+								resolve(res.tempFilePath)
+							},
+							fail: reject
+						})
+					})
+				},
+				fail: () => {
+					// 如果图片加载失败,绘制纯色背景
+					drawSimplePoster(ctx, width, height, resolve, reject)
+				}
+			})
+		})
+	}
+	
+	// 绘制简单海报(备用方案)
+	const drawSimplePoster = (ctx, width, height, resolve, reject) => {
+		// 绘制渐变背景
+		const gradient = ctx.createLinearGradient(0, 0, 0, height)
+		gradient.addColorStop(0, '#87CEEB')
+		gradient.addColorStop(1, '#98FB98')
+		ctx.fillStyle = gradient
+		ctx.fillRect(0, 0, width, height)
+		
+		// 绘制白色内容区域
+		ctx.fillStyle = '#FFFFFF'
+		ctx.fillRect(20, 100, width - 40, height - 200)
+		
+		// 绘制标题
+		ctx.fillStyle = '#000000'
+		ctx.font = 'bold 20px PingFang-SC'
+		ctx.fillText('公益活动邀请', width / 2 - 70, 150)
+		
+		// 绘制活动信息
+		ctx.font = '16px PingFang-SC'
+		ctx.fillText(activityData.title.substring(0, 15), 30, 200)
+		if (activityData.title.length > 15) {
+			ctx.fillText(activityData.title.substring(15), 30, 225)
+		}
+		
+		ctx.font = '14px PingFang-SC'
+		ctx.fillStyle = '#666666'
+		ctx.fillText('活动时间:' + activityData.time, 30, 270)
+		ctx.fillText('活动地点:' + activityData.address.substring(0, 12), 30, 295)
+		
+		ctx.draw(false, () => {
+			uni.canvasToTempFilePath({
+				canvasId: 'posterCanvas',
+				width: width,
+				height: height,
+				destWidth: width * 2,
+				destHeight: height * 2,
+				success: (res) => {
+					posterUrl.value = res.tempFilePath
+					resolve(res.tempFilePath)
+				},
+				fail: reject
+			})
+		})
+	}
+	
+	// 保存海报到相册
+	const savePoster = () => {
+		uni.saveImageToPhotosAlbum({
+			filePath: posterUrl.value,
+			success: () => {
+				uni.showToast({
+					title: '保存成功',
+					icon: 'success'
+				})
+				showPoster.value = false
+			},
+			fail: (err) => {
+				if (err.errMsg.includes('auth deny')) {
+					uni.showModal({
+						title: '提示',
+						content: '需要您授权保存图片到相册',
+						confirmText: '去设置',
+						success: (res) => {
+							if (res.confirm) {
+								uni.openSetting()
+							}
+						}
+					})
+				} else {
+					uni.showToast({
+						title: '保存失败',
+						icon: 'none'
+					})
+				}
+			}
+		})
+	}
+	
+	// 分享海报给好友
+	const sharePoster = () => {
+		// 这里可以实现分享海报的逻辑
+		uni.showToast({
+			title: '请保存后手动分享',
+			icon: 'none'
+		})
+	}
+	
 	onLoad(options=>{
 		id.value = options?.id;
 	})
 	
+	// 分享给好友
 	onShareAppMessage(() => {
-	  return {
-	    title: '公益活动详情',
-	    path: '/pagesHome/activityDetail?id='+id.value
-	  };
+		return {
+			title: activityData.title,
+			path: '/pagesHome/activityDetail?id=' + id.value,
+			imageUrl: imgList.value[0] // 使用活动图片作为分享图
+		};
+	});
+	
+	// 分享到朋友圈
+	onShareTimeline(() => {
+		return {
+			title: activityData.title,
+			query: 'id=' + id.value,
+			imageUrl: posterUrl.value || imgList.value[0] // 优先使用海报,否则使用活动图片
+		};
 	});
 </script>
 
@@ -677,5 +952,83 @@
 				}
 			}
 		}
+	
+		.poster-preview {
+			position: fixed;
+			left: 0;
+			right: 0;
+			top: 0;
+			bottom: 0;
+			background: rgba(0,0,0,0.8);
+			display: flex;
+			flex-direction: column;
+			justify-content: center;
+			align-items: center;
+			z-index: 9999;
+			
+			.poster-box {
+				width: 80%;
+				max-width: 600rpx;
+				background: #FFFFFF;
+				border-radius: 24rpx;
+				overflow: hidden;
+				
+				.poster-header {
+					display: flex;
+					justify-content: space-between;
+					align-items: center;
+					padding: 30rpx;
+					border-bottom: 1rpx solid #E7E7E7;
+					
+					text {
+						font-family: PingFang-SC, PingFang-SC;
+						font-weight: bold;
+						font-size: 32rpx;
+						color: #151B29;
+					}
+					
+					.close {
+						width: 40rpx;
+						height: 40rpx;
+					}
+				}
+				
+				.poster-content {
+					padding: 30rpx;
+					display: flex;
+					justify-content: center;
+					
+					.poster-img {
+						width: 100%;
+						border-radius: 12rpx;
+					}
+				}
+				
+				.poster-actions {
+					display: flex;
+					padding: 0 30rpx 30rpx;
+					gap: 20rpx;
+					
+					button {
+						flex: 1;
+						height: 80rpx;
+						border-radius: 40rpx;
+						border: none;
+						font-size: 28rpx;
+						font-weight: bold;
+					}
+					
+					.save-btn {
+						background: #F5F5F5;
+						color: #151B29;
+					}
+					
+					.share-btn {
+						background: #B7F358;
+						color: #151B29;
+					}
+				}
+			}
+		}
 	}
 </style>

+ 1 - 1
pagesHome/allActivity.vue

@@ -88,7 +88,7 @@
 	const typeList2 = ref([])
 	const scrollLeft = ref(0)
 	const time = ref('全部时间')
-	const timeText = ref('')
+	const timeText = ref('全部时间')
 	const place = ref('全部地区')
 	const status = ref('活动状态')
 	const queryParams = ref({})