Kaynağa Gözat

时间筛选部分功能完成(自定义日历组件)

htc 1 hafta önce
ebeveyn
işleme
7fe9e22272

+ 234 - 0
components/pages/activityCalendar/index.vue

@@ -0,0 +1,234 @@
+<template>
+	<view class="page adffc">
+		<div class="week adf">
+			<div class="week-pre adfacjc" v-for="(w, i) in weeks" :class="{ active: i === 0 || i === 6 }" :key="i">{{ w }}</div>
+		</div>
+		<div class="list">
+			<div class="list-item" v-for="(c,i) in calendarsCopy" :key="i">
+				<div class="title">{{c.monthTitle}}</div>
+				<div class="days">
+					<div class="days-pre" 
+					:class="{'active':d.selected&&d.isCurrentMonth,'in':d.inrange&&d.isCurrentMonth}" 
+					v-for="(d,di) in c.days" :key="di">
+						<div class="today" v-if="d.isToday">今天</div>
+						<div class="day adfacjc" :class="{'hui':d.isPast}">{{d.isCurrentMonth?d.day:''}}</div>
+						<div class="se" v-if="d.start&&d.isCurrentMonth">开始</div>
+						<div class="se" v-if="d.end&&d.isCurrentMonth">结束</div>
+					</div>
+				</div>
+			</div>
+		</div>
+	</view>
+</template>
+
+<script setup name="ActivityCalendar">
+import { ref, onMounted } from 'vue';
+
+const weeks = ['日', '一', '二', '三', '四', '五', '六'];
+const calendars = ref([]);
+const calendarsCopy = ref([]);
+
+const createCalendar = (months) => {
+	return new Promise((resolve,reject)=>{
+		const calendarList = [];
+		const today = new Date();
+		today.setHours(0, 0, 0, 0); // 标准化日期
+		
+		for (let i = 0; i < months; i++) {
+			// --- 1. 计算目标月份 ---
+			const targetDate = new Date();
+			targetDate.setHours(0, 0, 0, 0);
+			targetDate.setMonth(targetDate.getMonth() + i, 1);
+		
+			const year = targetDate.getFullYear();
+			const month = targetDate.getMonth(); // 0-11
+		
+			const monthData = {
+				monthTitle: `${year}/${String(month + 1).padStart(2, '0')}`,
+				days: []
+			};
+		
+			// --- 2. 计算日历网格的起始和结束日期 ---
+			let startDate;
+			const firstDayOfMonth = new Date(year, month, 1);
+			const lastDayOfMonth = new Date(year, month + 1, 0);
+		
+			// **核心逻辑:根据是否为第一个月,决定网格的起始日期**
+			if (i === 0) {
+				// **第一个月(当前月):** 网格从今天所在周的周日开始
+				startDate = new Date(today);
+				startDate.setDate(today.getDate() - today.getDay());
+			} else {
+				// **后续月份:** 网格从该月第一天所在周的周日开始,保证月份的完整性
+				startDate = new Date(firstDayOfMonth);
+				startDate.setDate(firstDayOfMonth.getDate() - firstDayOfMonth.getDay());
+			}
+		
+			// **网格的结束日期总是该月最后一天所在周的周六**
+			const endDate = new Date(lastDayOfMonth);
+			endDate.setDate(lastDayOfMonth.getDate() + (6 - lastDayOfMonth.getDay()));
+		
+			// --- 3. 循环填充日历网格中的每一天 ---
+			for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
+				const currentYear = d.getFullYear();
+				const currentMonth = d.getMonth() + 1;
+				const currentDay = d.getDate();
+		
+				monthData.days.push({
+					day: currentDay,
+					dateStr: `${currentYear}-${String(currentMonth).padStart(2, '0')}-${String(currentDay).padStart(2, '0')}`,
+					isToday: d.getTime() === today.getTime(),
+					// 标识是否属于当前正在生成的月份 (e.g., 在7月数据中,8月1日这项为 false)
+					isCurrentMonth: d.getMonth() === month,
+					// 标识是否为过去日期 (仅在第一个月生效)
+					isPast: i === 0 && d.getTime() < today.getTime()
+				});
+			}
+		
+			calendarList.push(monthData);
+		}
+		resolve(calendarList)
+	})
+};
+
+const setStartEndDay = type => {//type 0:全部时间 1:一周内 2:一月内 3:本周末
+	let d = new Date();
+	let s = new Date().Format('yyyy-MM-dd');
+	let e = null;
+	switch(type){
+		case 0:
+			s = e = '';
+			break;
+		case 1:
+			e = new Date(d.setDate(d.getDate(s)+6)).Format('yyyy-MM-dd');
+			break;
+		case 2:
+			e = new Date(d.setDate(d.getDate(s)+30)).Format('yyyy-MM-dd');
+			break;
+		case 3:
+			let sw = new Date(s).getDay();
+			if(sw===0){//当天是周日
+				s = new Date(d.setDate(d.getDate(s)-1)).Format('yyyy-MM-dd');
+				e = new Date(s).Format('yyyy-MM-dd');
+			}else{
+				s = new Date(d.setDate(d.getDate(s)+(6-sw))).Format('yyyy-MM-dd');
+				e = new Date(d.setDate(d.getDate(s)+1)).Format('yyyy-MM-dd');
+			}
+			break;
+	}
+	calendarsCopy.value = JSON.parse(JSON.stringify(calendars.value));
+	if(!s||!e) return
+	calendarsCopy.value.forEach(c=>{
+		c?.days.forEach(d=>{
+			if(d.dateStr===s||d.dateStr===e){
+				d.selected = true;
+				d.start = d.dateStr===s;
+				d.end = d.dateStr===e;
+			}
+			if(Date.parse(new Date(d.dateStr))>Date.parse(new Date(s))&&Date.parse(new Date(d.dateStr))<Date.parse(new Date(e))){
+				d.inrange = true;
+			}
+		})
+	})
+}
+
+onMounted(async () => {
+	calendars.value = await createCalendar(2);
+	calendarsCopy.value = JSON.parse(JSON.stringify(calendars.value));
+});
+
+defineExpose({
+	setStartEndDay
+})
+</script>
+
+<style scoped lang="scss">
+.page {
+	width: 100%;
+	height: 100%;
+	background: #f7f7f7;
+	.week {
+		width: 100%;
+		height: 81rpx;
+		background: #ffffff;
+		&-pre {
+			width: calc(100% / 7);
+			height: 100%;
+			font-family: PingFangSC, PingFang SC;
+			font-weight: 400;
+			font-size: 24rpx;
+			color: #6b7280;
+			line-height: 33rpx;
+			&.active {
+				font-weight: bold;
+				color: #70cf52;
+			}
+		}
+	}
+	.list {
+		flex: 1;
+		overflow-y: auto;
+		&-item{
+			.title{
+				font-family: PingFang-SC, PingFang-SC;
+				font-weight: bold;
+				font-size: 30rpx;
+				color: #252525;
+				line-height: 30rpx;
+				text-align: center;
+				margin-top: 30rpx;
+			}
+			.days{
+				display: flex;
+				flex-wrap: wrap;
+				margin-top: 40rpx;
+				&-pre{
+					width: calc(100% / 7);
+					height: 120rpx;
+					position: relative;
+					&.active{
+						background: #B7F358;
+					}
+					&.in{
+						background: rgba(183, 243,88, .4);
+					}
+					.day{
+						width: 100%;
+						height: 100%;
+						font-family: PingFang-SC, PingFang-SC;
+						font-weight: bold;
+						font-size: 30rpx;
+						color: #252525;
+						line-height: 42rpx;
+						&.hui{
+							color: #989998;
+						}
+					}
+					.today{
+						width: 100%;
+						font-family: PingFangSC, PingFang SC;
+						font-weight: 400;
+						font-size: 20rpx;
+						color: #989998;
+						line-height: 28rpx;
+						text-align: center;
+						position: absolute;
+						top: 10rpx;
+					}
+					.se{
+						width: 100%;
+						font-family: PingFangSC, PingFang SC;
+						font-weight: bold;
+						font-size: 20rpx;
+						color: #989998;
+						line-height: 28rpx;
+						text-align: center;
+						position: absolute;
+						bottom: 15rpx;
+					}
+				}
+			}
+		}
+	}
+}
+</style>

