Parcourir la source

全部活动页剩余静态页完成

htc il y a 1 semaine
Parent
commit
6ea60f7fd1

+ 11 - 3
components/CusSearch/index.vue

@@ -2,9 +2,10 @@
 	<view class="search adfac">
 		<image class="icon" src="https://transcend.ringzle.com/xiaozhi-app/profile/2025/09/19/d568b395-8490-4e44-af30-7fb5288b8bad.png"></image>
 		<div class="input">
-			<up-input :placeholder="placeholder" v-model="keyword" border="none" style="font-size: 26rpx;"></up-input>
+			<up-input :placeholder="placeholder" v-model="keyword" border="none" style="font-size: 26rpx;" @confirm="handleSearch"></up-input>
 		</div>
-		<div class="btn" @tap="handleSearch">搜索</div>
+		<div class="btn" @tap="handleSearch" v-if="!isCancel">搜索</div>
+		<div class="btn" @tap="handleCancel" v-else>取消</div>
 	</view>
 </template>
 
@@ -13,17 +14,24 @@
 		placeholder:{
 			typeof: String,
 			default: '查找公益活动'
+		},
+		isCancel:{
+			typeof: Boolean,
+			default: false
 		}
 	})
 	
 	import { ref } from 'vue'
 	
 	const keyword = ref('')
-	const emits = defineEmits(['handleSearch'])
+	const emits = defineEmits(['handleSearch','handleCancel'])
 	
 	const handleSearch = () => {
 		emits('handleSearch',keyword.value);
 	}
+	const handleCancel = () => {
+		emits('handleCancel');
+	}
 </script>
 
 <style scoped lang="scss">

+ 77 - 0
components/pages/activityArea/city-data.js

@@ -0,0 +1,77 @@
+// 模拟的省市区数据
+// 真实项目中建议使用完整的城市数据JS或JSON文件
+export const cityData = [
+  {
+    "name": "北京",
+    "children": [
+      {
+        "name": "北京市",
+        "children": [
+          { "name": "东城区" },
+          { "name": "西城区" },
+          { "name": "朝阳区" },
+          { "name": "海淀区" }
+        ]
+      }
+    ]
+  },
+  {
+    "name": "浙江省",
+    "children": [
+      {
+        "name": "杭州市",
+        "children": [
+          { "name": "上城区" },
+          { "name": "下城区" },
+          { "name": "江干区" },
+          { "name": "拱墅区" },
+          { "name": "西湖区" },
+          { "name": "滨江区" }
+        ]
+      },
+      {
+        "name": "宁波市",
+        "children": [
+          { "name": "海曙区" },
+          { "name": "江北区" },
+          { "name": "镇海区" },
+          { "name": "北仑区" }
+        ]
+      },
+      {
+        "name": "温州市",
+        "children": [
+          { "name": "鹿城区" },
+          { "name": "龙湾区" },
+          { "name": "瓯海区" },
+          { "name": "萧山区" }
+        ]
+      }
+    ]
+  },
+  {
+    "name": "江苏省",
+    "children": [
+      {
+        "name": "南京市",
+        "children": [
+          { "name": "玄武区" },
+          { "name": "秦淮区" },
+          { "name": "建邺区" }
+        ]
+      },
+      {
+        "name": "苏州市",
+        "children": [
+          { "name": "姑苏区" },
+          { "name": "虎丘区" },
+          { "name": "吴中区" }
+        ]
+      }
+    ]
+  },
+  // ... 其他省份
+];
+
+// 热门城市
+export const hotCities = ['北京', '上海', '广州', '深圳', '杭州', '南京'];

+ 302 - 0
components/pages/activityArea/index.vue

