快乐的梦鱼 vor 3 Wochen
Ursprung
Commit
bc0a5b13d6

+ 1 - 0
src/components/basic/Image.vue

@@ -14,6 +14,7 @@
       :style="{
         width: precentOrFull(style.width),
         height: precentOrFull(style.height),
+        borderRadius: themeContext.resolveThemeSize(props.radius),
       }"
       :mode="(mode as any)"
       :lazyLoad="$attrs.lazyLoad"

+ 1 - 0
src/components/display/block/ImageBlock.vue

@@ -36,6 +36,7 @@
         <Text class="nana-image-desc" color="text.second" v-bind="descProps" :text="desc" />
       </BackgroundBox>
     </slot>
+    <slot name="footer" />
   </Touchable>
 </template>
 

+ 8 - 14
src/components/display/block/ImageBlock2.vue

@@ -9,10 +9,10 @@
   >
     <Image 
       :src="src" 
+      :width="imageWidth || '100%'"
       :height="imageHeight"
       :radius="imageRadius"
-      width="100%"
-      mode="aspectFill"
+      :mode="imageHeight ? 'aspectFill' : 'widthFix'"
     />
     <slot name="desc">
       <FlexCol :padding="15">
@@ -26,6 +26,7 @@
             <slot name="extra" />
           </template>
         </IconTextBlock>
+        <slot name="footer" />
       </FlexCol>
     </slot>
   </Touchable>
