Quellcode durchsuchen

新需求功能修改:账号和用户切换、档案、义工时长、爱心值区分账号还是成员;等

htc vor 3 Tagen
Ursprung
Commit
7758831f1c

+ 0 - 1
components/pages/archivesBox/index.vue

@@ -8,7 +8,6 @@
 				<image class="sex" v-else-if="item?.gender==0" src="https://oss.familydaf.cn/sxsnfile/20251218/02bc40a1c47b40f1a36b55cd0553211c.png"></image>
 				<view class="age" :class="{'women':item?.gender==1,'man':item?.gender==0}">{{item?.age||0}}岁</view>
 			</view>
-			<view class="ni-top-right"></view>
 		</view>
 		<view class="ni-info adfacjb">
 			<view class="ni-info-left">

+ 308 - 23
pages/my.vue

@@ -7,26 +7,28 @@
 		<view class="info">
 			<view class="info-top adfacjb">
 				<view class="info-top-left adfac">
-					<image class="avatar" :src="userInfo?.avatarPath||'https://oss.familydaf.cn/sxsnfile/20251218/3821654e080945998d464f3c3aa64122.png'"></image>
+					<image class="avatar" :src="userInfo?.avatarPath||'https://oss.familydaf.cn/sxsnfile/20251218/3821654e080945998d464f3c3aa64122.png'" @click="handleTurnPage('/pagesMy/information')"></image>
 					<view class="text" @click.prevent="showLogin">
 						<view class="text-top">{{userInfo?userInfo?.realName:'未登录'}}</view>
-						<view class="text-bottom lv adfac" v-if="userInfo" @click.stop="handleTurnPage('/pagesMy/levelDetail')">Lv&nbsp;&nbsp;&nbsp;{{userInfo?.userLevel||0}}&nbsp;&nbsp;&nbsp;></view>
-						<view class="text-bottom" v-else>去登录注册&nbsp;>></view>
+						<view class="text-bottom no adfac" v-if="userInfo&&!isFamilyMember">编号:{{userInfo?.uniqueNo||''}}</view>
+						<view class="text-bottom lv adfac" v-if="userInfo&&isFamilyMember" @click.stop="handleTurnPage('/pagesMy/levelDetail')">Lv&nbsp;&nbsp;&nbsp;{{userInfo?.userLevel||0}}&nbsp;&nbsp;&nbsp;></view>
+						<view class="text-bottom" v-if="!userInfo">去登录注册&nbsp;>></view>
 					</view>
 				</view>
-				<view class="info-top-right" @click="handleTurnPage('/pagesMy/information')">
-					<image src="https://oss.familydaf.cn/sxsnfile/20251218/a4bdf4248ddd44909e8707c83fe33265.png"></image>
-				</view>
+				<div class="info-top-change adfac" v-if="userInfo" @click="handleChangeUser">
+					<image src="https://oss.familydaf.cn/sxsnfile/20260105/7242f7d209f646a29e5570fa73da352d.png"></image>
+					<text>切换用户</text>
+				</div>
 			</view>
 			<view class="info-tip">
 				<view class="info-tip-pre adfacjb" @click="handleTurnPage('/pagesMy/information')">
-					<view class="left">家庭公益名称:{{userInfo?userInfo.welfareName:'暂无'}}</view>
+					<view class="left">家庭公益名称:<text v-if="userInfo">{{userInfo?.welfareName||''}}</text><text style="color: #657588;" v-else>暂无,快去填写吧~</text></view>
 					<view class="right">
 						<image src="https://oss.familydaf.cn/sxsnfile/20251218/938f573288ad401fbd1e57404a870059.png"></image>
 					</view>
 				</view>
 				<view class="info-tip-pre adfacjb" @click="handleTurnPage('/pagesMy/information')">
-					<view class="left">家庭公益口号:{{userInfo?userInfo.welfareSlogan:'暂无'}}</view>
+					<view class="left">家庭公益口号:<text v-if="userInfo">{{userInfo?.welfareSlogan||''}}</text><text style="color: #657588;" v-else>暂无,快去填写吧~</text></view>
 					<view class="right">
 						<image src="https://oss.familydaf.cn/sxsnfile/20251218/938f573288ad401fbd1e57404a870059.png"></image>
 					</view>
@@ -62,7 +64,7 @@
 				</view>
 			</view>
 		</view>
-		<view class="box" @click="handleTurnPage('/pagesMy/achievement')">
+		<view class="box" @click="handleTurnPage('/pagesMy/achievement')" v-if="isFamilyMember">
 			<view class="box-title">我的成就</view>
 			<view class="box-achievement adfacjb">
 				<view class="box-achievement-left">收获<span>{{numInfo?.myMedals||0}}</span>项成就</view>
@@ -85,12 +87,44 @@
 					<image src="https://oss.familydaf.cn/sxsnfile/20251218/248ed751f8bf4821b29cd236ec2dc78b.png"></image>
 					<text>家庭成员</text>
 				</view>
-				<view class="box-other-pre adffcac" @click="handleTurnPage('/pagesMy/exclusiveScroll')">
+				<view class="box-other-pre adffcac" @click="handleTurnPage('/pagesMy/exclusiveScroll')" v-if="!isFamilyMember">
 					<image src="https://oss.familydaf.cn/sxsnfile/20251218/7ee3c42e8408489fb0768406d72b73b4.png"></image>
 					<text>我的专享券</text>
 				</view>
+				<view class="box-other-pre adffcac" @click="handleBackUser" v-else>
+					<image src="https://oss.familydaf.cn/sxsnfile/20260105/5b8a24ffd1e34486a6aa5d88260677d0.png"></image>
+					<text>返回家庭页</text>
+				</view>
 			</view>
 		</view>