@@ -0,0 +1,302 @@
+<template>
+  <view class="city-picker-container">
+    <view class="picker-content">
+      <view class="header">
+        <text class="title">城市地区</text>
+      </view>
+      <view class="hot-cities-section">
+        <view class="sub-title">热门城市</view>
+        <view class="hot-cities-grid">
+          <view
+            class="hot-city-item"
+            v-for="city in hotCities"
+            :key="city"
+            @click="handleHotCityClick(city)"
+          >
+            {{ city }}
+          </view>
+        </view>
+      </view>
+
+      <view class="divider"></view>
+
+      <!-- 选择器 -->
+      <view class="picker-wrapper">
+        <view class="picker-header">
+          <text>省份</text>
+          <text>城市</text>
+          <text>区县</text>
+        </view>
+        <picker-view :value="pickerValue" @change="handlePickerChange" class="picker-view">
+          <!-- 省份列 -->
+          <picker-view-column>
+            <view
+              class="picker-item"
+              :class="{ 'selected-item': pickerValue[0] === index }"
+              v-for="(province, index) in provinces"
+              :key="province.name"
+            >
+              {{ province.name }}
+            </view>
+          </picker-view-column>
+          <!-- 城市列 -->
+          <picker-view-column>
+            <view
+              class="picker-item"
+              :class="{ 'selected-item': pickerValue[1] === index }"
+              v-for="(city, index) in cities"
+              :key="city.name"
+            >
+              {{ city.name }}
+            </view>
+          </picker-view-column>
+          <!-- 区县列 -->
+          <picker-view-column>
+            <view
+              class="picker-item"
+              :class="{ 'selected-item': pickerValue[2] === index }"
+              v-for="(area, index) in areas"
+              :key="area.name"
+            >
+              {{ area.name }}
+            </view>
+          </picker-view-column>
+        </picker-view>
+      </view>
+
+      <!-- 确认按钮 -->
+      <view class="footer">
+        <button class="confirm-btn" @click="handleConfirm">确定</button>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup>
+import { ref, watch, computed } from 'vue';
+import { cityData, hotCities } from './city-data.js';
+
+const emit = defineEmits(['update:show', 'confirm']);
+
+// --- Data ---
+const provinces = ref(cityData);
+const cities = ref([]);
+const areas = ref([]);
+
+const pickerValue = ref([0, 0, 0]);
+
+const selectedProvince = computed(() => provinces.value[pickerValue.value[0]] || {});
+const selectedCity = computed(() => cities.value[pickerValue.value[1]] || {});
+const selectedArea = computed(() => areas.value[pickerValue.value[2]] || {});
+
+
+// 确认选择
+const handleConfirm = () => {
+  const result = {
+    province: selectedProvince.value.name,
+    city: selectedCity.value.name,
+    area: selectedArea.value.name,
+  };
+  emit('confirm', result);
+};
+
+// picker-view 滚动时触发
+const handlePickerChange = (e) => {
+  const newPickerValue = e.detail.value;
+  const [provinceIndex, cityIndex, areaIndex] = newPickerValue;
+  const oldProvinceIndex = pickerValue.value[0];
+  const oldCityIndex = pickerValue.value[1];
+
+  // 如果省份改变了
+  if (provinceIndex !== oldProvinceIndex) {
+    // 更新城市列表和地区列表,并将它们的索引重置为0
+    cities.value = provinces.value[provinceIndex].children;
+    areas.value = cities.value[0]?.children || [];
+    pickerValue.value = [provinceIndex, 0, 0];
+  } 
+  // 如果城市改变了
+  else if (cityIndex !== oldCityIndex) {
+    // 更新地区列表,并将地区索引重置为0
+    areas.value = cities.value[cityIndex]?.children || [];
+    pickerValue.value = [provinceIndex, cityIndex, 0];
+  } 
+  // 如果只是地区改变
+  else {
+    pickerValue.value = newPickerValue;
+  }
+};
+
+// 点击热门城市
+// 新方法:遍历cityData找到对应的省市区并更新pickerValue
+const handleHotCityClick = (cityName) => {
+  let provinceIndex = -1;
+  let cityIndex = -1;
+  let found = false;
+
+  // 遍历省份
+  for (let i = 0; i < provinces.value.length; i++) {
+    const province = provinces.value[i];
+    // 遍历城市
+    if (province.children && province.children.length > 0) {
+      for (let j = 0; j < province.children.length; j++) {
+        const city = province.children[j];
+        // 检查城市名是否匹配(使用 startsWith 来兼容 "杭州" 和 "杭州市")
+        if (city.name.startsWith(cityName)) {
+          provinceIndex = i;
+          cityIndex = j;
+          found = true;
+          break; // 找到城市,跳出内层循环
+        }
+      }
+    }
+    if (found) {
+      break; // 找到城市,跳出外层循环
+    }
+  }
+
+  // 如果找到了对应的城市
+  if (found) {
+    // 1. 更新城市列表,使其为选中省份的城市列表
+    cities.value = provinces.value[provinceIndex].children;
+    
+    // 2. 更新区县列表,使其为选中城市的区县列表
+    //    使用可选链 ?. 防止选中城市没有区县数据(如海南省直辖县)
+    areas.value = cities.value[cityIndex]?.children || [];
+    
+    // 3. 更新 pickerValue,这会驱动 picker-view 滚动到指定位置
+    //    区县默认选择第一个 (索引为0)
+    pickerValue.value = [provinceIndex, cityIndex, 0];
+  } else {
+    // 如果在数据中找不到该热门城市,可以给一个提示
+    uni.showToast({
+      title: `未在数据源中找到 ${cityName}`,
+      icon: 'none'
+    });
+  }
+};
+
+// 初始化数据
+const initialize = () => {
+  const [pIndex, cIndex] = pickerValue.value;
+  cities.value = provinces.value[pIndex]?.children || [];
+  areas.value = cities.value[cIndex]?.children || [];
+};
+
+// 初始化
+initialize();
+
+defineExpose({
+	initialize
+})
+</script>
+
+<style lang="scss" scoped>
+.city-picker-container {
+  .picker-content {
+    width: 100%;
+    background-color: #ffffff;
+    border-radius: 0 0 24rpx 24rpx;
+    display: flex;
+    flex-direction: column;
+  }
+}
+
+.header {
+  padding: 36rpx 0 0 28rpx;
+  position: relative;
+  .title {
+    font-family: PingFang-SC, PingFang-SC;
+    font-weight: bold;
+    font-size: 36rpx;
+    color: #252525;
+    line-height: 36rpx;
+  }
+}
+
+.hot-cities-section {
+  padding: 0 28rpx;
+  margin-top: 40rpx;
+  .sub-title {
+    font-family: PingFangSC, PingFang SC;
+    font-weight: 400;
+    font-size: 24rpx;
+    color: #989998;
+    line-height: 24rpx;
+  }
+  .hot-cities-grid {
+    display: grid;
+    grid-template-columns: repeat(4, 1fr);
+    gap: 20rpx;
+	margin: 24rpx 0;
+  }
+  .hot-city-item {
+    border-radius: 6rpx;
+    border: 1rpx solid #E5E7EB;
+    padding: 13rpx 0;
+    font-family: PingFangSC, PingFang SC;
+    font-weight: 400;
+    font-size: 24rpx;
+    color: #252525;
+    line-height: 33rpx;
+    text-align: center;
+  }
+}
+
+.divider {
+  height: 14rpx;
+  background: #F7F7F7;
+}
+
+.picker-wrapper {
+  .picker-header {
+    display: flex;
+    justify-content: space-around;
+    padding: 20rpx 0;
+    font-family: PingFang-SC, PingFang-SC;
+    font-weight: bold;
+    font-size: 28rpx;
+    color: #252525;
+    line-height: 36rpx;
+  }
+}
+
+.picker-view {
+  width: 100%;
+  height: 380rpx;
+}
+
+.picker-item {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 30rpx;
+  color: #A4A4A4;
+  transition: all 0.2s;
+  
+  &.selected-item {
+    font-size: 32rpx;
+    color: #252525;
+  }
+}
+
+.footer {
+  padding: 64rpx 147rpx 40rpx;
+  .confirm-btn {
+    width: 100%;
+    height: 80rpx;
+    line-height: 88rpx;
+    background: #B7F358;
+    border-radius: 45rpx;
+    font-family: PingFang-SC, PingFang-SC;
+    font-weight: bold;
+    font-size: 28rpx;
+    color: #252525;
+    line-height: 80rpx;
+	letter-spacing: 2rpx;
+    border: none;
+    &:after {
+      border: none;
+    }
+  }
+}
+</style>