+ 299 - 2
pagesHome/allActivity.vue

@@ -1,28 +1,325 @@
 <template>
-	<view class="common_page" :style="{'min-height':h+'px', 'padding-top':mt+'px'}">
+	<view class="common_page adffc" :style="{'height':h+'px', 'padding-top':mt+'px'}">
 		<cus-header title="全部活动" bgColor="transparent"></cus-header>
 		<image src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/09/19/54b75bc8-d926-449b-95a5-1126f700b481.png" class="top_bg_img" mode="widthFix"></image>
 		<div class="top-search">
 			<cus-search @handleSearch="search"></cus-search>
 		</div>
+		<div class="type adfacjb">
+			<div class="type-list">
+				<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 typeList" :key="index" @tap="changeType(item,index)">
+						<view class="cl_item" :class="{'active':typeIndex===index}">
+							<text>{{item.name}}</text>
+						</view>
+					</view>
+				</scroll-view>
+			</div>
+			<div class="type-all" :class="{'active':typeIndex===''}" @tap="handleAll">全部</div>
+		</div>
+		<div class="filter adfac" id="filter">
+			<div class="filter-pre adfac" @tap="showTime">
+				<text>{{time}}</text>
+				<image src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/09/19/1813f2d7-42e9-4c5d-9d05-924e30568f9e.png"></image>
+			</div>
+			<div class="filter-pre adfac" @tap="placeShow=true">
+				<text>{{place}}</text>
+				<image src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/09/19/1813f2d7-42e9-4c5d-9d05-924e30568f9e.png"></image>
+			</div>
+			<div class="filter-pre adfac">
+				<text>{{status}}</text>
+				<image src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/09/19/1813f2d7-42e9-4c5d-9d05-924e30568f9e.png"></image>
+			</div>
+		</div>
+		<div class="list" id="list">
+			<up-list @scrolltolower="scrolltolower" style="height: 100%;">
+				<up-list-item v-for="(item, index) in list" :key="index">
+					<NonprofitActivety></NonprofitActivety>
+				</up-list-item>
+			</up-list>
+		</div>
+		<div class="window" :style="{'top':top+'px','height':'calc(100vh - '+top+'px)'}" v-if="timeShow">
+			<div class="window-time">
+				<div class="top adfac">
+					<div class="top-pre" :class="{'active':wtIndex===0}" @tap="changeWindowTime(0,'全部时间')">全部时间</div>
+					<div class="top-pre" :class="{'active':wtIndex===1}" @tap="changeWindowTime(1,'一周内')">一周内</div>
+					<div class="top-pre" :class="{'active':wtIndex===2}" @tap="changeWindowTime(2,'一月内')">一月内</div>
+					<div class="top-pre" :class="{'active':wtIndex===3}" @tap="changeWindowTime(3,'本周末')">本周末</div>
+				</div>
+				<div class="middle">
+					<ActivityCalendar ref="acRef"></ActivityCalendar>
+				</div>
+				<div class="bottom adfacjb">
+					<div class="reset" @tap="changeWindowTime(0,'全部时间')">重置</div>
+					<div class="confirm" @tap="timeConfirm">确定</div>
+				</div>
+			</div>
+		</div>
+		<div class="window" :style="{'top':top+'px','height':'calc(100vh - '+top+'px)'}" v-if="placeShow">
+			<div class="window-place"></div>
+		</div>
+		<div class="window" :style="{'top':topAll+'px','height':'calc(100vh - '+topAll+'px)'}" v-if="allShow">
+			<div class="window-all"></div>
+		</div>
 	</view>
 </template>
 
 <script setup name="">
 	import CusHeader from '@/components/CusHeader/index.vue'
 	import CusSearch from '@/components/CusSearch/index.vue'