+		<div class="dialog adffc" v-if="changeShow">
+			<div class="change adffc">
+				<div class="change-top adfacjc">
+					<text>切换用户</text>
+					<image src="https://oss.familydaf.cn/sxsnfile/20260105/2ffe44b1abef403788b21aca09f6a8c3.png" @click="changeShow=false"></image>
+				</div>
+				<div class="change-list">
+					<div class="change-list-item adfacjb" v-for="(item,index) in familyMemberList" :key="item.id" @click="selectFamilyMember(index)">
+						<div class="change-list-item-left">
+							<view class="change-list-item-left-top adfac">
+								<view class="name">{{item.name||''}}</view>
+								<image class="sex" v-if="item.gender==1" src="https://oss.familydaf.cn/sxsnfile/20251218/473d5677fbdb4106acdb9a163377d27f.png"></image>
+								<image class="sex" v-else-if="item.gender==0" src="https://oss.familydaf.cn/sxsnfile/20251218/02bc40a1c47b40f1a36b55cd0553211c.png"></image>
+								<view class="age" :class="{'women':item.gender==1,'man':item.gender==0}">{{item.age}}岁</view>
+							</view>
+							<view class="change-list-item-left-bottom">
+								身份证 {{item.idCardCopy}}
+							</view>
+						</div>
+						<div class="change-list-item-right">
+							<image v-if="item.select" src="https://oss.familydaf.cn/sxsnfile/20251218/c11b9a1b56f34e1189621f4270f0349a.png"></image>
+							<image v-else src="https://oss.familydaf.cn/sxsnfile/20251218/2a2f7bdefb474a3e93faa00aef6d0e1f.png"></image>
+						</div>
+					</div>
+				</div>
+				<div class="change-btn" @click="changeUserConfirm">确定</div>
+			</div>
+		</div>
 		<CusTabbar :tabbarIndex="2"></CusTabbar>
 		<login-register></login-register>
 	</view>
@@ -107,39 +141,146 @@
 	
 	const userInfo = ref(null)
 	const numInfo = ref(null)
+	const isFamilyMember = ref(false)
+	const changeShow = ref(false)
+	const familyMemberList = ref([])
+	const familyMemberInfo = ref(null)
+	const memberId = ref('')
 	
 	const showLogin = () => {
-		if(userInfo.value) return handleTurnPage('/pagesMy/information')
-		userStore.openLoginModal();
+		if(!isLogin()) return
+		handleTurnPage('/pagesMy/information')
+	}
+	
+	const handleChangeUser = () => {
+		changeShow.value = true;
+		getMemberList();
+	}
+	
+	const getMemberList = () => {
+		const userId = uni.getStorageSync('userInfo')?JSON.parse(uni.getStorageSync('userInfo'))?.id:'';
+		proxy.$api.get('/core/family/member/page',{page:1,limit:-1,userId}).then(({data:res})=>{
+			if(res.code!==0) return proxy.$showToast(res.msg)
+			familyMemberList.value = res.data.list;
+			familyMemberList.value.forEach(l=>{
+				l.select = false;
+				l.age = getAge(l.idCard)
+				l.idCardCopy = l.idCard.replace(/^(\d{6})(\d{8})(\d{3}[\dX])$/i,'$1********$3')
+			})
+		})
+	}
+	
+	const isValid = (idCard) => {
+		// 正则表达式校验18位身份证号码(最后一位可以是数字或X/x)
+		const regex = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
+		return typeof idCard === 'string' && regex.test(idCard);
+	  }
+	
+	const getAge = (idCard) => {
+		if (!isValid(idCard)) return 0
+	
+		// 从身份证的第7位开始,截取8位作为出生日期字符串 (YYYYMMDD)
+		const birthDateStr = idCard.substring(6, 14);
+		const birthYear = parseInt(birthDateStr.substring(0, 4), 10);
+		const birthMonth = parseInt(birthDateStr.substring(4, 6), 10);
+		const birthDay = parseInt(birthDateStr.substring(6, 8), 10);
+	
+		const today = new Date();
+		const currentYear = today.getFullYear();
+		const currentMonth = today.getMonth() + 1; // getMonth() 返回 0-11
+		const currentDay = today.getDate();
+	
+		// 计算周岁
+		let age = currentYear - birthYear;
+	
+		// 如果当前月份小于出生月份,或者月份相同但日期小于出生日期,说明今年的生日还没过
+		if (currentMonth < birthMonth || (currentMonth === birthMonth && currentDay < birthDay)) {
+		  age--;
+		}
+	
+		return age < 0 ? 0 : age; 
+	}
+	
+	const selectFamilyMember = index => {
+		familyMemberList.value.forEach((f,i)=>{
+			familyMemberList.value[i].select = index===i;
+		})
+	}
+	
+	const handleBackUser = () => {
+		memberId.value = '';
+		familyMemberInfo.value = null;
+		isFamilyMember.value = false;
+		uni.removeStorageSync('isFamilyMember');
+		uni.removeStorageSync('familyMemberInfo');
+	}
+	
+	const changeUserConfirm = () => {
+		const selectedUser = familyMemberList.value.find(f=>f.select)
+		if(!selectedUser) return proxy.$showToast('请选择要切换的用户')
+		memberId.value = selectedUser?.id||'';
+		familyMemberInfo.value = selectedUser;
+		isFamilyMember.value = true;
+		userInfo.value = {...deepClone(userInfo.value),...deepClone(selectedUser)};
+		uni.setStorageSync('isFamilyMember',true);
+		uni.setStorageSync('familyMemberInfo',JSON.stringify(familyMemberInfo.value));
+		changeShow.value = false;
 	}
 	
 	const handleTurnPage = (url) => {
 		if(!isLogin()) return
+		if(url == '/pagesMy/information'){
+			if(isFamilyMember.value){
+				uni.navigateTo({
+					url:'/pagesMy/familyMemberVindicate?id='+(JSON.parse(uni.getStorageSync('familyMemberInfo'))?.id||'')+'&type=my'
+				})
+				return
+			}
+		}
 		if(url == '/pagesMy/achievement'){
 			url = `/pagesMy/achievement?numInfo=`+JSON.stringify(numInfo.value)
 		}
 		uni.navigateTo({ url })
 	}
 	