+ 6 - 0
pages.json

@@ -28,6 +28,12 @@
 					"style": {
 						"navigationStyle": "custom"
 					}
+				},
+				{
+					"path": "searchActivity",
+					"style": {
+						"navigationStyle": "custom"
+					}
 				}
 			]
 		}

+ 72 - 12
pagesHome/allActivity.vue

@@ -2,8 +2,8 @@
 	<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 class="top-search" @tap="toSearch">
+			<cus-search></cus-search>
 		</div>
 		<div class="type adfacjb">
 			<div class="type-list">
@@ -22,14 +22,14 @@
 				<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">
+			<div class="filter-pre adfac" @tap="placeShow=true;timeShow=false;allShow=false">
 				<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">
+			<!-- <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>
 		<div class="list" id="list">
 			<up-list @scrolltolower="scrolltolower" style="height: 100%;">
@@ -56,10 +56,14 @@
 			</div>
 		</div>
 		<div class="window" :style="{'top':top+'px','height':'calc(100vh - '+top+'px)'}" v-if="placeShow">
-			<div class="window-place"></div>
+			<div class="window-place">
+				<ActivityArea @confirm="areaConfirm"></ActivityArea>
+			</div>
 		</div>
 		<div class="window" :style="{'top':topAll+'px','height':'calc(100vh - '+topAll+'px)'}" v-if="allShow">
