浏览代码

📦 亮乡源首页地图构建

快乐的梦鱼 12 小时之前
父节点
当前提交
76c21646e3

+ 10 - 0
src/api/inheritor/ActivityContent.ts

@@ -0,0 +1,10 @@
+import { CommonContentApi } from '../CommonContent';
+
+export class ActivityContentApi extends CommonContentApi {
+
+  constructor() {
+    super(undefined, 16, "传承活动");
+  }
+}
+
+export default new ActivityContentApi();

+ 10 - 0
src/api/inheritor/ArtifactProtectContent.ts

@@ -0,0 +1,10 @@
+import { CommonContentApi } from '../CommonContent';
+
+export class ArtifactProtectContentApi extends CommonContentApi {
+
+  constructor() {
+    super(undefined, 1, "文物保护", 1);
+  }
+}
+
+export default new ArtifactProtectContentApi();

+ 10 - 0
src/api/inheritor/InheritorContent.ts

@@ -0,0 +1,10 @@
+import { CommonContentApi } from '../CommonContent';
+
+export class InheritorContentApi extends CommonContentApi {
+
+  constructor() {
+    super(undefined, 7, "非遗保护名录-非遗传承人", 38);
+  }
+}
+
+export default new InheritorContentApi();

+ 19 - 0
src/api/inheritor/MoveableContent.ts

@@ -0,0 +1,19 @@
+import type { DataModel, NewDataModel } from '@imengyu/js-request-transform';
+import { CommonContentApi, GetColumContentList, GetColumListParams, GetContentListItem, GetContentListParams } from '../CommonContent';
+import type { QueryParams } from '@imengyu/imengyu-utils';
+
+export class MoveableContentApi extends CommonContentApi {
+
+  constructor() {
+    super(undefined, 1, "文物保护-可移动文物");
+  }
+
+  getContentList<T extends DataModel = GetContentListItem>(params: GetContentListParams, page: number, pageSize?: number, modelClassCreator?: NewDataModel, querys?: QueryParams): Promise<{ list: T[]; total: number; }> {
+    return super.getContentList(params, page, pageSize, modelClassCreator, { 
+      ...querys,
+      movable: 1
+    });
+  }
+}
+
+export default new MoveableContentApi();

+ 10 - 0
src/api/inheritor/ProductsContent.ts

@@ -0,0 +1,10 @@
+import { CommonContentApi } from '../CommonContent';
+
+export class ProductsContentApi extends CommonContentApi {
+
+  constructor() {
+    super(undefined, 16, "非遗保护名录-非遗产品", [295,317]);
+  }
+}
+
+export default new ProductsContentApi();

+ 10 - 0
src/api/inheritor/ProjectsContent.ts

@@ -0,0 +1,10 @@
+import { CommonContentApi } from '../CommonContent';
+
+export class ProjectsContentApi extends CommonContentApi {
+
+  constructor() {
+    super(undefined, 2, "非遗保护名录-非遗代表性项目");
+  }
+}
+
+export default new ProjectsContentApi();

+ 10 - 0
src/api/inheritor/ProtectListContent.ts

@@ -0,0 +1,10 @@
+import { CommonContentApi } from '../CommonContent';
+
+export class ProtectListContentApi extends CommonContentApi {
+
+  constructor() {
+    super(undefined, 2, "非遗保护名录");
+  }
+}
+
+export default new ProtectListContentApi();

+ 10 - 0
src/api/inheritor/SeminarContent.ts

@@ -0,0 +1,10 @@
+import { CommonContentApi } from '../CommonContent';
+
+export class SeminarContentApi extends CommonContentApi {
+
+  constructor() {
+    super(undefined, 17, "非遗保护名录-非遗传习中心(所)", 221);
+  }
+}
+
+export default new SeminarContentApi();

+ 10 - 0
src/api/inheritor/UnitContent.ts

@@ -0,0 +1,10 @@
+import { CommonContentApi } from '../CommonContent';
+
+export class UnitContentApi extends CommonContentApi {
+
+  constructor() {
+    super(undefined, 17, "非遗保护名录-非遗保护单位", 259);
+  }
+}
+
+export default new UnitContentApi();

+ 20 - 0
src/api/inheritor/UnmoveableContent.ts

@@ -0,0 +1,20 @@
+import type { DataModel, NewDataModel } from '@imengyu/js-request-transform';
+import { CommonContentApi, GetColumContentList, GetColumListParams, GetContentListItem, GetContentListParams } from '../CommonContent';
+import type { QueryParams } from '@imengyu/imengyu-utils';
+
+export class UnmoveableContentApi extends CommonContentApi {
+
+  constructor() {
+    super(undefined, 1, "文物保护-不可移动文物");
+  }
+
+
+  getContentList<T extends DataModel = GetContentListItem>(params: GetContentListParams, page: number, pageSize?: number, modelClassCreator?: NewDataModel, querys?: QueryParams): Promise<{ list: T[]; total: number; }> {
+    return super.getContentList(params, page, pageSize, modelClassCreator, { 
+      ...querys,
+      movable: 0
+    });
+  }
+}
+
+export default new UnmoveableContentApi();

