Browse Source

📦 首页与地图搜索村社功能

快乐的梦鱼 4 weeks ago
parent
commit
f90123510d
3 changed files with 143 additions and 13 deletions
  1. 53 4
      src/pages/home/components/LightMap.vue
  2. 24 9
      src/pages/home/index.vue
  3. 66 0
      src/pages/home/search/index.vue

+ 53 - 4
src/pages/home/components/LightMap.vue

@@ -55,6 +55,17 @@
       <ActivityIndicator :size="64" />
     </FlexCol>
 
+    <NButton 
+      :innerStyle="{
+        position: 'absolute',
+        bottom: '20rpx',
+        left: '20rpx',
+        zIndex: 100,
+        backgroundColor: '#ffffff',
+      }"
+      icon="search" 
+      @click="showSearch" 
+    />
     <SimpleDropDownPicker 
       v-if="!isEmptyRegion"
       class="light-map-region-picker" 
@@ -74,6 +85,17 @@
       icon="navigation" 
       @click="emit('getCurrentLonlat')" 
     />
+    <Dialog 
+      v-model:show="searchDialogShow" 
+      title="搜索村社"
+      showCancel
+      :maskClosable="false"
+      :onConfirm="searchConfirm"
+    >
+      <template #content>
+        <Field v-model="searchKeyword" placeholder="请输入村社名称进行搜索" />
+      </template>
+    </Dialog>
   </div>
 </template>
 
@@ -93,12 +115,23 @@ import Icon from '@/components/basic/Icon.vue';
 import ActivityIndicator from '@/components/basic/ActivityIndicator.vue';
 import type { MapMarker } from '@/types/Map';
 import MapApi from '@/api/map/MapApi';
+import Dialog from '@/components/dialog/Dialog.vue';
+import Field from '@/components/form/Field.vue';
+import { toast } from '@/components/dialog/CommonRoot';
 
 const instance = getCurrentInstance();
 const mapCtx = uni.createMapContext('prevMap', instance);
+
 const selectedRegion = ref<number>();
 const nextNeedAutoFocus = ref(false);
 const villageData = new Map<number, VillageListItem>();