+	const deepClone = (obj) => {
+		  if (obj === null || typeof obj !== 'object') {
+			return obj;
+		  }
+		  
+		  if (Array.isArray(obj)) {
+			return obj.map(item => deepClone(item));
+		  }
+		  
+		  const clonedObj = {};
+		  
+		  for (const key in obj) {
+			clonedObj[key] = deepClone(obj[key]);
+		  }
+		  
+		  return clonedObj;
+	}
+	
 	const getUserInfo = () => {
 		try{
-			proxy.$api.get(`/wx/${JSON.parse(uni.getStorageSync('userInfo')).id}`).then(({data:res})=>{
+			let mid = deepClone(memberId.value);
+			if(uni.getStorageSync('isFamilyMember')) mid = uni.getStorageSync('familyMemberInfo')?JSON.parse(uni.getStorageSync('familyMemberInfo')).id:'';
+			proxy.$api.get(`/wx/userWelfareData`,{memberId:mid}).then(({data:res})=>{
 				if(res.code!==0) return proxy.$showToast(res.msg)
 				userInfo.value = res.data;
-				uni.setStorageSync('userInfo',JSON.stringify(userInfo.value));
+				numInfo.value = res.data;
+				if(uni.getStorageSync('isFamilyMember')){
+					uni.setStorageSync('isFamilyMember',JSON.stringify(res.data));
+				}else {
+					uni.setStorageSync('userInfo',JSON.stringify({...JSON.parse(uni.getStorageSync('userInfo')),...res.data}));
+				}
 			})
 		}catch(e){
 			userInfo.value = null
+			numInfo.value = null
 		}
 	}
 	
-	const getUserNums = () => {
-		proxy.$api.get(`/wx/userWelfareData/${JSON.parse(uni.getStorageSync('userInfo')).id}`).then(({data:res})=>{
-			if(res.code!==0) return proxy.$showToast(res.msg)
-			numInfo.value = res.data;
-		})
-	}
-	
 	watch(()=>userStore.token,(newVal,oldVal)=>{
 		if(oldVal!=newVal&&newVal){
 			setTimeout(()=>{
@@ -150,26 +291,142 @@
 			},200)
 			nextTick(()=>{
 				getUserInfo()
-				getUserNums()
+			})
+		}
+	})
+	
+	watch(()=>memberId.value,(newVal,oldVal)=>{
+		if(oldVal!=newVal){
+			nextTick(()=>{
+				getUserInfo()
 			})
 		}
 	})
 	
 	onMounted(()=>{
+		isFamilyMember.value = uni.getStorageSync('isFamilyMember')?true:false;
+		if(uni.getStorageSync('familyMemberInfo')) memberId.value = JSON.parse(uni.getStorageSync('familyMemberInfo')).id;
 		if(uni.getStorageSync('userInfo')){
 			getUserInfo()
-			getUserNums()
 		}
 	})
 </script>
 
 <style scoped lang="scss">
+	.dialog{
+		position: fixed;
+		left: 0;
+		right: 0;
+		top: 0;
+		bottom: 0;
+		background: rgba(0, 0, 0, .4);
+		z-index: 1001;
+		justify-content: flex-end;
+		.change{
+			width: 100%;
+			height: 920rpx;
+			background: #FFFFFF;
+			border-radius: 24rpx 24rpx 0rpx 0rpx;
+			padding: 48rpx 30rpx 64rpx;
+			box-sizing: border-box;
+			&-top{
+				position: relative;
+				text{
+					font-family: PingFang-SC, PingFang-SC;
+					font-weight: bold;
+					font-size: 36rpx;
+					color: #151B29;
+					line-height: 36rpx;
+				}
+				image{
+					width: 36rpx;
+					height: 36rpx;
+					position: absolute;
+					right: 0;
+				}
+			}
+			&-list{
+				flex: 1;
+				margin-top: 48rpx;
+				overflow-y: auto;
+				&-item{
+					box-shadow: inset 0rpx -1rpx 0rpx 0rpx #E5E7EB;
+					width: 100%;
+					padding: 36rpx 0;
+					&-left{
+						&-top{
+							.name{
+								font-family: PingFang-SC, PingFang-SC;
+								font-weight: bold;
+								font-size: 32rpx;
+								color: #151B29;
+								line-height: 32rpx;
+							}
+							.sex{
+								width: 44rpx;
+								height: 32rpx;
+								margin-left: 16rpx;
+							}
+							.age{
+								border-radius: 13rpx;
+								font-family: PingFangSC, PingFang SC;
+								font-weight: 400;
+								font-size: 20rpx;
+								line-height: 24rpx;
+								padding: 4rpx 10rpx;
+								margin-left: 13rpx;
+								&.women{
+									background: rgba(244,101,122,0.14);
+									color: #F4657A;
+								}
+								&.man{
+									background: rgba(5,169,254,0.12);
+									color: #05A9FE;
+								}
+							}
+						}
+						&-bottom{
+							font-family: PingFangSC, PingFang SC;
+							font-weight: 400;
+							font-size: 24rpx;
+							color: #989998;
+							line-height: 24rpx;
+							margin-top: 23rpx;
+						}
+					}
+					&-right{
+						image{
+							width: 36rpx;
+							height: 36rpx;
+						}
+					}
+				}
+			}
+			&-btn{
+				width: 100%;
+				height: 90rpx;
+				background: #B7F358;
+				border-radius: 45rpx;
+				font-family: PingFang-SC, PingFang-SC;
+				font-weight: bold;
+				font-size: 32rpx;
+				color: #151B29;
+				line-height: 90rpx;
+				text-align: center;
+				letter-spacing: 2rpx;
+				margin-top: 20rpx;
+				
+			}
+		}
+	}
+	
 	.tab_page{
 		.info{
 			margin-top: 40rpx;
 			position: relative;
 			padding: 0 24rpx 0 16rpx;
 			&-top{
+				position: relative;
 				&-left{
 					.avatar{
 						width: 148rpx;
@@ -206,6 +463,13 @@
 								box-sizing: border-box;
 								margin-top: 18rpx;
 							}
+							&.no{
+								font-family: PingFangSC, PingFang SC;
+								font-weight: 400;
+								font-size: 26rpx;
+								color: #657588;
+								line-height: 26rpx;
+							}
 						}
 					}
 				}
@@ -215,6 +479,27 @@
 						height: 48rpx;
 					}
 				}
+				&-change{
+					padding: 0 20rpx 0 16rpx;
+					height: 60rpx;
+					background: #B7F358;
+					border-radius: 30rpx 0rpx 0rpx 30rpx;
+					position: absolute;
+					top: 24rpx;
+					right: -48rpx;
+					image{
+						width: 32rpx;
+						height: 32rpx;
+					}
+					text{
+						font-family: PingFang-SC, PingFang-SC;
+						font-weight: bold;
+						font-size: 24rpx;
+						color: #151B29;
+						line-height: 30rpx;
+						margin-left: 8rpx;
+					}
+				}
 			}
 			&-tip{
 				margin-top: 4rpx;

+ 148 - 8
pagesHome/applyMemberVindicate.vue

@@ -2,7 +2,30 @@
 	<view class="common_page" :style="{'min-height':h+'px', 'padding-top':mt+'px'}">
 		<cus-header :title="title" bgColor="#FFFFFF"></cus-header>
 		<view class="form">
-			<view class="title">报名人员信息</view>
+			<view class="avatar">
+				<view class="title"><span>*</span>头像</view>
+				<view class="adfac" style="margin: 24rpx 0 52rpx;">
+					<view class="imgbox">
+						<image class="imgbox-avatar" :src="memberInfo?.avatarPath||defaultAvatar" @click="changeAvatar"></image>
+					</view>
+					<image class="add-avatar" src="https://oss.familydaf.cn/sxsnfile/20251219/0b4d0f26464945dbae17491f1b529229.png" @click="changeAvatar"></image>
+				</view>
+			</view>
+			<view class="item adfacjb">
+				<view class="left">个人公益名称</view>
+				<view class="right">
+					<input min="3" class="setinp"v-model="memberInfo.welfareName" placeholder-style="color: #A1A4A9;font-size: 28rpx;" placeholder="请输入最少三个字"></input>
+				</view>
+			</view>
+			<view class="item adfacjb">
+				<view class="left">个人公益口号</view>
+				<view class="right">
+					<input class="setinp"v-model="memberInfo.welfareSlogan" placeholder-style="color: #A1A4A9;font-size: 28rpx;" placeholder="请输入个人公益口号"></input>
+				</view>
+			</view>
+		</view>
+		<view class="form">
+			<view class="title">成员信息</view>
 			<view class="pre adfac" style="margin-top: 40rpx;">
 				<view class="pre-title"><span>*</span>人员类型</view>
 				<view class="pre-content adfac">
@@ -21,19 +44,19 @@
 			<view class="pre adfac">
 				<view class="pre-title"><span>*</span>姓名</view>
 				<view class="pre-content">
-					<input v-model="memberInfo.name" style="font-size: 30rpx;color: #252525;" placeholder="请输入姓名"></input>
+					<input v-model="memberInfo.name" style="font-size: 30rpx;color: #252525;text-align: right;" placeholder-style="color: #A1A4A9;font-size: 28rpx;" placeholder="请输入姓名"></input>
 				</view>
 			</view>
 			<view class="pre adfac">
 				<view class="pre-title"><span>*</span>身份证</view>
 				<view class="pre-content">
-					<input v-model="memberInfo.idCard" style="font-size: 30rpx;color: #252525;" placeholder="请输入身份证"></input>
+					<input v-model="memberInfo.idCard" style="font-size: 30rpx;color: #252525;text-align: right;" placeholder-style="color: #A1A4A9;font-size: 28rpx;" placeholder="请输入身份证"></input>
 				</view>
 			</view>
 			<view class="pre adfac">
 				<view class="pre-title"><span style="color: #FFFFFF;">*</span>义工号</view>
 				<view class="pre-content">
-					<input v-model="memberInfo.volunteerNo" style="font-size: 30rpx;color: #252525;" placeholder="请输入义工号"></input>
+					<input v-model="memberInfo.volunteerNo" style="font-size: 30rpx;color: #252525;text-align: right;" placeholder-style="color: #A1A4A9;font-size: 28rpx;" placeholder="请输入义工号"></input>
 				</view>
 			</view>
 			<view class="pre adfac">
@@ -54,21 +77,24 @@
 			<view class="pre adfac" v-if="memberInfo.personnelType==2">
 				<view class="pre-title"><span>*</span>就读学校</view>
 				<view class="pre-content">
-					<input v-model="memberInfo.currentSchool" style="font-size: 30rpx;color: #252525;" placeholder="请输入就读学校"></input>
+					<input v-model="memberInfo.currentSchool" style="font-size: 30rpx;color: #252525;text-align: right;" placeholder-style="color: #A1A4A9;font-size: 28rpx;" placeholder="请输入就读学校"></input>
 				</view>
 			</view>
 		</view>
 		<view class="btn" @click="handleSave">保存</view>
+		<qf-image-cropper :width="500" :height="500" :radius="30" @crop="handleCrop" ref="qicRef" v-if="qicShow"></qf-image-cropper>
 	</view>
 </template>
 
 <script setup name="">
+	import { BaseApi2 } from '../common/api/baseApi';
 	import CusHeader from '@/components/CusHeader/index.vue'
-	import { onLoad } from '@dcloudio/uni-app';
-	import { ref, getCurrentInstance } from 'vue'
+	import { onLoad } from '@dcloudio/uni-app'
+	import { ref, getCurrentInstance, nextTick } from 'vue'
 	const { proxy } = getCurrentInstance()
 	
 	const title = ref('添加报名人员')
+	const defaultAvatar = ref('https://oss.familydaf.cn/sxsnfile/20251218/3821654e080945998d464f3c3aa64122.png')
 	const memberInfo = ref({
 		userId:'',
 		personnelType:1,
@@ -78,12 +104,15 @@
 		gender:0,
 		currentSchool:''
 	})
+	const qicShow = ref(false)
 	
 	const changeMember = (value,key) => {
 		memberInfo.value[key] = value;
 	}
 	
 	const handleSave = () => {
+		if(!memberInfo.value.avatarPath) return proxy.$showToast('请上传头像')
+		if(memberInfo.value.welfareName&&memberInfo.value.welfareName.length<3) return proxy.$showToast('个人公益名称至少三个字')
 		if(!memberInfo.value.name) return proxy.$showToast('请输入姓名')
 		if(!proxy.$reg.idCard(memberInfo.value.idCard)) return proxy.$showToast('请输入正确的身份证号')
 		if(!memberInfo.value.currentSchool&&memberInfo.value.personnelType==2) return proxy.$showToast('请输入就读学校')
@@ -105,6 +134,66 @@
 		})
 	}
 	
