瀏覽代碼

📦 建村页面和首页地图优化

快乐的梦鱼 3 周之前
父節點
當前提交
f28760ccec

+ 6 - 1
src/components/basic/Button.vue

@@ -30,6 +30,7 @@
     </slot>
     <slot>
       <Text 
+        v-bind="textProps"
         :color="textColorFinal" 
         :fontSize="selectStyleType(size, 'medium', FonstSizes)" 
         :fontWeight="type === 'text' ? 'bold' : undefined"
@@ -55,7 +56,7 @@ import { propGetThemeVar, useTheme, type ViewStyle } from '../theme/ThemeDefine'
 import { configPadding, DynamicColor, DynamicSize, selectStyleType } from '../theme/ThemeTools';
 import type { IconProps } from './Icon.vue';
 import type { FlexProps } from '../layout/FlexView.vue';
-import Text from './Text.vue';
+import Text, { type TextProps } from './Text.vue';
 import ActivityIndicator from './ActivityIndicator.vue';
 import Icon from './Icon.vue';
 import Touchable from '../feedback/Touchable.vue';
@@ -71,6 +72,10 @@ export interface ButtonProp {
    */
   text?: string,
   /**
+   * 按钮文字的额外属性
+   */
+  textProps?: TextProps,
+  /**
    * 按钮支持 default、primary、success、warning、danger、custom 自定义 六种类型
    * @default 'default'
    */

+ 7 - 5
src/components/form/Cascader.vue

@@ -11,7 +11,6 @@
     <slot name="header" :currentIndex="headerTabCurrent" />
     <SimpleList 
       mode="single-check"
-      virtual
       :data="currentDataDynamicLoading ? [] : currentData"
       :dataDisplayProp="textKey"
       colorProp="color"
@@ -34,12 +33,12 @@
 <script setup lang="ts">
 import { computed, onMounted, ref, watch } from 'vue';
 import { useTheme } from '../theme/ThemeDefine';
+import { DynamicSize } from '../theme/ThemeTools';
+import { getCascaderText, getCascaderItemByValue } from './CascaderUtils';
 import Tabs, { type TabsItemData, type TabsProps } from '../nav/Tabs.vue';
 import SimpleList from '../list/SimpleList.vue';
-import { DynamicSize } from '../theme/ThemeTools';
 import LoadingPage from '../display/loading/LoadingPage.vue';
 import Empty from '../feedback/Empty.vue';
-import { getCascaderText } from './CascaderUtils';
 
 const themeContext = useTheme();
 
@@ -69,6 +68,10 @@ export interface CascaderItem extends Record<string, any> {
    * 子选项
    */
   children?: CascaderItem[];
+  /**
+   * 自定义数据
+   */
+  data?: any;
 }
 
 export interface CascaderProps {
@@ -203,7 +206,6 @@ async function loadAsyncData(group: CascaderItem) {
         group[props.childrenKey] = data;
         currentDataDynamicLoad.value = data;
         currentDataDynamicLoadErrorText.value = '';
-        console.log(group);
       }).catch((e) => {
         console.error('asyncLoadData failed', e);
         currentDataDynamicLoadErrorText.value = '' + e;
@@ -228,7 +230,7 @@ function handleItemClick(item: CascaderItem) {
     (!nextGroup || nextGroup.length === 0)
     && !canLoadLevel(headerTabCurrent.value + 1)
   )
-    emit('pickEnd');
+    emit('pickEnd', getCascaderItemByValue(values, props.valueKey, props.childrenKey, props.data));
 }
 function handleTabChange(v: number) {
   headerTabCurrent.value = v;

+ 23 - 0
src/components/form/CascaderUtils.ts

@@ -23,4 +23,27 @@ export function getCascaderText(
       selectTexts.push(item[textKey]);
   }
   return selectTexts.join(' / ');
+}
+export function getCascaderItemByValue(
+  value: (number|string)[], 
+  valueKey: string, 
+  childrenKey: string,
+  data: CascaderItem[],
+) {
+  const selectItems : CascaderItem[] = [];
+  let currentGroup : CascaderItem[]|undefined = data;
+  let i = 0;
+  for (; i < value.length; i++) {
+    const item : CascaderItem|undefined = currentGroup?.find(item => item[valueKey] === value[i]);
+    if (item) {
+      selectItems.push(item);
+      currentGroup = item[childrenKey];
+    }
+  }
+  if (currentGroup !== undefined) {
+    const item : CascaderItem|undefined = currentGroup?.find(item => item[valueKey] === value[i]);
+    if (item)
+      selectItems.push(item);
+  }
+  return selectItems;
 }

+ 28 - 23
src/components/list/SimpleList.vue

@@ -34,29 +34,34 @@
       <slot name="inner" />
     </template>
   </FixedVirtualList>
-  <FlexCol v-else :innerStyle="innerStyle">
-    <SimpleListItem
-      v-for="(item, index) in data"
-      :key="dataKey ? (item as Record<string, any>)[dataKey] : index"
-      :item="item"
-      :index="index"
-      :dataDisplayProp="dataDisplayProp"
-      :colorProp="colorProp"
-      :disabledProp="disabledProp"
-      :showCheck="mode !== 'select'"
-      :checked="checkedList.indexOf(item) >= 0"
-      :overrideItem="Boolean($slots.itemContent)"
-      @click="onItemPress(item, index)"
-    >
-      <template v-if="$slots.itemContent" #itemContent>
-        <slot name="itemContent" :item="item" :index="index" />
-      </template>
-    </SimpleListItem>
-    <slot v-if="data.length == 0" name="empty">
-      <Empty :description="emptyText" />
-    </slot>
-    <slot name="inner" />
-  </FlexCol>
+  <scroll-view v-else :scroll-y="true" :scroll-with-animation="true" :style="{ 
+    width: innerStyle?.width,
+    height: innerStyle?.height, 
+  }">
+    <FlexCol :innerStyle="innerStyle">
+      <SimpleListItem
+        v-for="(item, index) in data"
+        :key="dataKey ? (item as Record<string, any>)[dataKey] : index"
+        :item="item"
+        :index="index"
+        :dataDisplayProp="dataDisplayProp"
+        :colorProp="colorProp"
+        :disabledProp="disabledProp"
+        :showCheck="mode !== 'select'"
+        :checked="checkedList.indexOf(item) >= 0"
+        :overrideItem="Boolean($slots.itemContent)"
+        @click="onItemPress(item, index)"
+      >
+        <template v-if="$slots.itemContent" #itemContent>
+          <slot name="itemContent" :item="item" :index="index" />
+        </template>
+      </SimpleListItem>
+      <slot v-if="data.length == 0" name="empty">
+        <Empty :description="emptyText" />
+      </slot>
+      <slot name="inner" />
+    </FlexCol>
+  </scroll-view>
 </template>
 
 <script setup lang="ts" generic="T">

+ 7 - 0
src/pages.json

@@ -211,6 +211,13 @@
       }
     },
     {
+      "path": "pages/home/light/create-village",
+      "style": {
+        "navigationBarTitleText": "一键建村",
+        "navigationStyle": "custom"
+      }
+    },
+    {
       "path": "pages/article/details",
       "style": {
         "navigationBarTitleText": "新闻详情"

+ 13 - 17
src/pages/home/components/CitySelect.vue

@@ -27,34 +27,30 @@ import Cascader from '@/components/form/Cascader.vue';
 
 const selectedValue = ref<(string | number)[]>([]);
 const provinceList = ref<CascaderItem[]>([]);
-const regionMap = new Map<string | number, RegionItem>();
-
-function regionToCascaderItem(region: RegionItem): CascaderItem {
-  const item: CascaderItem = {
-    text: region.name,
-    value: String(region.areaCode),
-  };
-  regionMap.set(item.value, region);
-  return item;
-}
 
 async function asyncLoadData(item: CascaderItem) {
   const list = await RegionApi.getChildList(Number(item.value));
-  return list.map(regionToCascaderItem);
+  return list.map(region => ({
+    text: region.name,
+    value: String(region.areaCode),
+    data: region,
+  }));
 }
 
 onMounted(async () => {
   const list = await RegionApi.getChildList(0);
-  provinceList.value = list.map(regionToCascaderItem);
+  provinceList.value = list.map(region => ({
+    text: region.name,
+    value: String(region.areaCode),
+    data: region,
+  }));
 });
 
 const emit = defineEmits(['selectCity']);
 
-function handlePickEnd() {
-  const lastValue = selectedValue.value[selectedValue.value.length - 1];
-  const region = regionMap.get(String(lastValue));
-  if (region) {
-    emit('selectCity', region);
+function handlePickEnd(values: CascaderItem[]) {
+  if (values.length > 0) {
+    emit('selectCity', values[values.length - 1].data);
   }
 }
 </script>

+ 14 - 3
src/pages/home/components/LightMap.vue

@@ -48,6 +48,7 @@
       <FlexRow align="center" justify="space-between">
         <NButton
           :icon="searchKeyword ? 'close' : 'search'" 
+          iconMargin="0"
           @click="showSearch" 
         />
         <FlexRow gap="gap.md" align="center">
@@ -57,6 +58,10 @@
             type="custom"
             :radius="30"
             :padding="[30,10]"
+            :textProps="{
+              maxWidth: 150,
+              lines: 1
+            }"
             @click="emit('changeCity')" 
           />
           <SimpleDropDownPicker
@@ -67,8 +72,8 @@
           />
         </FlexRow>
         <NButton
-          text="定位"
           icon="navigation" 
+          iconMargin="0"
           @click="emit('getCurrentLonlat')" 
         />
       </FlexRow>
@@ -314,12 +319,14 @@ function loadFirstRegion() {
   let code : number | undefined = undefined;
   if (props.district)
     code = regionLoader.content.value?.find((p) => p.name === props.district)?.areaCode;
-  else
+  if (!code)
     code = regionLoader.content.value?.find((p) => p.id !== props.cityCode)?.areaCode;
+  if (!code)
+    code = regionLoader.content.value?.[0]?.areaCode;
 
   if (code)
     setCurrentRegion(code);
-  console.log('loadFirstRegion', code, props.district);
+  console.log('loadFirstRegion', code, regionLoader.content.value, props.district);
 }
 
 function showSearch() {
@@ -385,6 +392,10 @@ onMounted(async () => {
     });
   });
 });