-			<div class="window-all"></div>
+			<div class="window-all">
+				<div class="pre" v-for="(t,i) in typeList2" :key="i" @tap="changeType2(t.name,i)">{{t.name}}</div>
+			</div>
 		</div>
 	</view>
 </template>
@@ -69,6 +73,7 @@
 	import CusSearch from '@/components/CusSearch/index.vue'
 	import NonprofitActivety from '@/components/pages/nonprofitActivety/index.vue'
 	import ActivityCalendar from '@/components/pages/activityCalendar/index.vue'
+	import ActivityArea from '@/components/pages/activityArea/index.vue'
 	import { ref, onMounted, nextTick } from 'vue'
 	
 	const typeIndex = ref('')
@@ -80,6 +85,7 @@
 		{id:5,name:'健康行动'},
 		{id:6,name:'减肥运动'}
 	])
+	const typeList2 = ref([])
 	const scrollLeft = ref(0)
 	const time = ref('全部时间')
 	const timeText = ref('')
@@ -95,8 +101,15 @@
 	const wtIndex = ref(0)
 	const acRef = ref(null)
 	
-	const search = (keyword) => {
-		console.log(keyword);
+	const toSearch = () => {
+		uni.navigateTo({
+			url:'/pagesHome/searchActivity',
+			events:{
+				confirmSearch: data => {
+					console.log(data,'data');
+				}
+			}
+		})
 	}
 	
 	const changeType = (item,index) => {
@@ -107,11 +120,32 @@
 				scrollLeft.value = (index-2)*154/2;
 			}
 		}
+		allShow.value = false;
+	}
+	const changeType2 = (item,index) => {
+		if(item==='全部'){
+			typeIndex.value = '';
+			scrollLeft.value = 0;
+		}else{
+			index--;
+			typeIndex.value = index;
+			if(typeList.value.length>4){
+				if(index<3) scrollLeft.value = 0
+				else{
+					scrollLeft.value = (index-2)*154/2;
+				}
+			}
+		}
+		allShow.value = false;
 	}
 	
 	const handleAll = () => {
-		typeIndex.value = '';
-		scrollLeft.value = 0;
+		// typeIndex.value = '';
+		// scrollLeft.value = 0;
+		timeShow.value = false;
+		placeShow.value = false;
+		
+		typeList2.value = [{id:'',name:'全部'}].concat(typeList.value);
 		allShow.value = true;
 	}
 	