+	const changeAvatar = () => {
+		uni.showActionSheet({
+			itemList: ['从手机相册选择'],
+			success: (res) => {
+			  if (res.tapIndex === 0) { // 从手机相册选择
+				chooseLocalImage();
+			  }
+			}
+		});
+	}
+	
+	// 从本地选择图片并显示
+	const chooseLocalImage = () => {
+	  uni.chooseImage({
+		count: 1, // 只能选择一张
+		sizeType: ['compressed'], // 压缩图片
+		sourceType: ['album', 'camera'], // 可以从相册或相机选择
+		success: (res) => {
+		  const tempFilePaths = res.tempFilePaths;
+		  if (tempFilePaths && tempFilePaths.length > 0) {
+			qicShow.value = true;
+			nextTick(()=>{
+				proxy.$refs.qicRef.initImage(tempFilePaths[0],true)
+			})
+		  }
+		},
+		fail: (err) => {
+		  console.error('选择图片失败', err);
+		}
+	  });
+	}
+	
+	
+	const handleCrop = async (e) => {
+		const result = await uploadFilePromise(e.tempFilePath);
+		memberInfo.value.avatarPath = result||'';
+		qicShow.value = false;
+	}
+	
+	const uploadFilePromise = (url) => {
+	  return new Promise((resolve, reject) => {
+	    let a = uni.uploadFile({
+	      url: BaseApi2+'/sys/oss/upload',
+	      filePath: url,
+	      name: 'file',
+	      success: (res) => {
+	        setTimeout(() => {
+				let data = JSON.parse(res.data)
+				if(data&&data.code===0){
+					resolve(data.data);
+				}else proxy.$showToast(data?.msg)
+	        }, 1000);
+	      },
+		  fail: err =>{
+			resolve('');
+		  }
+	    });
+	  });
+	};
+	
 	onLoad((options)=>{
 		const id = options?.id;
 		if(id){
@@ -124,6 +213,54 @@
 			margin-top: 20rpx;
 			padding: 36rpx 24rpx 0;
 			
+			.avatar{
+				.title{
+					font-family: PingFang-SC, PingFang-SC;
+					font-weight: bold;
+					font-size: 30rpx;
+					color: #252525;
+					line-height: 42rpx;
+					label{
+						font-family: PingFangSC, PingFang SC;
+						font-weight: 400;
+						font-size: 30rpx;
+						color: #F4657A;
+						line-height: 42rpx;
+					}
+				}
+				.imgbox{
+					width: 122rpx;
+					height: 122rpx;
+					&-avatar{
+						width: 100%;
+						height: 100%;
+						border-radius: 50%;
+					}
+				}
+				.add-avatar{
+					margin-left: 40rpx;
+					width: 122rpx;
+					height: 122rpx;
+					border-radius: 50%;
+				}
+				border-bottom: 1rpx solid #E6E6E6;
+			}
+			.item{
+				height: 90rpx;
+				box-shadow: inset 0rpx -1rpx 0rpx 0rpx #E6E6E6;
+				.left{
+					width: 200rpx;
+					font-family: PingFangSC, PingFang SC;
+					font-weight: 400;
+					font-size: 28rpx;
+					color: #676775;
+					line-height: 40rpx;
+				}
+				.right{
+					width: calc(100% - 200rpx);
+				}
+			}
+			
 			.title{
 				font-family: PingFang-SC, PingFang-SC;
 				font-weight: bold;
@@ -153,8 +290,11 @@
 				}
 				&-content{
 					width: calc(100% - 209rpx);
+					display: flex;
+					align-items: center;
+					justify-content: flex-end;
 					&-box{
-						width: 184rpx;
+						margin-left: 80rpx;
 						image{
 							width: 28rpx;
 							height: 28rpx;

Datei-Diff unterdrückt, da er zu groß ist
+ 33 - 4
pagesMy/achievement.vue


+ 9 - 6
pagesMy/archives.vue

@@ -1,7 +1,7 @@
 <template>
 	<view class="common_page adffc" :style="{'height':h+'px', 'padding-top':mt+'px'}">
 		<cus-header title="我的档案" bgColor="#FFFFFF"></cus-header>
-		<view class="member">
+		<view class="member" v-if="!isFamilyMember">
 			<scroll-view class="scroll-view_H" scroll-x="true" scroll-with-animation="true" :scroll-left="scrollLeft">
 				<view class="scroll-view-item_H" :id="'svih_'+index" v-for="(item,index) in memberList" :key="index" @click="changeMember(item,index)">
 					<view class="cl_item" :class="{'active':midx===index}">
@@ -16,7 +16,6 @@
 					<ArchivesBox :item="item" @handleReviewFile="handleReview"></ArchivesBox>
 				</up-list-item>
 			</up-list>
-			<ArchivesBox v-for="(item,index) in list" :key="index"></ArchivesBox>
 		</view>
 		<view class="dataEmpty" v-else>
 			<page-empty text="暂无档案记录"></page-empty>
@@ -31,10 +30,11 @@
 	import { ref, getCurrentInstance, onMounted } from 'vue'
 	const { proxy } = getCurrentInstance()
 	
-	const midx = ref('')
+	const midx = ref(0)
 	const scrollLeft = ref(0)
 	const memberList = ref([])
 	const isOver = ref(false)
+	const isFamilyMember = ref(false)
 	const queryParams = ref({
 		page:1,
 		limit:10,
@@ -56,9 +56,11 @@
 	}
 	
 	const getMemberList = () => {
-		proxy.$api.get('/core/family/member/page',queryParams.value).then(({data:res})=>{
+		let query = JSON.parse(JSON.stringify(queryParams.value))
+		query.limit = -1;
+		proxy.$api.get('/core/family/member/page',query).then(({data:res})=>{
 			if(res.code!==0) return proxy.$showToast(res.msg)
-			memberList.value = res.data.list||[];
+			memberList.value = [{id:'',name:'全部'}].concat(res.data.list||[]);
 		})
 	}
 	
@@ -118,7 +120,9 @@
 	
 	onMounted(()=>{
 		try{
+			isFamilyMember.value = uni.getStorageSync('isFamilyMember')?true:false;
 			queryParams.value.userId = uni.getStorageSync('userInfo')&&JSON.parse(uni.getStorageSync('userInfo')).id;
+			if(uni.getStorageSync('familyMemberInfo')) queryParams.value.memberId = JSON.parse(uni.getStorageSync('familyMemberInfo')).id;
 			getMemberList()
 			getList()
 		}catch(e){
@@ -168,7 +172,6 @@
 				}
 			}
 		}
-	
 		
 		.list{
 			padding: 0 24rpx;

+ 156 - 8
pagesMy/familyMemberVindicate.vue

@@ -2,7 +2,30 @@
 	<view class="common_page" :style="{'min-height':h+'px', 'padding-top':mt+'px'}">
 		<cus-header :title="title" bgColor="#FFFFFF"></cus-header>
 		<view class="form">
-			<view class="title">家庭人员信息</view>
+			<view class="avatar">
+				<view class="title"><span>*</span>头像</view>
+				<view class="adfac" style="margin: 24rpx 0 52rpx;">
+					<view class="imgbox">
+						<image class="imgbox-avatar" :src="memberInfo?.avatarPath||defaultAvatar" @click="changeAvatar"></image>
+					</view>
+					<image class="add-avatar" src="https://oss.familydaf.cn/sxsnfile/20251219/0b4d0f26464945dbae17491f1b529229.png" @click="changeAvatar"></image>
+				</view>
+			</view>
+			<view class="item adfacjb">
+				<view class="left">个人公益名称</view>
+				<view class="right">
+					<input min="3" class="setinp"v-model="memberInfo.welfareName" placeholder-style="color: #A1A4A9;font-size: 28rpx;" placeholder="请输入最少三个字"></input>
+				</view>
+			</view>
+			<view class="item adfacjb">
+				<view class="left">个人公益口号</view>
+				<view class="right">
+					<input class="setinp"v-model="memberInfo.welfareSlogan" placeholder-style="color: #A1A4A9;font-size: 28rpx;" placeholder="请输入个人公益口号"></input>
+				</view>
+			</view>
+		</view>
+		<view class="form">
+			<view class="title">成员信息</view>
 			<view class="pre adfac" style="margin-top: 40rpx;">
 				<view class="pre-title"><span>*</span>人员类型</view>
 				<view class="pre-content adfac">
@@ -21,19 +44,19 @@
 			<view class="pre adfac">
 				<view class="pre-title"><span>*</span>姓名</view>
 				<view class="pre-content">
-					<input v-model="memberInfo.name" style="font-size: 30rpx;color: #252525;" placeholder="请输入姓名"></input>
+					<input v-model="memberInfo.name" style="font-size: 30rpx;color: #252525;text-align: right;" placeholder-style="color: #A1A4A9;font-size: 28rpx;" placeholder="请输入姓名"></input>
 				</view>
 			</view>
 			<view class="pre adfac">
 				<view class="pre-title"><span>*</span>身份证</view>
 				<view class="pre-content">
-					<input v-model="memberInfo.idCard" style="font-size: 30rpx;color: #252525;" placeholder="请输入身份证"></input>
+					<input v-model="memberInfo.idCard" style="font-size: 30rpx;color: #252525;text-align: right;" placeholder-style="color: #A1A4A9;font-size: 28rpx;" placeholder="请输入身份证"></input>
 				</view>
 			</view>
 			<view class="pre adfac">
 				<view class="pre-title"><span style="color: #FFFFFF;">*</span>义工号</view>
 				<view class="pre-content">
-					<input v-model="memberInfo.volunteerNo" style="font-size: 30rpx;color: #252525;" placeholder="请输入义工号"></input>
+					<input v-model="memberInfo.volunteerNo" style="font-size: 30rpx;color: #252525;text-align: right;" placeholder-style="color: #A1A4A9;font-size: 28rpx;" placeholder="请输入义工号"></input>
 				</view>
 			</view>
 			<view class="pre adfac">
@@ -44,7 +67,7 @@
 						<image v-else src="https://oss.familydaf.cn/sxsnfile/20251218/a27c9b23939d4d5085ff790ed4bd2d6a.png"></image>
 						<text>男</text>
 					</view>
-					<view class="pre-content-box adfac" @click="changeMember(1,'gender')">
+					<view class="pre-content-box adfac" @click="changeMember(1,'gender')" style="margin-left: 114rpx;">
 						<image v-if="memberInfo.gender==1" src="https://oss.familydaf.cn/sxsnfile/20251218/ef2d43944c7e4cea8c6ebe0b4179f0fc.png"></image>
 						<image v-else src="https://oss.familydaf.cn/sxsnfile/20251218/a27c9b23939d4d5085ff790ed4bd2d6a.png"></image>
 						<text>女</text>
@@ -54,21 +77,24 @@
 			<view class="pre adfac" v-if="memberInfo.personnelType==2">
 				<view class="pre-title"><span>*</span>就读学校</view>
 				<view class="pre-content">
-					<input v-model="memberInfo.currentSchool" style="font-size: 30rpx;color: #252525;" placeholder="请输入就读学校"></input>
+					<input v-model="memberInfo.currentSchool" style="font-size: 30rpx;color: #252525;text-align: right;" placeholder-style="color: #A1A4A9;font-size: 28rpx;" placeholder="请输入就读学校"></input>
 				</view>
 			</view>
 		</view>
 		<view class="btn" @click="handleSave">保存</view>
+		<qf-image-cropper :width="500" :height="500" :radius="30" @crop="handleCrop" ref="qicRef" v-if="qicShow"></qf-image-cropper>
 	</view>
 </template>
 
 <script setup name="">
+	import { BaseApi2 } from '../common/api/baseApi';
 	import CusHeader from '@/components/CusHeader/index.vue'
 	import { onLoad } from '@dcloudio/uni-app' 
-	import { ref, getCurrentInstance } from 'vue'
+	import { ref, getCurrentInstance, nextTick } from 'vue'
 	const { proxy } = getCurrentInstance()
 	
 	const title = ref('添加家庭成员')
+	const defaultAvatar = ref('https://oss.familydaf.cn/sxsnfile/20251218/3821654e080945998d464f3c3aa64122.png')
 	const memberInfo = ref({
 		userId:'',
 		personnelType:1,
@@ -78,12 +104,16 @@
 		gender:0,
 		currentSchool:''
 	})
+	const qicShow = ref(false)
+	const turnType = ref('')
 	
 	const changeMember = (value,key) => {
 		memberInfo.value[key] = value;
 	}
 	
 	const handleSave = () => {
+		if(!memberInfo.value.avatarPath) return proxy.$showToast('请上传头像')
+		if(memberInfo.value.welfareName&&memberInfo.value.welfareName.length<3) return proxy.$showToast('个人公益名称至少三个字')
 		if(!memberInfo.value.name) return proxy.$showToast('请输入姓名')
 		if(!proxy.$reg.idCard(memberInfo.value.idCard)) return proxy.$showToast('请输入正确的身份证号')
 		if(!memberInfo.value.currentSchool&&memberInfo.value.personnelType==2) return proxy.$showToast('请输入就读学校')
@@ -93,6 +123,12 @@
 			if(res.code!==0) return proxy.$showToast(res.msg)
 			proxy.$showToast(memberInfo.value.id?'编辑成功':'添加成功')
 			setTimeout(()=>{
+				if(turnType.value==='my'){
+					uni.reLaunch({
+						url:'/pages/my'
+					})
+					return
+				}
 				uni.redirectTo({
 					url:'/pagesMy/familyMember'
 				})
@@ -109,7 +145,68 @@
 		})
 	}
 	
+	const changeAvatar = () => {
+		uni.showActionSheet({
+			itemList: ['从手机相册选择'],
+			success: (res) => {
+			  if (res.tapIndex === 0) { // 从手机相册选择
+				chooseLocalImage();
+			  }
+			}
+		});
+	}
+	
+	// 从本地选择图片并显示
+	const chooseLocalImage = () => {
+	  uni.chooseImage({
+		count: 1, // 只能选择一张
+		sizeType: ['compressed'], // 压缩图片
+		sourceType: ['album', 'camera'], // 可以从相册或相机选择
+		success: (res) => {
+		  const tempFilePaths = res.tempFilePaths;
+		  if (tempFilePaths && tempFilePaths.length > 0) {
+			qicShow.value = true;
+			nextTick(()=>{
+				proxy.$refs.qicRef.initImage(tempFilePaths[0],true)
+			})
+		  }
+		},
+		fail: (err) => {
+		  console.error('选择图片失败', err);
+		}
+	  });
+	}
+	
+	
+	const handleCrop = async (e) => {
+		const result = await uploadFilePromise(e.tempFilePath);
+		memberInfo.value.avatarPath = result||'';
+		qicShow.value = false;
+	}
+	
+	const uploadFilePromise = (url) => {
+	  return new Promise((resolve, reject) => {
+	    let a = uni.uploadFile({
+	      url: BaseApi2+'/sys/oss/upload',
+	      filePath: url,
+	      name: 'file',
+	      success: (res) => {
+	        setTimeout(() => {
+				let data = JSON.parse(res.data)
+				if(data&&data.code===0){
+					resolve(data.data);
+				}else proxy.$showToast(data?.msg)
+	        }, 1000);
+	      },
+		  fail: err =>{
+			resolve('');
+		  }
+	    });
+	  });
+	};
+	
 	onLoad((options)=>{
+		turnType.value = options.type;
 		const id = options?.id;
 		if(id){
 			title.value = '编辑家庭成员';
@@ -128,6 +225,54 @@
 			margin-top: 20rpx;
 			padding: 36rpx 24rpx 0;
 			
+			.avatar{
+				.title{
+					font-family: PingFang-SC, PingFang-SC;
+					font-weight: bold;
+					font-size: 30rpx;
+					color: #252525;
+					line-height: 42rpx;
+					label{
+						font-family: PingFangSC, PingFang SC;
+						font-weight: 400;
+						font-size: 30rpx;
+						color: #F4657A;
+						line-height: 42rpx;
+					}
+				}
+				.imgbox{
+					width: 122rpx;
+					height: 122rpx;
+					&-avatar{
+						width: 100%;
+						height: 100%;
+						border-radius: 50%;
+					}
+				}
+				.add-avatar{
+					margin-left: 40rpx;
+					width: 122rpx;
+					height: 122rpx;
+					border-radius: 50%;
+				}
+				border-bottom: 1rpx solid #E6E6E6;
+			}
+			.item{
+				height: 90rpx;
+				box-shadow: inset 0rpx -1rpx 0rpx 0rpx #E6E6E6;
+				.left{
+					width: 200rpx;
+					font-family: PingFangSC, PingFang SC;
+					font-weight: 400;
+					font-size: 28rpx;
+					color: #676775;
+					line-height: 40rpx;
+				}
+				.right{
+					width: calc(100% - 200rpx);
+				}
+			}
+			
 			.title{
 				font-family: PingFang-SC, PingFang-SC;
 				font-weight: bold;
@@ -157,8 +302,11 @@
 				}
 				&-content{
 					width: calc(100% - 209rpx);
+					display: flex;
+					align-items: center;
+					justify-content: flex-end;
 					&-box{
-						width: 184rpx;
+						margin-left: 80rpx;
 						image{
 							width: 28rpx;
 							height: 28rpx;

+ 11 - 7
pagesMy/heartNumber.vue

@@ -4,7 +4,7 @@
 		<image src="https://oss.familydaf.cn/sxsnfile/20251218/812f9cd7c1cf4c1b82853f3a6266e93a.png" class="top_bg_img" mode="widthFix"></image>
 		<view class="top">
 			<view class="num">{{loveValue}}</view>
-			<view class="text">我的爱心值</view>
+			<view class="text">{{isFamilyMember?'使用爱心值':'剩余爱心值'}}</view>
 		</view>
 		<view class="list" v-if="list.length">
 			<up-list @scrolltolower="scrolltolower" style="height: 100%;">
@@ -15,8 +15,9 @@
 							<view class="left adfac">
 								<image src="https://oss.familydaf.cn/sxsnfile/20251218/9ac66362a57b4c6d94ddbc9538735be7.png"></image>
 								<view class="texts">
-									<view class="p">{{transactionTypeDict[item.transactionType]||''}}</view>
-									<view class="p tip">{{'报名参与公益实践活动'}}</view>
+									<view class="p" v-if="item.transactionType==1">导入爱心值</view>
+									<view class="p" v-else>{{item.activityName||''}}</view>
+									<view class="p tip">{{item.memberName||''}}</view>
 								</view>
 							</view>
 							<view class="right adfac">
@@ -46,15 +47,17 @@
 	const queryParams = ref({
 		page:1,
 		limit:10,
-		userId:''
+		userId:'',
+		memberId:''
 	})
 	const transactionTypeDict = ref({
-		1:'渠道导入',
-		2:'公益捐赠'
+		1:'导入爱心值',
+		2:'参与献爱心'
 	})
 	const loveValue = ref(0)
 	const isOver = ref(false)
 	const list = ref([])
+	const isFamilyMember = ref(false)
 	
 	const scrolltolower = () => {
 		if(isOver.value) return
@@ -74,8 +77,10 @@
 	}
 	
 	onLoad(options=>{
+		isFamilyMember.value = uni.getStorageSync('isFamilyMember')?true:false;
 		loveValue.value = options.loveValue??0;
 		queryParams.value.userId = uni.getStorageSync('userInfo')&&JSON.parse(uni.getStorageSync('userInfo')).id;
+		if(uni.getStorageSync('familyMemberInfo')) queryParams.value.memberId = JSON.parse(uni.getStorageSync('familyMemberInfo')).id;
 		getList()
 	})
 </script>
@@ -140,7 +145,6 @@
 								font-weight: 400;
 								font-size: 32rpx;
 								color: #151B29;
-								line-height: 32rpx;
 								&.tip{
 									font-size: 24rpx;
 									color: #676775;

+ 76 - 1
pagesMy/volunteerHours.vue

@@ -1,6 +1,15 @@
 <template>
 	<view class="common_page adffc" :style="{'height':h+'px', 'padding-top':mt+'px'}">
 		<cus-header title="义工时长" bgColor="#FFFFFF"></cus-header>
+		<view class="member" v-if="!isFamilyMember">
+			<scroll-view class="scroll-view_H" scroll-x="true" scroll-with-animation="true" :scroll-left="scrollLeft">
+				<view class="scroll-view-item_H" :id="'svih_'+index" v-for="(item,index) in memberList" :key="index" @click="changeMember(item,index)">
+					<view class="cl_item" :class="{'active':midx===index}">
+						<text>{{item.name}}</text>
+					</view>
+				</view>
+			</scroll-view>
+		</view>
 		<view class="hours">义工时长:{{volunteerHours||0}}<text>小时</text></view>
 		<view class="list" v-if="list.length">
 			<up-list @scrolltolower="scrolltolower" style="height: 100%;">
@@ -11,7 +20,7 @@
 							<image src="https://oss.familydaf.cn/sxsnfile/20251218/681b23140cde486a8bf4b6844ce9b2e3.png"></image>
 							<view class="texts">
 								<view class="p">{{item.activityName??''}}</view>
-								<view class="p tip">{{item.typeName??''}}</view>
+								<view class="p tip">{{item.memberName||''}} - {{item.typeName??''}}</view>
 							</view>
 						</view>
 						<view class="right adfac">
@@ -37,10 +46,15 @@
 	const queryParams = ref({
 		page:1,
 		limit:10,
+		memberId:'',
 		userId:''
 	})
+	const midx = ref(0)
+	const scrollLeft = ref(0)
+	const memberList = ref([])
 	const volunteerHours = ref(0)
 	const isOver = ref(false)
+	const isFamilyMember = ref(false)
 	const list = ref([])
 	
 	const scrolltolower = () => {
@@ -48,6 +62,27 @@
 		getList()
 	}
 	
+	const initList = () => {
+		queryParams.value.page = 1;
+		isOver.value = false;
+		list.value = [];
+	}
+	const changeMember = (item,index) => {
+		midx.value = index;
+		queryParams.value.memberId = item.id;
+		initList()
+		getList()
+	}
+	
+	const getMemberList = () => {
+		let query = JSON.parse(JSON.stringify(queryParams.value))
+		query.limit = -1;
+		proxy.$api.get('/core/family/member/page',query).then(({data:res})=>{
+			if(res.code!==0) return proxy.$showToast(res.msg)
+			memberList.value = [{id:'',name:'全部'}].concat(res.data.list||[]);
+		})
+	}
+	
 	const getList = () => {
 		proxy.$api.get('/core/love/value/record/volunteerHoursList',queryParams.value).then(({data:res})=>{
 			if(res.code!==0) return proxy.$showToast(res.msg)
@@ -59,13 +94,53 @@
 	
 	onLoad(options=>{
 		volunteerHours.value = options.volunteerHours||0;
+		isFamilyMember.value = uni.getStorageSync('isFamilyMember')?true:false;
 		queryParams.value.userId = uni.getStorageSync('userInfo')&&JSON.parse(uni.getStorageSync('userInfo')).id;
+		if(uni.getStorageSync('familyMemberInfo')) queryParams.value.memberId = JSON.parse(uni.getStorageSync('familyMemberInfo')).id;
+		getMemberList()
 		getList()
 	})
 </script>
 
 <style scoped lang="scss">
+	.scroll-view_H {
+		white-space: nowrap;
+		width: 100%;
+	}
+	
+	.scroll-view-item_H {
+		display: inline-block;
+		height: 100%;
+		margin-left: 30rpx;
+		&:first-child{
+			margin-left: 0;
+		}
+	}
+	
 	.common_page{
+		.member{
+			width: 100%;
+			height: 56rpx;
+			margin-top: 20rpx;
+			margin-bottom: 16rpx;
+			box-sizing: border-box;
+			.cl_item{
+				padding: 0 26rpx;
+				height: 56rpx;
+				background: #FFFFFF;
+				border-radius: 10rpx;
+				font-family: PingFangSC, PingFang SC;
+				font-weight: 400;
+				font-size: 26rpx;
+				color: #252525;
+				line-height: 56rpx;
+				text-align: center;
+				&.active{
+					background: #B7F358;
+				}
+			}
+		}
+		
 		.hours{
 			padding: 0 24rpx;
 			height: 120rpx;