+
+defineExpose({
+  showSearch,
+});
 </script>
 
 <style lang="scss">

+ 1 - 1
src/pages/home/index.vue

@@ -64,7 +64,7 @@
         :lonlat="currentLocation.currentLonlat.value"
         @getCurrentLonlat="currentLocation.getCurrentExactLocation"
         @selectVillage="handleGoVillageDetails"
-        @changeCity="handleSelectCity"
+        @changeCity="showCityPopup=true"
       >
         <NoticeBar 
           v-if="currentNoticeContent"

+ 121 - 0
src/pages/home/light/create-village.vue

@@ -0,0 +1,121 @@
+<template>
+  <CommonTopBanner title="创建村庄">
+    <FlexCol padding="padding.md" gap="gap.md">
+      <template v-if="step === 'selectArea'">
+        <FlexRow center>
+          <Text>请选择您的家乡位置</Text>
+        </FlexRow>
+        <Cascader
+          v-model="selectedValue"
+          :data="provinceList"
+          textKey="text"
+          valueKey="value"
+          :asyncLoadData="asyncLoadData"
+          :maxSelectLevel="5"
+          @pickEnd="handlePickEnd"
+        />
+      </template>
+      <template v-else-if="step === 'inputInfo'">
+        <FlexRow center gap="gap.md">
+          <Text>请输入家乡名称,然后选择地图位置定位哦</Text>
+        </FlexRow>
+        <Field v-model="currentVillageName" placeholder="请输入家乡名称,例如:大庆村" />
+        <map
+          id="prevMap"
+          map-id="prevMap"
+          :longitude="currentLonLat.longitude"
+          :latitude="currentLonLat.latitude"
+          :zoom="15"
+          :enable-satellite="enableSatellite"
+          :style="{
+            width: '100%',
+            height: '500px',
+          }"
+          @regionchange="handleRegionchange"
+        >
+        </map>
+        <FlexRow justify="flex-end">
+          <Button :text="'切换' + (enableSatellite ? '地图' : '卫星')" @click="enableSatellite = !enableSatellite" />
+        </FlexRow>
+        <FlexCol gap="gap.md">
+          <Button text="返回上一步" @click="step = 'selectArea'" />
+          <Button text="立即建村" type="primary" @click="handleConfirm" />
+        </FlexCol>
+      </template>
+    </FlexCol>
+  </CommonTopBanner>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue';
+import FlexCol from '@/components/layout/FlexCol.vue';
+import FlexRow from '@/components/layout/FlexRow.vue';
+import Cascader, { type CascaderItem } from '@/components/form/Cascader.vue';
+import CommonTopBanner from '@/common/components/CommonTopBanner.vue';
+import RegionApi, { type RegionItem } from '@/api/map/RegionApi';
+import Text from '@/components/basic/Text.vue';
+import Button from '@/components/basic/Button.vue';
+import Field from '@/components/form/Field.vue';
+import { showError } from '@/common/composeabe/ErrorDisplay';
+import { toast } from '@/components/dialog/CommonRoot';
+
+const mapCtx = uni.createMapContext('prevMap');
+
+const step = ref<'selectArea'|'inputInfo'>('selectArea');
+const selectedRegion = ref<RegionItem>();
+const currentLonLat = ref({ longitude: 0, latitude: 0 });
+const currentVillageName = ref('')
+
+const enableSatellite = ref(false);
+const selectedValue = ref<(string | number)[]>([]);
+const provinceList = ref<CascaderItem[]>([]);
+
+async function asyncLoadData(item: CascaderItem) {
+  const list = await RegionApi.getChildList(Number(item.value));
+  return list.map(region => ({
+    text: region.name,
+    value: String(region.areaCode),
+    data: region,
+  }));
+}
+function handlePickEnd(values: CascaderItem[]) {
+  if (values.length > 0) {
+    selectedRegion.value = values[values.length - 1].data;
+    currentLonLat.value = {
+      longitude: Number(selectedRegion.value!.longitude),
+      latitude: Number(selectedRegion.value!.latitude),
+    };
+    mapCtx.moveToLocation({
+      longitude: Number(currentLonLat.value.longitude),
+      latitude: Number(currentLonLat.value.latitude),
+    });
+    console.log('currentLonLat.value', currentLonLat.value);
+    step.value = 'inputInfo';
+  }
+}
+function handleRegionchange(e: any) {
+  if (e.type === 'end') {
+    currentLonLat.value = {
+      longitude: Number(e.detail.centerLocation.longitude),
+      latitude: Number(e.detail.centerLocation.latitude),
+    };
+  }
+}
+function handleConfirm() {
+  if (!currentVillageName.value) {
+    toast('请输入家乡名称');
+    return;
+  }
+  showError('暂无接口');
+}
+
+onMounted(async () => {
+  const list = await RegionApi.getChildList(0);
+  provinceList.value = list.map(region => ({
+    text: region.name,
+    value: String(region.areaCode),
+    data: region,
+  }));
+});
+
+</script>