-	import { ref } from 'vue'
+	import NonprofitActivety from '@/components/pages/nonprofitActivety/index.vue'
+	import ActivityCalendar from '@/components/pages/activityCalendar/index.vue'
+	import { ref, onMounted, nextTick } from 'vue'
+	
+	const typeIndex = ref('')
+	const typeList = ref([
+		{id:1,name:'儿童关爱'},
+		{id:2,name:'老人关爱'},
+		{id:3,name:'社区发展'},
+		{id:4,name:'社会服务'},
+		{id:5,name:'健康行动'},
+		{id:6,name:'减肥运动'}
+	])
+	const scrollLeft = ref(0)
+	const time = ref('全部时间')
+	const timeText = ref('')
+	const place = ref('全部地区')
+	const status = ref('活动状态')
+	const queryParams = ref({})
+	const list = ref([1,1,1,1])
+	const top = ref(0)
+	const topAll = ref(0)
+	const timeShow = ref(false)
+	const placeShow = ref(false)
+	const allShow = ref(false)
+	const wtIndex = ref(0)
+	const acRef = ref(null)
 	
 	const search = (keyword) => {
 		console.log(keyword);
 	}
+	
+	const changeType = (item,index) => {
+		typeIndex.value = index;
+		if(typeList.value.length>4){
+			if(index<3) scrollLeft.value = 0
+			else{
+				scrollLeft.value = (index-2)*154/2;
+			}
+		}
+	}
+	
+	const handleAll = () => {
+		typeIndex.value = '';
+		scrollLeft.value = 0;
+		allShow.value = true;
+	}
+	
+	const changeWindowTime = (wtindex,text) => {
+		wtIndex.value = wtindex;
+		timeText.value = text;
+		acRef.value.setStartEndDay(wtindex);
+	}
+	
+	const showTime = () => {
+		timeShow.value = true;
+		setTimeout(()=>{
+			acRef.value.setStartEndDay(wtIndex.value);
+		},100)
+	}
+	const timeConfirm = () => {
+		time.value = timeText.value;
+		timeShow.value = false;
+	}
+	
+	const scrolltolower = () => {
+		console.log(1);
+	}
+	
+	const getTop = () => {
+		let query = uni.createSelectorQuery();
+		query.select('#list').boundingClientRect(rect=>{
+			if(rect){
+				top.value = (rect?.top+20)||0;//20是上间距
+			}
+		}).exec()
+		query.select('#filter').boundingClientRect(rect=>{
+			if(rect){
+				topAll.value = (rect?.top-13)||0;
+			}
+		}).exec()
+	}
+	onMounted(()=>{
+		nextTick(()=>{
+			getTop();
+		})
+	})
 </script>
 
 <style scoped lang="scss">