+ 21 - 1
src/api/light/LightVillageApi.ts

@@ -32,7 +32,6 @@ export class VillageListItem extends DataModel<VillageListItem> {
       }
       this.thumbnail = this.image;
       this.title = this.villageName
-      this.isLight = Math.random() > 0.5;
     }
 
   }
@@ -80,6 +79,27 @@ export class LightVillageApi extends AppServerRequestModule<DataModel> {
     });
     return transformArrayDataModel<VillageListItem>(VillageListItem, transformSomeToArray(res.requireData().data), `村落`, true);
   }
+
+  async getIpAddress() {
+    const res = await this.get<{
+      address: string,
+      address_detail:{
+        adcode: string,
+        city: string,
+        city_code: number,
+        district: string,
+        province: string,
+        street: string,
+        street_number: string,
+      },
+      point: {
+        x: string,
+        y: string,
+      },
+    }>('/village/volunteer/getIpArea', '获取IP地址');
+    return res.requireData();
+  }
 }
 
+
 export default new LightVillageApi();

+ 14 - 0
src/pages.json

@@ -39,6 +39,20 @@
       }
     },
     {
+      "path": "pages/home/light/details",
+      "style": {
+        "navigationBarTitleText": "村落详情",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/home/light/submit",
+      "style": {
+        "navigationBarTitleText": "点亮村落",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
       "path": "pages/dig/details",
       "style": {
         "navigationBarTitleText": "村社文化资源挖掘平台-详情",

+ 16 - 0
src/pages/article/common/CommonContent.ts

@@ -8,6 +8,22 @@ export interface IHomePageMiniCommonListGoMoreAndGoDetail {
   goList: () => void; 
 }
 
+export function goCommonContentList(p: {
+  title?: string,
+  mainBodyId?: number,
+  mainBodyColumnId?: number|number[],
+  modelId?: number,
+}) {
+  navTo('/pages/article/common/list', {
+    title: p.title,
+    mainBodyId: p.mainBodyId,
+    mainBodyColumnId: typeof p.mainBodyColumnId == 'object' ? 
+      p.mainBodyColumnId.join(',') : 
+      p.mainBodyColumnId,
+    modelId: p.modelId,
+  }) 
+} 
+
 /**
  * 专用于通用内容的首页小列表控制代码组合
  * @param p 

+ 185 - 65
src/pages/components/LightMap.vue

@@ -4,48 +4,80 @@
       id="prevMap"
       map-id="prevMap"
       class="light-map-map"
-      :markers="mapLoader.content.value || []"
-      :polygons="polygon"
-      :polyline="polyline"
       :enable-poi="false"
-      :scale="10"
+      :scale="12"
       :longitude="AppCofig.defaultLonLat[0]"
       :latitude="AppCofig.defaultLonLat[1]"
+      @markertap="onMarkerTap"
+      
     />
     <SimpleDropDownPicker 
       class="light-map-region-picker" 
-      v-model:modelValue="selectedRegion" 
+      :modelValue="selectedRegion" 
+      @update:modelValue="onSelectedRegion"
       :columns="regionLoader.content.value" 
     />
+    <Button 
+      :innerStyle="{
+        position: 'absolute',
+        bottom: '20rpx',
+        right: '20rpx',
+        zIndex: 100,
+        backgroundColor: '#ffffff',
+      }" 
+      icon="navigation" 
+      size="large"
+      @click="getCurrentLonlat" 
+    />
   </div>
 </template>
 
 <script setup lang="ts">
-import { getCurrentInstance, ref, watch } from 'vue';
+import { getCurrentInstance, onMounted, ref, watch } from 'vue';
 import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
 import { geoJsonToWechatMapShapes } from '@/common/utils/geoJsonToWechatMap';
-import LightVillageApi from '@/api/light/LightVillageApi';
+import LightVillageApi, { VillageListItem } from '@/api/light/LightVillageApi';
 import MapApi from '@/api/map/MapApi';
 import AppCofig from '@/common/config/AppCofig';
 import type { MapMarker, MapPolygon, MapPolyline } from '@/types/Map';
 import SimpleDropDownPicker from '@/common/components/SimpleDropDownPicker.vue';
+import Button from '@/components/basic/Button.vue';
+import { navTo } from '@/components/utils/PageAction';
+import CommonContent from '@/api/CommonContent';
+import { waitTimeOut } from '@imengyu/imengyu-utils';
 
 const instance = getCurrentInstance();
 const mapCtx = uni.createMapContext('prevMap', instance);
 const selectedRegion = ref<number>();
+const nextNeedAutoFocus = ref(false);
+const currentAddress = ref<string>('');
+const currentLonlat = ref<{ longitude: number, latitude: number }>({ 
+  longitude: AppCofig.defaultLonLat[0], 
+  latitude: AppCofig.defaultLonLat[1],
+});
+const villageData = new Map<number, VillageListItem>();
+const emit = defineEmits(['getedCurrentLonlat','update:isLightMode']);
+
+const props = defineProps<{
+  isLightMode: boolean;
+}>();
 
 const regionLoader = useSimpleDataLoader(async () => {
-  return [
-    {
-      id: undefined as any,
-      name: '厦门市 · 湖里区',
-    },
-  ]
-});
+  return (await CommonContent.getCategoryChildList(5)).map(p => ({
+    id: p.id,
+    name: p.title,
+  }));
+}, false);
 const mapLoader = useSimpleDataLoader<MapMarker[]>(async () => {
-  const res = (await LightVillageApi.getVillageList(undefined, selectedRegion.value, undefined, 1, 12)).map((p, i) => {
+  mapCtx.removeMarkers({
+    markerIds: Array.from(villageData.keys()),
+  })
+  villageData.clear();
+
+  await waitTimeOut(200);
+  const res = (await LightVillageApi.getVillageList(undefined, selectedRegion.value, undefined, 1, 100)).map((p, i) => {
+    villageData.set(p.id, p);
     const maker : MapMarker = {
-      ...p,
       id: p.id ?? i,
       title: p.villageName,
       longitude: Number(p.longitude),
@@ -53,10 +85,13 @@ const mapLoader = useSimpleDataLoader<MapMarker[]>(async () => {
       width: 30,
       height: 30,
       iconPath: '',
+      joinCluster: true,
       callout: {
+        display: 'ALWAYS',
         content: p.villageName,
         color: '#000000',
         fontSize: 12,
+        padding: 5,
         bgColor: '#ffffff',
         borderRadius: 5,
       },
@@ -64,71 +99,149 @@ const mapLoader = useSimpleDataLoader<MapMarker[]>(async () => {
 
     if (p.isLight) {
       if (p.lightValue >= 1) {
-        maker.iconPath = `https://mncdn.wenlvti.net/app_static/xiangyuan/images/map/LightSun.png`;
+        maker.iconPath = `https://mncdn.wenlvti.net/app_static/xiangyuan/images/map/Light3.png`;
       } else if (p.lightValue >= 0.5) {
-        maker.iconPath = `https://mncdn.wenlvti.net/app_static/xiangyuan/images/map/LightMoon.png`;
+        maker.iconPath = `https://mncdn.wenlvti.net/app_static/xiangyuan/images/map/Light2.png`;
       } else if (p.lightValue >= 0.25) {
-        maker.iconPath = `https://mncdn.wenlvti.net/app_static/xiangyuan/images/map/LightStar.png`;
+        maker.iconPath = `https://mncdn.wenlvti.net/app_static/xiangyuan/images/map/Light1.png`;
       } else {
-        maker.iconPath = `https://mncdn.wenlvti.net/app_static/xiangyuan/images/map/LightStar.png`;
+        maker.iconPath = `https://mncdn.wenlvti.net/app_static/xiangyuan/images/map/Light1.png`;
       }
-      const size = (p.lightValue + 0.5) * 36;
+      const size = Math.floor(25 +(p.lightValue) * 10);
       maker.width = size;
       maker.height = size;
+      
     } else {
-      maker.width = 15;
-      maker.height = 15;
-      maker.iconPath = `https://mncdn.wenlvti.net/app_static/xiangyuan/images/map/StarNotLight.png`;
+      maker.width = 25;
+      maker.height = 25;
+      maker.iconPath = `https://mncdn.wenlvti.net/app_static/xiangyuan/images/map/LightUnLight.png`;
     }
 
     return maker as MapMarker;
   });
 
-  /* const countryGeoJsonData = await MapApi.loadCountryGeoJsonData();
-  const { polygons, polylines: geoPolylines } = geoJsonToWechatMapShapes(countryGeoJsonData, {
-    fillColor: '#344A41',
-    strokeColor: '#4AB58A',
-    strokeWidth: 2,
-    level: 'abovebuildings',
-  });
-  polygon.value.push(...polygons);
-  polyline.value = geoPolylines; */
-
-  setTimeout(() => {
-    mapCtx.includePoints({
-      points: res.map(p => {
-        if (!p.longitude || !p.latitude) {
-          p.longitude = AppCofig.defaultLonLat[0];
-          p.latitude = AppCofig.defaultLonLat[1];
-        }
-        return {
-          latitude: p.latitude,
-          longitude: p.longitude,
-        }
-      }),
-      padding: [20, 20, 20, 20],
-    });
-  }, 200);
+  mapCtx.addMarkers({
+    clear: true,
+    markers: res, 
+  })
+
+  if (nextNeedAutoFocus.value) {
+    setTimeout(() => {
+      mapCtx.includePoints({
+        points: res.map(p => {
+          if (!p.longitude || !p.latitude) {
+            p.longitude = AppCofig.defaultLonLat[0];
+            p.latitude = AppCofig.defaultLonLat[1];
+          }
+          return {
+            latitude: p.latitude,
+            longitude: p.longitude,
+          }
+        }),
+        padding: [20, 20, 20, 20],
+      });
+    }, 200);
+  }
 
   return res;
-});
+}, false, false, true);
+
+function onMarkerTap(e: any) {
+  if (props.isLightMode) {
+    emit('update:isLightMode', false);
+    navTo('/pages/home/light/submit', {
+      villageId: e.markerId,
+    });
+    return;
+  }
+  const village = villageData.get(e.markerId);
+  if (village) {
+    uni.setStorageSync('VillageTemp', JSON.stringify(village));
+    setTimeout(() => {
+      navTo('/pages/home/light/details', { id: village.id });
+    }, 200);
+  }
+}
+function onSelectedRegion(regionId: number) {
+  selectedRegion.value = regionId;
+  nextNeedAutoFocus.value = true;
+  mapLoader.loadData(undefined, true);
+}
 