+ 13 - 6
src/pages/home/light/submit-map.vue

@@ -2,7 +2,7 @@
 import { ref } from 'vue';
 import { useLoadQuerys } from '@/components/composeabe/LoadQuerys';
 import { useGetCurrentLocation } from '../composeabe/GetCurrentLocation';
-import { backAndCallOnPageBack, callPrevOnPageBack } from '@/components/utils/PageAction';
+import { backAndCallOnPageBack, callPrevOnPageBack, navTo } from '@/components/utils/PageAction';
 import AppCofig from '@/common/config/AppCofig';
 import LightMap from '../components/LightMap.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
@@ -18,6 +18,7 @@ import Icon from '@/components/basic/Icon.vue';
 import CitySelect from '../components/CitySelect.vue';
 import Popup from '@/components/dialog/Popup.vue';
 import type { RegionItem } from '@/api/map/RegionApi';
+import Button from '@/components/basic/Button.vue';
 
 const { querys } = useLoadQuerys({
   latitude: AppCofig.defaultLonLat[1],
@@ -51,6 +52,8 @@ const showCityPopup = ref(false);
 const currentCity = ref('');
 const currentCityCode = ref(0);
 
+const lightMap = ref<InstanceType<typeof LightMap>>();
+
 function handleSelectCity(city: RegionItem) {
   currentLocation.setCurrentLocationWithCity(city.name, '');
   currentCity.value = city.name;
@@ -74,7 +77,7 @@ function handleSelectCity(city: RegionItem) {
     <FlexRow center>
       <BoxMid 
         :innerStyle="{
-          width: '500rpx',
+          width: '670rpx',
         }"
         direction="column"
         align="center"
@@ -84,10 +87,13 @@ function handleSelectCity(city: RegionItem) {
           <Image src="https://xy.wenlvti.net/app_static/images/home/BadgeNew.png" :width="220" mode="widthFix" />
           <Text text="请选择您要点亮的村社" fontConfig="primaryTitle" />
         </FlexCol>
-        <Touchable direction="row" align="center" gap="gap.md">
-          <Icon icon="home-filling" size="40" />
-          <Text text="没有我的家乡?点击这里一键建村!" fontConfig="contentText" />
-        </Touchable>
+        <FlexRow gap="gap.md">
+          <Button type="text" icon="search" text="点击搜索" @click="lightMap?.showSearch()" />
+          <Touchable direction="row" align="center" gap="gap.md" @click="navTo('/pages/home/light/create-village')">
+            <Icon icon="home-filling" size="40" />
+            <Text text="没有我的家乡?点击这里一键建村!" fontConfig="contentText" />
+          </Touchable>
+        </FlexRow>
       </BoxMid>
     </FlexRow>
   </FlexCol>
@@ -97,6 +103,7 @@ function handleSelectCity(city: RegionItem) {
     @finish="handleJoinFinish"
   />
   <LightMap 
+    ref="lightMap"
     full
     :city="currentCity"
     :cityCode="currentCityCode"