@@ -132,6 +166,11 @@
 		timeShow.value = false;
 	}
 	
+	const areaConfirm = (data) => {
+		place.value = data.city+'-'+data.area;
+		placeShow.value = false;
+	}
+	
 	const scrolltolower = () => {
 		console.log(1);
 	}
@@ -145,7 +184,7 @@
 		}).exec()
 		query.select('#filter').boundingClientRect(rect=>{
 			if(rect){
-				topAll.value = (rect?.top-13)||0;
+				topAll.value = rect?.top||0;
 			}
 		}).exec()
 	}
@@ -321,5 +360,26 @@
 				}
 			}
 		}
+	
+		&-all{
+			padding: 26rpx 30rpx 103rpx;
+			background: #FFFFFF;
+			display: flex;
+			flex-wrap: wrap;
+			justify-content: space-between;
+			.pre{
+				width: calc(100% / 3 - 12rpx);
+				height: 59rpx;
+				background: #F7F7F7;
+				border-radius: 30rpx;
+				font-family: PingFangSC, PingFang SC;
+				font-weight: 400;
+				font-size: 24rpx;
+				color: #252525;
+				line-height: 59rpx;
+				text-align: center;
+				margin-top: 24rpx;
+			}
+		}
 	}
 </style>

+ 86 - 0
pagesHome/searchActivity.vue

@@ -0,0 +1,86 @@
+<template>
+	<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 :isCancel="true" @handleSearch="handleSearch"></cus-search>
+		</div>
+		<div class="text">最近搜索</div>
+		<div class="list">
+			<div class="pre" v-for="(item,index) in list" :key="index">{{item}}</div>
+		</div>
+	</view>
+</template>
+
+<script setup name="">
+	import CusHeader from '@/components/CusHeader/index.vue'
+	import CusSearch from '@/components/CusSearch/index.vue'
+	import { ref, getCurrentInstance, onMounted } from 'vue'
+	const { proxy } = getCurrentInstance()
+	
+	const list = ref([])
+	
+	const handleSearch = data => {
+		let sh = uni.getStorageSync('searchHistory');
+		try{
+			let sh_arr = [];
+			if(sh) sh_arr = JSON.parse(sh);
+			let arr = Array.from(new Set(sh_arr.concat([data])));
+			uni.setStorageSync('searchHistory',JSON.stringify(arr));
+		}catch(e){
+			console.log(e,'e');
+		}	
+		proxy.getOpenerEventChannel().emit('confirmSearch',{
+			data
+		})
+		uni.navigateBack();
+	}
+	
+	const getSearchHistory = () => {
+		let sh = uni.getStorageSync('searchHistory');
+		try{
+			list.value = JSON.parse(sh?sh:'[]')
+		}catch(e){
+			console.log(e,'e');
+		}
+	}
+	
+	onMounted(()=>{
+		getSearchHistory()
+	})
+</script>
+
+<style scoped lang="scss">
+	.common_page{
+		.top-search{
+			position: relative;
+			margin-top: 20rpx;
+		}
+		.text{
+			font-family: PingFang-SC, PingFang-SC;
+			font-weight: bold;
+			font-size: 36rpx;
+			color: #252525;
+			line-height: 50rpx;
+			margin-top: 40rpx;
+			position: relative;
+		}
+		.list{
+			position: relative;
+			flex: 1;
+			margin-top: 4rpx;
+			overflow-y: auto;
+			.pre{
+				background: #FFFFFF;
+				border-radius: 24rpx;
+				padding: 25rpx 24rpx;
+				font-family: PingFangSC, PingFang SC;
+				font-weight: 400;
+				font-size: 28rpx;
+				color: #252525;
+				line-height: 40rpx;
+				margin-top: 24rpx;
+			}
+		}
+	}
+</style>