@@ -60,6 +61,10 @@ export interface ImageBlock2Props extends Partial<FlexProps> {
    */
   imageHeight?: string | number;
   /**
+   * 图片宽度。
+   */
+  imageWidth?: string | number;
+  /**
    * 图片的路径。
    */
   src?: string;
@@ -84,7 +89,6 @@ export interface ImageBlock2Props extends Partial<FlexProps> {
 const theme = useTheme();
 const props = withDefaults(defineProps<ImageBlock2Props>(), {
   width: 400,
-  imageHeight: 250,
   imageRadius: 0,
   direction: 'column',
   backgroundColor: "white",
@@ -94,14 +98,4 @@ const props = withDefaults(defineProps<ImageBlock2Props>(), {
 defineEmits([	
   "click"	
 ])
-</script>
-
-<style lang="scss">
-.nana-image-desc {
-  color: #fff;
-  font-size: 25rpx;
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-}
-</style>
+</script>

+ 1 - 1
src/components/display/block/ImageBlock3.vue

@@ -29,7 +29,7 @@
           </template>
         </IconTextBlock>
       </slot>
-
+      <slot name="footer" />
     </FlexView>
   </Touchable>
 </template>

+ 32 - 0
src/components/layout/masonry/MasonryGrid.vue

@@ -0,0 +1,32 @@
+<template>
+  <FlexView v-bind="props" :innerStyle="{
+    display: 'grid',
+    gridTemplateColumns: `repeat(${columnCount}, 1fr)`, /* 定义3列 */
+    gridTemplateRows: 'masonry', /* 行方向采用瀑布流布局 */
+    rowGap: resolveThemeSize(rowSpacing), /* 间距 */
+    columnGap: resolveThemeSize(columnSpacing), /* 间距 */
+    ...(innerStyle || {}),
+  }">
+    <slot />
+  </FlexView>
+</template>
+
+<script setup lang="ts">
+import { useTheme } from '@/components/theme/ThemeDefine';
+import FlexView, { type FlexProps } from '../FlexView.vue';
+
+export interface MasonryGridProps extends FlexProps {
+  columnCount?: number;
+  columnSpacing?: number;
+  rowSpacing?: number;
+  itemHeight?: number;
+}
+
+const props = withDefaults(defineProps<MasonryGridProps>(), {
+  columnCount: 2,
+  columnSpacing: 10,
+  rowSpacing: 10,
+});
+
+const { resolveThemeSize } = useTheme();
+</script>

+ 68 - 0
src/pages/home/components/VillageUserRankList.vue

@@ -0,0 +1,68 @@
+<template>
+  <FlexRow align="flex-end">
+    <FlexCol 
+      v-for="(item) in list" 
+      position="relative" 
+      :key="item.title" 
+      :width="item.rank == 1 ? '35%' : '32.5%'" 
+      :height="item.rank == 1 ? 380 : 320"
+      :innerStyle="{
+        backgroundSize: '100% auto',
+        backgroundRepeat: 'no-repeat',
+        backgroundPosition: 'center',
+        backgroundImage: `url('/static/images/home/Rank${item.rank}.png')`
+      }"
+      overflow="hidden"
+    >
+      <FlexCol 
+        position="absolute"
+        :left="15"
+        :right="15"
+        :bottom="item.rank == 1 ? 66 : 40"
+        center
+      >
+        <Avatar :url="item.image" :size="item.rank == 1 ? 100 : 90" mode="aspectFill" :radius="20" />
+        <Height :height="item.rank == 1 ? 15 : 10" />
+        <Text fontConfig="h4" :text="item.title" color="white" />
+        <Text fontConfig="h2" :text="item.score" :color="scoreColors[item.rank - 1]" />
+      </FlexCol>
+    </FlexCol>
+  </FlexRow>
+</template>
+
+<script setup lang="ts">
+import Image from '@/components/basic/Image.vue';
+import Text from '@/components/basic/Text.vue';
+import Avatar from '@/components/display/Avatar.vue';
+import FlexCol from '@/components/layout/FlexCol.vue';
+import FlexRow from '@/components/layout/FlexRow.vue';
+import Height from '@/components/layout/space/Height.vue';
+
+
+const list = [
+  {
+    image: 'https://mncdn.wenlvti.net/app_static/minnan/images/test/ImageTest2.png',
+    title: '用户2',
+    rank: 2,
+    score: 90,
+  },
+  {
+    image: 'https://mncdn.wenlvti.net/app_static/minnan/images/test/ImageTest1.png',
+    title: '用户1',
+    rank: 1,
+    score: 100,
+  },
+  {
+    image: 'https://mncdn.wenlvti.net/app_static/minnan/images/test/ImageTest3.png',
+    title: '用户3',
+    rank: 3,
+    score: 80,
+  },
+]
+const scoreColors = [
+  '#cb8833',
+  '#7788b9',
+  '#cd7853',
+]
+
+</script>

+ 47 - 115
src/pages/home/index.vue

@@ -28,9 +28,7 @@
       />
     </FlexCol>
 
-    <VillageMiniMap 
-      @getedCurrentLonlat="getedCurrentLonlat" 
-    />
+    <VillageMiniMap />
     <FlexRow justify="space-between" :padding="[10, 16]">
       <Button icon="/static/images/home/IconSwitch.png" :radius="40" :padding="[10, 30]" :iconProps="{ innerStyle: { marginRight: '10rpx' }}">切换城市</Button>
       <Button icon="/static/images/home/IconSwitch.png" :radius="40" :padding="[10, 30]" :iconProps="{ innerStyle: { marginRight: '10rpx' }}">我的关注</Button>
@@ -41,12 +39,35 @@
     <VillageRankList />
 
     <HomeTitle title="志愿者排名" showMore :lightCount="3" />
-    
-
+    <VillageUserRankList />
 
     <HomeTitle title="精选记忆" showMore />
-
-
+    <MasonryGrid>
+      <ImageBlock2
+        v-for="(item, i) in recommendLoader.content.value"
+        :key="i"
+        :src="item.image"
+        :title="item.title"
+        :desc="item.desc"
+        :width="340"
+        :imageWidth="340"
+        :imageRadius="15"
+        backgroundColor="transparent"
+      >
+        <template #footer> 
+          <FlexRow justify="space-between" align="center" :padding="[10,0]" :margin="[10,0,0,0]">
+            <FlexRow align="center" :gap="10">
+              <Avatar :url="item.image" :size="40" />
+              <Text :text="item.userName" :fontSize="24" color="gray" />
+            </FlexRow>
+            <FlexRow align="center" :gap="10">
+              <Icon icon="favorite" :color="item.isLike ? 'primary' : 'gray'" :size="30" />
+              <Text :text="item.likes" :fontSize="30" :color="item.isLike ? 'primary' : 'gray'" />
+            </FlexRow>
+          </FlexRow>
+        </template>
+      </ImageBlock2>
+    </MasonryGrid>
 
     <Loadmore status="nomore" />
     <Height :height="150" />
@@ -54,131 +75,42 @@
 </template>
 
 <script setup lang="ts">
-import { navTo } from '@/components/utils/PageAction';
-import { goCommonContentDetail, goCommonContentList } from '../article/common/CommonContent';
+import { ref } from 'vue';
 import { useTheme } from '@/components/theme/ThemeDefine';
 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';
-import SimplePageContentLoader from '@/common/components/SimplePageContentLoader.vue';
-import AppCofig from '@/common/config/AppCofig';
 import Image from '@/components/basic/Image.vue';
-import Text from '@/components/basic/Text.vue';
 import Loadmore from '@/components/display/loading/Loadmore.vue';
-import Touchable from '@/components/feedback/Touchable.vue';
 import FlexCol from '@/components/layout/FlexCol.vue';
 import FlexRow from '@/components/layout/FlexRow.vue';
 import Height from '@/components/layout/space/Height.vue';
-import Width from '@/components/layout/space/Width.vue';
-import CommonContent, { GetContentListParams } from '@/api/CommonContent';
-import UnmoveableContent from '@/api/inheritor/UnmoveableContent';
 import SearchBar from '@/components/form/SearchBar.vue';
 import VillageMiniMap from './components/VillageMiniMap.vue';
 import Button from '@/components/basic/Button.vue';
 import HomeTitle from '@/common/components/parts/HomeTitle.vue';
 import VillageRankList from './components/VillageRankList.vue';
+import VillageUserRankList from './components/VillageUserRankList.vue';
+import MasonryGrid from '@/components/layout/masonry/MasonryGrid.vue';
+import ImageBlock2 from '@/components/display/block/ImageBlock2.vue';
+import VillageInfoApi from '@/api/inhert/VillageInfoApi';
+import Avatar from '@/components/display/Avatar.vue';
+import Icon from '@/components/basic/Icon.vue';
+import Text from '@/components/basic/Text.vue';
 
 const themeContext = useTheme();
-
-const currentLonlat = ref<{ longitude: number, latitude: number }>({ longitude: 0, latitude: 0 });
 const searchKeywords = ref('');
 
-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;
-  uni.setStorageSync('VillageTemp', JSON.stringify(villageLoader.content.value?.find(p => p.id == id)));
-  setTimeout(() => {
-    navTo('/pages/home/village/details', { id: id });
-  }, 200);
-}
-
-/** 根据两点经纬度计算直线距离(米),Haversine 公式 */
-function getDistanceMeters(
-  lon1: number, lat1: number,
-  lon2: number, lat2: number
-): number {
-  const R = 6371000; // 地球半径 米
-  const dLat = (lat2 - lat1) * Math.PI / 180;
-  const dLon = (lon2 - lon1) * Math.PI / 180;
-  const a =
-    Math.sin(dLat / 2) * Math.sin(dLat / 2) +
-    Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
-    Math.sin(dLon / 2) * Math.sin(dLon / 2);
-  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
-  return R * c;
-}
-function getDistance(longitude: number, latitude: number): string {
-  const meters = getDistanceMeters(longitude, latitude, currentLonlat.value.longitude, currentLonlat.value.latitude);
-  if (meters < 100) return '一百米内';
-  if (meters < 1000) return `${Math.round(meters / 100) * 100}米`;
-  return `${Math.round(meters / 1000)}km`;
-}
-
-const recommendedNearbySitesLoader = useSimpleDataLoader(async () => {
-  const res = (await CommonContent.getContentList(new GetContentListParams()
-    .setModelId(UnmoveableContent.modelId)
-    .setSelfValues({
-      longitude: currentLonlat.value.longitude,
-      latitude: currentLonlat.value.latitude,
-    }), 1, 8, undefined, true)).list;
-
-  for (const item of res) 
-    item.distance = getDistance(item.longitude, item.latitude);
-  return res;
-});
-
-const villageLoader = useSimpleDataLoader(async () => {
-  const res = (await VillageApi.getVallageList(undefined, 1)).map((p, i) => ({
-    ...p,
-    id: p.id ?? i,
-    title: p.villageName,
-    longitude: Number(p.longitude),
-    latitude: Number(p.latitude),
-    width: 30,
-    height: 30,
-    iconPath: p.thumbnail || p.image,
-  }));
-  return res;
-});
-
 const recommendLoader = useSimpleDataLoader(async () => {
-  //const category = (await CommonContent.getCategoryList(151)).find(p => p.title == '省级');
-  return (await VillageApi.getVallageList(undefined, 1));
+   const res = (await VillageInfoApi.getListForDiscover(
+    1, 20, 
+    '', 
+  ));
+  return res.list.concat(res.list).map((item) => ({
+    ...item,
+    isLike: Math.random() > 0.5,
+    likes: Math.floor(Math.random() * 1000),
+    userName: '用户' + Math.floor(Math.random() * 1000000),
+    badge: item.villageName || '',
+  }))
 });
 
-const discoverLoader = useSimpleDataLoader(async () => {
-  return (await VillageInfoApi.getListForDiscover(1, 10)).list.map((item) => {
-    return {
-      ...item,
-      image: (item.thumbnail || item.image) as string,
-      desc: item.desc || '',
-      title: item.title,
-    }
-  })
-});
-
-const newsLoader = useSimpleDataLoader(async () => {
-  return (await CommonContent.getContentList(new GetContentListParams()
-    .setModelId(18)
-    .setMainBodyColumnId(363)
-  , 1, 8)).list;
-});
-
-function goDiscoverDetails(item: any) {
-  navTo('/pages/home/discover/details', {
-    id: item.id,
-    collectModelId: item.collectModuleId,
-    collectModelInternalName: item.collectModuleInternalName,
-  });
-}
-
-function goList(keywords: string) {
-}
-
 </script>