+
+const ready = ref(false);
+const hasResItems = ref(false);
+
+const searchDialogShow = ref(false);
+const searchKeyword = ref('');
+
 const emit = defineEmits([
   'getCurrentLonlat',
   'update:isLightMode', 
@@ -107,8 +140,6 @@ const emit = defineEmits([
   'regionChanged',
   'lightVillage',
 ]);
-const ready = ref(false);
-const hasResItems = ref(false);
 
 const props = defineProps<{
   lonlat?: { longitude: number, latitude: number } | undefined;
@@ -134,7 +165,12 @@ const mapLoader = useSimpleDataLoader<MapMarker[]>(async () => {
   if (!selectedRegion.value)
     return [];
   await waitTimeOut(200);
-  const res = (await LightVillageApi.getVillageList(undefined, selectedRegion.value, undefined, 1, 1000)).list;
+  const res = (await LightVillageApi.getVillageList({
+    region: selectedRegion.value,
+    keyword: searchKeyword.value.trim() || undefined,
+    page: 1,
+    pageSize: 1000
+  })).list;
   hasResItems.value = res.length > 0;
   const list = res.map((p, i) => {
     villageData.set(p.id, p);
@@ -193,7 +229,7 @@ const mapLoader = useSimpleDataLoader<MapMarker[]>(async () => {
 
   if (nextNeedAutoFocus.value) {
     
-    if (res.length > 20) {
+    if (res.length > 0) {
       setTimeout(() => {
         mapCtx.includePoints({
           points: list.map(p => {
@@ -263,6 +299,19 @@ function setCurrentRegion(regionName: string) {
   mapLoader.reload();
 }
 
+function showSearch() {
+  searchDialogShow.value = true;
+  searchKeyword.value = '';
+}
+async function searchConfirm() {
+  searchDialogShow.value = false;
+  if (searchKeyword.value.trim() === '') {
+    toast('请输入搜索关键词');
+    return;
+  }
+  await mapLoader.reload();
+}
+
 watch(() => props.city, async (newVal) => {
   await regionLoader.reload();
   setCurrentRegion(newVal || '');

+ 24 - 9
src/pages/home/index.vue

@@ -1,7 +1,7 @@
 <template>
   <FlexCol>
     <FlexCol :gap="20" :padding="[30,30,0,30]" :innerStyle="{
-      marginTop: '-130px',
+      marginTop: '-80px',
       backgroundImage: `url(${appConfiguration?.banners.homeTop})`,
       backgroundSize: '100% auto',
       backgroundRepeat: 'no-repeat',
@@ -28,14 +28,19 @@
       <Height height="150px" />
       <FlexCol :gap="20" align="center">
         <FlexRow justify="space-between" align="center" width="100%">
-          <SearchBar v-model="searchKeywords" placeholder="搜索" :innerStyle="{
-            backgroundColor: 'white',
-            borderRadius: '20rpx',
-            borderWidth: '1px',
-            borderStyle: 'solid',
-            borderColor: themeContext.resolveThemeColor('primary'),
-            width: '610rpx',//490rpx
-          }" />
+          <SearchBar 
+            v-model="searchKeywords" 
+            placeholder="搜索" 
+            :innerStyle="{
+              backgroundColor: 'white',
+              borderRadius: '20rpx',
+              borderWidth: '1px',
+              borderStyle: 'solid',
+              borderColor: themeContext.resolveThemeColor('primary'),
+              width: '630rpx',//490rpx
+            }" 
+            @search="handleSearch"
+          />
           <!-- <Button icon="ai-thinking" @click="handleGoAI" text="问AI" /> -->
         </FlexRow>
         <ImageSwiper 
@@ -377,6 +382,16 @@ async function handleGoPublish() {
 async function handleLightVillage() {
   requireLogin(async () => navTo('/pages/home/light/submit-map', { city: currentCity.value }), '登录后才能点亮村社哦!');
 }
+function handleSearch() {
+  if (!searchKeywords.value) {
+    toast('请输入搜索关键词');
+    return;
+  }
+  navTo('/pages/home/search/index', { 
+    region: currentRegion.value ?? 0,
+    searchValue: searchKeywords.value,
+  });
+}
 
 async function loadInfo() {
 

+ 66 - 0
src/pages/home/search/index.vue

@@ -0,0 +1,66 @@
+<template>
+  <CommonTopBanner title="搜索">
+    <SimplePageListLoader>
+      <FlexCol gap="gap.md">
+        <Empty v-if="listLoader.list.value.length === 0" description="暂无搜索结果,换个关键词试试吧" />
+        <FlexRow 
+          v-for="(item, i) in listLoader.list.value"
+          :key="i"
+          justify="space-between" align="center" gap="gap.md"
+        >
+          <ImageBlock3
+            backgroundColor="transparent"
+            :src="item.image"
+            defaultImage="https://xy.wenlvti.net/app_static/images/village/PlaceholderVillage.jpg"
+            :title="item.name"
+            :desc="item.address"
+            :imageRadius="15"
+            :imageWidth="200"
+            :imageHeight="140"
+            @click="onGoDetails(item as VillageListItem)"
+          />
+          <FlexRow justify="flex-end" align="center" gap="gap.md" width="200rpx">
+            <Tag v-if="item.isFollow" text="已关注" size="small" />
+            <Tag v-if="item.isJoined" text="已加入" size="small" type="primary" />
+          </FlexRow>
+        </FlexRow>
+      </FlexCol>
+    </SimplePageListLoader>
+  </CommonTopBanner>
+</template>
+
+<script setup lang="ts">
+import { useSimplePageListLoader } from '@/components/composeabe/loader/SimplePageListLoader';
+import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
+import FlexCol from '@/components/layout/FlexCol.vue';
+import ImageBlock3 from '@/components/display/block/ImageBlock3.vue';
+import LightVillageApi, { type VillageListItem } from '@/api/light/LightVillageApi';
+import SimplePageListLoader from '@/components/loader/SimplePageListLoader.vue';
+import Empty from '@/components/feedback/Empty.vue';
+import Tag from '@/components/display/Tag.vue';
+import FlexRow from '@/components/layout/FlexRow.vue';
+import CommonTopBanner from '@/common/components/CommonTopBanner.vue';
+import { backAndCallOnPageBack } from '@/components/utils/PageAction';
+
+const { querys } = useLoadQuerys({
+  searchValue: '',
+  region: 0,
+}, () => {
+  listLoader.load();
+});
+
+const listLoader = useSimplePageListLoader(16, async (page, pageSize) => {
+  return await LightVillageApi.getVillageList({
+    keyword: querys.value.searchValue,
+    region: querys.value.region,
+    page,
+    pageSize,
+  });
+}, false);
+
+function onGoDetails(item: VillageListItem) {
+  backAndCallOnPageBack('goVillage', { id: item.id });
+}
+
+
+</script>