-const polygon = ref<MapPolygon[]>([
-  /* {
-    fillColor: '#000',
-    level: 'abovebuildings',
-    points: [
-      { latitude: -89, longitude: 0 },
-      { latitude: 89, longitude: 0 },
-      { latitude: 89, longitude: 179.999 },
-      { latitude: -89, longitude: 179.999 },
-    ]
-  } */
-]);
-const polyline = ref<MapPolyline[]>([]);
-
-watch(selectedRegion, async () => {
+function setCurrentRegion(regionName: string) {
+  selectedRegion.value = regionLoader.content.value?.find(p => p.name == regionName)?.id || undefined;
   mapLoader.loadData(undefined, true);
+}
+
+function getCurrentLonlat() {
+  uni.getLocation({
+    type: 'wgs84',
+    success: async (res) => {
+      currentLonlat.value = {
+        longitude: res.longitude,
+        latitude: res.latitude,
+      };
+      const address = await MapApi.regeo(res.latitude, res.longitude);
+      currentAddress.value = address.district;
+      setCurrentRegion(address.district);
+      emit('getedCurrentLonlat', currentLonlat.value);
+      mapCtx.moveToLocation({
+        latitude: currentLonlat.value.latitude,
+        longitude: currentLonlat.value.longitude,
+      });
+    },
+  });
+}
+
+onMounted(async () => {
+  mapCtx.initMarkerCluster({
+    enableDefaultStyle: false,
+    zoomOnClick: true,
+    gridSize: 40,
+  });
+  mapCtx.on('markerClusterCreate', (e: { clusters: any[] }) => {
+    const customClusters = e.clusters.map((cluster) => {
+      const { center, clusterId, markerIds } = cluster;
+      return {
+        ...center,
+        width: 0,
+        height: 0,
+        clusterId,
+        label: {
+          content: markerIds.length.toString(), // 聚合点的数量
+          fontSize: 16,
+          color: '#fff',
+          width: 30,
+          height: 30,
+          bgColor: '#8bb346', // 背景颜色
+          borderRadius: 25,
+          textAlign: 'center',
+          anchorX: -10,
+          anchorY: -35,
+        },
+      };
+    });
+    mapCtx.addMarkers({
+      markers: customClusters,
+      clear: false,
+    });
+  });
+
+  await regionLoader.loadData(undefined, true);
+
+  const res = await LightVillageApi.getIpAddress();
+  currentLonlat.value = {
+    longitude: Number(res.point.x),
+    latitude: Number(res.point.y),
+  };
+  await waitTimeOut(100);
+  currentAddress.value = res.address_detail.district;
+  setCurrentRegion(res.address_detail.district);
+  emit('getedCurrentLonlat', currentLonlat.value);
+  mapCtx.moveToLocation({
+    latitude: currentLonlat.value.latitude,
+    longitude: currentLonlat.value.longitude,
+  });
 });
 
 </script>
@@ -153,5 +266,12 @@ watch(selectedRegion, async () => {
     transform: translateX(-50%);
     z-index: 100;
   }
+
+  .light-map-address {
+    position: absolute;
+    bottom: 20rpx;
+    right: 20rpx;
+    z-index: 100;
+  }
 }
 </style>

+ 24 - 0
src/pages/dig/admin/data/volunteer.ts

@@ -110,6 +110,30 @@ export function getVolunteerForm(options: {
               upload: useAliOssUploadCo('xiangyuan/volunteer/images')
             } as UploaderFieldProps,
           },
+          {
+            label: '类型', name: 'type', type: 'radio-value',
+            additionalProps: {
+              options: [
+                { text: '志愿者', value: 'volunteer' },
+                { text: '社区工作者', value: 'staff' }
+              ]
+            } as RadioValueProps,
+            rules: [{ required: true, message: '请选择类型' }],
+          },
+          { 
+            label: '工作单位', name: 'unit', type: 'text', additionalProps: { placeholder: '请输入手机号' } ,
+            rules: [{ required: true, message: '请输入手机号' }],
+            show: { callback: () => options.formRef.value?.getValueByPath('type') === 'staff' },
+          },
+          { 
+            label: '职位', name: 'job', type: 'text', additionalProps: { placeholder: '请输入手机号' } ,
+            rules: [{ required: true, message: '请输入手机号' }],
+            show: { callback: () => options.formRef.value?.getValueByPath('type') === 'staff' },
+          },
+          { 
+            label: '手机号', name: 'mobile', type: 'text', additionalProps: { placeholder: '请输入手机号' } ,
+            rules: [{ required: true, message: '请输入手机号' }],
+          },
           { label: '现居地址', name: 'address', type: 'text', additionalProps: { placeholder: '请输入现居地址' } },
           { 
             label: '个人介绍', 

+ 64 - 6
src/pages/home/index.vue

@@ -1,7 +1,7 @@
 <template>
   <FlexCol :gap="20" :padding="30" :innerStyle="{
     marginTop: '-100px',
-    backgroundImage: 'url(https://mncdn.wenlvti.net/app_static/xiangyuan/images/home/HomeBackground.png)',
+    backgroundImage: 'url(https://mn.wenlvti.net/app_static/xiangyuan/images/home/HomeBackground.jpg)',
     backgroundSize: '100% auto',
     backgroundRepeat: 'no-repeat',
     backgroundPosition: 'top center',
@@ -13,14 +13,45 @@
       <Image src="https://mncdn.wenlvti.net/app_static/xiangyuan/images/home/ButtonSubTitle.png" width="290rpx" mode="widthFix" />
     </FlexCol>
     <Box icon="https://mncdn.wenlvti.net/app_static/xiangyuan/images/home/icon-pin-distance.png" :padding="false">
-      <LightMap />
+      <LightMap 
+        v-model:isLightMode="isLightMode"
+        @getedCurrentLonlat="getedCurrentLonlat"
+      />
       <FlexRow justify="space-between" :padding="10">
-        <ImageButton src="https://mncdn.wenlvti.net/app_static/xiangyuan/images/home/ButtonMainAction.png" :width="220" :height="145" />
-        <ImageButton src="https://mncdn.wenlvti.net/app_static/xiangyuan/images/home/ButtonMainLight.png" :width="220" :height="145" />
-        <ImageButton src="https://mncdn.wenlvti.net/app_static/xiangyuan/images/home/ButtonMainSupport.png" :width="220" :height="145" />
+        <ImageButton src="https://mn.wenlvti.net/app_static/xiangyuan/images/home/ButtonMainAction.png" :width="230" :height="155" @click="goCommonContentList({ modelId: 18, mainBodyColumnId: 361 })" />
+        <ImageButton src="https://mn.wenlvti.net/app_static/xiangyuan/images/home/ButtonMainLight.png" :innerStyle="{ marginLeft: '-10rpx' }" :width="230" :height="155" @click="handleLight()" />
+        <ImageButton src="https://mn.wenlvti.net/app_static/xiangyuan/images/home/ButtonMainSupport.png" :innerStyle="{ marginLeft: '-10rpx' }" :width="230" :height="155" @click="goCommonContentList({ modelId: 18, mainBodyColumnId: 362 })" />
       </FlexRow>
     </Box>
 
+    <Box title="发现 · 周边" icon="https://mncdn.wenlvti.net/app_static/xiangyuan/images/home/icon-compass.png" showMore @moreClicked="$emit('goDiscover')">  
+      <SimplePageContentLoader :loader="recommendedNearbySitesLoader">
+        <FlexCol :gap="25">
+          <Touchable 
+            v-for="(item, i) in recommendedNearbySitesLoader.content.value"
+            :key="i"
+            justify="space-between"
+            align="center"
+            direction="row"
+            @click="goDiscoverDetails(item)"
+          > 
+            <FlexCol flex="1" :gap="20">
+              <Text :text="item.title" fontConfig="h5" />
+              <Text :text="item.desc" fontConfig="subText" />
+            </FlexCol>
+            <Width :width="25" />
+            <Image 
+              :src="item.image" 
+              :failedImage="AppCofig.defaultImage"
+              :width="170" 
+              :height="120" 
+              :radius="15" 
+              mode="aspectFill"
+            />
+          </Touchable>
+        </FlexCol>
+      </SimplePageContentLoader>
+    </Box>
     <Box title="线上史馆展示" icon="https://mncdn.wenlvti.net/app_static/xiangyuan/images/home/icon-ancient-gate.png">
       <SimplePageContentLoader :loader="recommendLoader">
         <FlexRow justify="space-between" align="center" wrap :gap="25">
@@ -45,7 +76,7 @@
         </FlexRow>
       </SimplePageContentLoader>
     </Box>
-    <Box title="发现 · 周边" icon="https://mncdn.wenlvti.net/app_static/xiangyuan/images/home/icon-compass.png" showMore @moreClicked="$emit('goDiscover')">  
+    <Box title="精彩推荐" icon="https://mncdn.wenlvti.net/app_static/xiangyuan/images/home/icon-compass.png" showMore @moreClicked="$emit('goDiscover')">  
       <SimplePageContentLoader :loader="discoverLoader">
         <FlexCol :gap="25">
           <Touchable 
@@ -81,6 +112,7 @@
 <script setup lang="ts">
 import { navTo } from '@/components/utils/PageAction';
 import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
+import { ref } from 'vue';
 import VillageApi from '@/api/inhert/VillageApi';
 import VillageInfoApi from '@/api/inhert/VillageInfoApi';
 import Box from '@/common/components/parts/Box.vue';
@@ -99,6 +131,23 @@ import Grid from '@/components/layout/grid/Grid.vue';
 import GridItem from '@/components/layout/grid/GridItem.vue';
 import LightMap from '../components/LightMap.vue';
 import ImageButton from '@/components/basic/ImageButton.vue';
+import CommonContent, { GetContentListParams } from '@/api/CommonContent';
+import UnmoveableContent from '@/api/inheritor/UnmoveableContent';
+import { goCommonContentList } from '../article/common/CommonContent';
+import { toast } from '@/components/utils/DialogAction';
+
+const currentLonlat = ref<{ longitude: number, latitude: number }>({ longitude: 0, latitude: 0 });
+const isLightMode = ref(false);
+
+function handleLight() {
+  isLightMode.value = true;
+  toast('请在地图上选择你要点亮的村落哦');
+}
+
+function getedCurrentLonlat(lonlat: { longitude: number, latitude: number }) {
+  currentLonlat.value = lonlat;
+  recommendedNearbySitesLoader.loadData();
+}
 
 function goVillageDetails(e: any) {
   const id = typeof e.markerId == 'number' ? e.markerId : e.id;
@@ -108,6 +157,15 @@ function goVillageDetails(e: any) {
   }, 200);
 }
 
+const recommendedNearbySitesLoader = useSimpleDataLoader(async () => {
+  return (await CommonContent.getContentList(new GetContentListParams()
+    .setModelId(UnmoveableContent.modelId)
+    .setSelfValues({
+      longitude: currentLonlat.value.longitude,
+      latitude: currentLonlat.value.latitude,
+    }), 1, 8)).list;
+});
+
 const villageLoader = useSimpleDataLoader(async () => {
   const res = (await VillageApi.getVallageList(undefined, 1)).map((p, i) => ({
     ...p,

+ 183 - 0
src/pages/home/light/details.vue

@@ -0,0 +1,183 @@
+<template>
+  <SimplePageContentLoader :loader="contentLoader">
+    <FlexCol v-if="contentLoader.loadStatus.value == 'finished'">
+      <swiper 
+        circular
+        :indicator-dots="false"
+        :autoplay="true"
+        :interval="2000"
+        :duration="1000"
+        :style="{
+          width: '100%',
+          height: '500rpx',
+        }"
+      >
+        <swiper-item v-for="(item, k) in data.images" :key="k">
+          <Image 
+            :src="item" 
+            :radius="20"
+            :height="500"
+            :width="750"
+            mode="aspectFill"
+            touchable
+            @click="onPreviewImage(k as number)"
+          />
+        </swiper-item>
+      </swiper>
+
+      <FlexCol :padding="20" :radius="20" :gap="20">
+
+        <Height :height="20" />
+        <FlexCol>
+          <SubTitle :title="data.villageName" />
+          <IntroBlock 
+            small
+            :descItems="[
+              {
+                label: '保护级别',
+                value: data.historyLevelText ,
+              },
+              {
+                label: '年份时间',
+                value: data.ageText,
+              },
+              {
+                label: '所属区域',
+                value: data.regionText ,
+              },
+            ]"
+          />
+          <FlexCol :margin="[20, 0, 0, 0]">
+            <Parse :content="data.overview" />
+            <Text v-if="!data.overview" fontConfig="subText">无内容,请添加内容! </Text>
+          </FlexCol>
+        </FlexCol>
+
+        <FlexCol :margin="[20, 0, 30, 0]">
+          <SubTitle title="地理位置" />
+          <FlexCol :radius="20" backgroundColor="white" :margin="[20, 0, 0, 0]">
+            <map 
+              id="map"
+              :latitude="center[1]"
+              :longitude="center[0]"
+              :markers="markers"
+              :scale="15"
+              :style="{
+                width: '100%',
+                height: '350rpx',
+              }"
+            />
+            <Touchable 
+              v-if="data.address"
+              :title="data.address"
+              touchable
+              direction="column"
+              :padding="20"
+              @click="goAddress"
+            >
+              <IconTextBlock 
+                icon="map-filling"
+                :iconProps="{ size: 30 }"
+                :title="data.address"
+                extra="去这里"
+                :extraProps="{
+                  color: 'primary',
+                }"
+              />
+            </Touchable>
+          </FlexCol>
+        </FlexCol>
+      </FlexCol>
+
+    </FlexCol>
+  </SimplePageContentLoader>
+</template>
+
+<script setup lang="ts">
+import { ref, toRefs, type Ref } from 'vue';
+import { useLoadQuerys } from '@/common/composeabe/LoadQuerys';
+import { useSwiperImagePreview } from '@/common/composeabe/SwiperImagePreview';
+import { navTo } from '@/components/utils/PageAction';
+import { onShareTimeline, onShareAppMessage } from '@dcloudio/uni-app';
+import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
+import { useHomePageMiniCommonListGoMoreAndGoDetail, type IHomePageMiniCommonListGoMoreAndGoDetail } from '@/pages/article/common/CommonContent';
+import IntroBlock from '@/pages/article/common/IntroBlock.vue';
+import Parse from '@/components/display/parse/Parse.vue';
+import SimplePageContentLoader from '@/common/components/SimplePageContentLoader.vue';
+import Box2LineLargeImageUserShadow from '@/common/components/parts/Box2LineLargeImageUserShadow.vue';
+import VillageApi from '@/api/inhert/VillageApi';
+import ImagesUrls from '@/common/config/ImagesUrls';
+import SubTitle from '@/components/display/title/SubTitle.vue';
+import FlexCol from '@/components/layout/FlexCol.vue';
+import Image from '@/components/basic/Image.vue';
+import Text from '@/components/basic/Text.vue';
+import FlexRow from '@/components/layout/FlexRow.vue';
+import Touchable from '@/components/feedback/Touchable.vue';
+import Height from '@/components/layout/space/Height.vue';
+import IconTextBlock from '@/components/display/block/IconTextBlock.vue';
+
+const EmptyImage = 'https://mncdn.wenlvti.net/app_static/minnan/EmptyImage.png';
+
+
+const center = ref([118.15723, 24.48147]);
+const markers = ref<any>([]);
+
+const { querys } = useLoadQuerys({ 
+  id: 0,
+}, () => contentLoader.loadData());
+
+const data = ref<Record<string, any>>({
+  images: [],
+  overview: '',
+  longitude: 0,
+  latitude: 0,
+  region: 0,
+  address: '',
+  villageName: '',
+})
+function goAddress() {
+  uni.openLocation({
+    latitude: Number(data.value.latitude),
+    longitude: Number(data.value.longitude),
+  })
+}
+const { onPreviewImage } = useSwiperImagePreview(() => data.value.images || [])
+
+const contentLoader = useSimpleDataLoader(async () => {
+  data.value = {
+    ...data.value,
+    ...JSON.parse(uni.getStorageSync('VillageTemp') || '{}'),
+  };
+  console.log(data.value);
+
+  if (data.value.longitude && data.value.latitude) {
+    center.value = [Number(data.value.longitude), Number(data.value.latitude)];
+  } else {
+    center.value = [118.11593, 24.467580];
+  }
+  markers.value = [
+    {
+      id: 1,
+      latitude: center.value[1],
+      longitude: center.value[0],
+      iconPath: ImagesUrls.IconMarker,
+      width: 40,
+      height: 40,
+    }
+  ];
+}, false);
+
+
+function getPageShareData() {
+  return {
+    title: data.value.villageName,
+    imageUrl: data.value.images[0],
+  }
+}
+onShareTimeline(() => {
+  return getPageShareData(); 
+})
+onShareAppMessage(() => {
+  return getPageShareData();
+})
+</script>

+ 151 - 0
src/pages/home/light/submit.vue

@@ -0,0 +1,151 @@
+<template>
+  <CommonRoot>
+    <FlexCol :gap="20" :padding="30">
+      <!--注册-->
+      <FlexCol v-if="step === 'register'">
+        <DynamicForm
+          ref="registerFormRef"
+          :model="registerFormModel"
+          :options="registerFormDefine"
+        />
+        <Height :height="20" />
+        <Button block type="primary" @click="registerSubmit" :loading="registerFormLoading">提交</Button>
+      </FlexCol>
+      <!--注册完成-->
+      <Result 
+        v-else-if="step === 'finished'" 
+        status="success"
+        title="注册志愿者成功"
+        description="请等待管理员审核,在此期间,可以在社区中先逛逛,学习如何采编村社文化资源信息吧"
+      >
+        <Height :size="20" />
+        <Button type="primary" @click="back()">进入首页</Button>
+      </Result>
+      <!--错误-->
+      <Result 
+        v-else-if="step === 'error'" 
+        status="error"
+        title="分享链接参数有误"
+        description="请联系管理员,或稍后重试"
+      />
+      <!--登录-->
+      <FlexCol v-else center :height="400">
+        
+        <Icon icon="smile-filling" color="primary" :size="156" />
+        <Height :height="20" />
+        <Text :fontSize="26" color="primary" text="欢迎注册,加入志愿者队伍,点亮村落" />
+        <Height :size="40" />
+
+        <!-- #ifdef MP-WEIXIN -->
+        <Button type="primary" block text="微信登录" @click="loginWechat" />
+        <Height :size="20" />
+        <!-- #endif -->
+        <!-- #ifndef MP-WEIXIN -->
+        <Result status="warning" title="提示" desc="当前环境不支持微信登录" />
+        <!-- #endif -->
+
+      </FlexCol>
+    </FlexCol>
+  </CommonRoot>
+</template>
+
+<script setup lang="ts">
+import Button from '@/components/basic/Button.vue';
+import Result from '@/components/feedback/Result.vue';
+import FlexCol from '@/components/layout/FlexCol.vue';
+import Height from '@/components/layout/space/Height.vue';
+import Icon from '@/components/basic/Icon.vue';
+import Text from '@/components/basic/Text.vue';
+import CommonRoot from '@/components/dialog/CommonRoot.vue';
+import DynamicForm from '@/components/dynamic/DynamicForm.vue';
+import { useAppInit } from '@/common/composeabe/AppInit';
+import { UserApi } from '@/api/auth/UserApi';
+import { useAuthStore } from '@/store/auth';
+import { back, redirectTo } from '@/components/utils/PageAction';
+import { closeToast, toast } from '@/components/dialog/CommonRoot';
+import { showError } from '@/common/composeabe/ErrorDisplay';
+import { useLoadQuerys } from '@/common/composeabe/LoadQuerys';
+import { onMounted, ref } from 'vue';
+import { getVolunteerForm } from '@/pages/dig/admin/data/volunteer';
+import VillageApi, { VolunteerInfo } from '@/api/inhert/VillageApi';
+import type { IDynamicFormOptions, IDynamicFormRef } from '@/components/dynamic';
+
+/**
+ * 分享注册页面
+ */
+
+const authStore = useAuthStore();
+const { init } = useAppInit();
+
+const { querys } = useLoadQuerys({ 
+  villageId: 0,  
+});
+const step = ref<'register'|'finished'|'error'>('register');
+
+onMounted(async () => {
+  if (!querys.value.villageId) {
+    step.value = 'error';
+    return;
+  }
+  registerFormDefine.value = getVolunteerForm({
+    canSetCatalog: false,
+    villageId: querys.value.villageId,
+    onlyPassword: false,
+    isNew: ref(true),
+    formRef: registerFormRef,
+  });
+});
+
+async function loginWechat() {
+  toast({
+    type: 'loading',  
+    content: '登录中...',
+  })
+
+  try {
+    const res = await Promise.all([
+      uni.login({ provider: 'weixin' }),
+      uni.getUserProfile({ desc: '用于完善会员资料' }),
+    ]);
+    await authStore.loginWechart(res[0].code, res[1]);
+    toast({
+      type: 'success',  
+      content: '登录成功',
+    });
+    step.value = 'register';
+  } catch(e) {
+    showError(e);
+  } finally {
+    closeToast();
+  }
+}
+
+const registerFormLoading = ref(false);
+const registerFormRef = ref<IDynamicFormRef>();
+const registerFormModel = ref<VolunteerInfo>(new VolunteerInfo());
+const registerFormDefine = ref<IDynamicFormOptions>();
+
+async function registerSubmit() {
+  if (!registerFormRef.value || !registerFormModel.value)
+    return;
+  try {
+    await registerFormRef.value.validate();
+  } catch (e) {
+    toast({ content: '有必填项未填写,请检查' });
+    return;
+  }
+  try {
+    registerFormLoading.value = true;
+    registerFormModel.value!.villageId = querys.value.villageId;
+    const loginRes = await VillageApi.shareAddVolunteer(registerFormModel.value as VolunteerInfo);
+    await authStore.loginResultHandle(loginRes, UserApi.LOGIN_TYPE_USER);
+    await init();
+    toast({ content: '注册成功' });
+    step.value = 'finished';
+  } catch (e) {
+    showError(e);
+  } finally {
+    registerFormLoading.value = false;
+  }
+}
+</script>

+ 2 - 2
src/pages/home/village/details.vue

@@ -186,8 +186,8 @@ const data = ref<Record<string, any>>({
 })
 function goAddress() {
   uni.openLocation({
-    latitude: data.value.latitude,
-    longitude: data.value.longitude,
+    latitude: Number(data.value.latitude),
+    longitude: Number(data.value.longitude),
   })
 }
 const { onPreviewImage } = useSwiperImagePreview(() => data.value.images || [])