+	.scroll-view_H {
+		white-space: nowrap;
+		width: 100%;
+	}
+	
+	.scroll-view-item_H {
+		display: inline-block;
+		height: 50rpx;
+		margin-left: 42rpx;
+		&:first-child{
+			margin-left: 0;
+		}
+	}
+	
 	.common_page{
 		.top-search{
 			position: relative;
 			margin-top: 20rpx;
 		}
+		
+		.type{
+			margin-top: 40rpx;
+			width: 100%;
+			height: 50rpx;
+			position: relative;
+			&-list{
+				width: calc(100% - 72rpx);
+				padding-right: 40rpx;
+				box-sizing: border-box;
+				.cl_item{
+					width: 112rpx;
+					line-height: 50rpx;
+					font-family: PingFangSC, PingFang SC;
+					font-weight: 400;
+					font-size: 30rpx;
+					color: #676775;
+					&.active{
+						font-weight: bold;
+						font-size: 36rpx;
+						color: #252525;
+						line-height: 50rpx;
+					}
+				}
+			}
+			&-all{
+				width: 72rpx;
+				font-family: PingFangSC, PingFang SC;
+				font-weight: 400;
+				font-size: 28rpx;
+				color: #676775;
+				line-height: 40rpx;
+				position: relative;
+				&.active{
+					font-weight: bold;
+					font-size: 36rpx;
+					color: #252525;
+					line-height: 50rpx;
+					&::after{
+						content: '';
+						width: 40rpx;
+						height: 8rpx;
+						background: linear-gradient( 270deg, #B7F358 0%, #00AE57 100%);
+						border-radius: 4rpx;
+						position: absolute;
+						left: 50%;
+						margin-left: -20rpx;
+						bottom: -10rpx;
+					}
+				}
+			}
+		}
+	
+		.filter{
+			margin-top: 33rpx;
+			position: relative;
+			&-pre{
+				width: 186rpx;
+				text{
+					font-family: PingFang-SC, PingFang-SC;
+					font-weight: bold;
+					font-size: 24rpx;
+					color: #252525;
+					line-height: 33rpx;
+				}
+				image{
+					width: 24rpx;
+					height: 24rpx;
+					margin-left: 6rpx;
+				}
+			}
+		}
+	
+		.list{
+			flex: 1;
+			padding: 20rpx 0;
+			box-sizing: border-box;
+			overflow-y: auto;
+		}
+	}
+	
+	.window{
+		width: 100%;
+		position: fixed;
+		left: 0;
+		right: 0;
+		background: rgba(0, 0, 0, .4);
+		
+		&-time{
+			background: #FFFFFF;
+			border-radius: 0 0 24rpx 24rpx;
+			.top{
+				padding: 32rpx 30rpx;
+				&-pre{
+					padding: 10rpx 24rpx;
+					background: #F5F6F8;
+					border-radius: 27rpx;
+					font-family: PingFangSC, PingFang SC;
+					font-weight: 400;
+					font-size: 24rpx;
+					color: #252525;
+					line-height: 33rpx;
+					margin-left: 20rpx;
+					&:first-child{
+						margin-left: 0;
+					}
+					&.active{
+						background: #B7F358;
+						font-weight: bold;
+					}
+				}
+			}
+			.middle{
+				height: 594rpx;
+				background: #F7F7F7;
+			}
+			.bottom{
+				padding: 24rpx 24rpx 36rpx;
+				.reset{
+					width: 222rpx;
+					height: 80rpx;
+					border-radius: 45rpx;
+					border: 1rpx solid #252525;
+					font-family: PingFang-SC, PingFang-SC;
+					font-weight: bold;
+					font-size: 28rpx;
+					color: #252525;
+					line-height: 80rpx;
+					text-align: center;
+					letter-spacing: 2rpx;
+				}
+				.confirm{
+					width: calc(100% - 252rpx);
+					height: 80rpx;
+					background: #B7F358;
+					border-radius: 45rpx;
+					font-family: PingFang-SC, PingFang-SC;
+					font-weight: bold;
+					font-size: 28rpx;
+					color: #252525;
+					line-height: 80rpx;
+					text-align: center;
+				}
+			}
+		}
 	}
 </style>