浏览代码

📦 村庄页设计图补充,细节优化

快乐的梦鱼 1 月之前
父节点
当前提交
ca1d359c6d

+ 20 - 1
src/common/components/Construction.vue

@@ -10,6 +10,10 @@
         </FlexCol>
       </view>
     </slot>
+    <view v-if="$slots.default" class="construction-float">
+      <Icon icon="warning-filling" :size="34" color="#f5c518" />
+      <Text :text="text" fontConfig="subText" textAlign="center" color="text.second" />
+    </view>
   </view>
 </template>
 
@@ -31,7 +35,7 @@ withDefaults(defineProps<{
 
 <style lang="scss" scoped>
 .construction-frame {
-  width: 100%;
+  position: relative;
   padding: 10rpx;
   border-radius: 20rpx;
   background-color: #f5c518;
@@ -45,6 +49,21 @@ withDefaults(defineProps<{
   box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
 }
 
+.construction-float {
+  position: absolute;
+  top: -25rpx;
+  left: 50%;
+  transform: translateX(-50%);
+  background-color: #fff;
+  padding: 10rpx;
+  border-radius: 10rpx;
+  box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 10rpx;
+}
+
 .construction-inner {
   border-radius: 14rpx;
   background: #fff;

+ 27 - 0
src/common/components/FrameButton.vue

@@ -0,0 +1,27 @@
+<template>
+  <BackgroundImageButton 
+    :backgroundImage="primary ? 'https://xy.wenlvti.net/app_static/images/FrameButtonPrimary.png' : 
+      'https://xy.wenlvti.net/app_static/images/FrameButton.png'"
+    :backgroundCutBorder="32"
+    :backgroundCutBorderSize="36"
+    :padding="[32, 36]"
+    @click="emit('click')"
+  >
+    <Text :text="text" fontConfig="contentText" :color="primary ? 'white' : 'black'" />
+  </BackgroundImageButton>
+</template>
+
+<script setup lang="ts">
+import BackgroundImageButton from '@/components/basic/BackgroundImageButton.vue';
+import Text from '@/components/basic/Text.vue';
+
+const props = withDefaults(defineProps<{
+  text: string;
+  primary?: boolean;
+}>(), {
+  text: '去发布',
+  primary: false,
+}); 
+
+const emit = defineEmits(['click']);
+</script>

+ 6 - 0
src/common/config/AppCofig.ts

@@ -47,6 +47,12 @@ const accountInfo = uni.getAccountInfoSync();
  * 获取当前环境版本
  */
 export const envVersion = accountInfo.miniProgram.envVersion;
+
+
+/**
+ * 获取当前环境是否为小程序模拟器
+ */
+export const isDevEnv = envVersion === 'develop' 
 /**
  * 获取当前环境是否为测试环境
  */

+ 13 - 6
src/components/display/block/BackgroundBox.vue

@@ -38,7 +38,7 @@ export default {}
 
 <script setup lang="ts">
 import FlexView, { type FlexProps } from '@/components/layout/FlexView.vue';
-import { useTheme, type ViewStyle } from '@/components/theme/ThemeDefine';
+import { resolveSize, useTheme, type ViewStyle } from '@/components/theme/ThemeDefine';
 import { solveUrl } from '@/components/theme/ThemeTools';
 import { computed, type PropType } from 'vue';
 
@@ -105,14 +105,14 @@ export interface BackgroundBoxProps extends FlexProps {
    * 格式:
    * - 数组:[ top, right, bottom, left ]
    */
-  backgroundCutBorder?: Array<number|string>;
+  backgroundCutBorder?: Array<number|string> | string | number;
   /**
    * 背景图片九宫格渲染大小。
    *
    * 格式:
    * - 数组:[ top, right, bottom, left ]
    */
-  backgroundCutBorderSize?: Array<number|string>;
+  backgroundCutBorderSize?: Array<number|string> | string | number;
 }
 
 defineOptions({
@@ -170,11 +170,18 @@ const style = computed(() => {
 
   } else if (props.backgroundImage) {
     const b = props.backgroundCutBorder;
-    const s = props.backgroundCutBorderSize;
+    let s = props.backgroundCutBorderSize;
+    if (!s) {
+      if (Array.isArray(b)) {
+        s = b.map((i) => resolveSize(i) as any);
+      } else {
+        s = resolveSize(b) as any;
+      }
+    }
     if (b) {
       o.borderImageSource = solveUrl(props.backgroundImage);
-      o.borderImageSlice = `${b[0]} ${b[1]} ${b[2]} ${b[3]} fill`;
-      o.borderImageWidth = `${theme.resolveSize(s[0])} ${theme.resolveSize(s[1])} ${theme.resolveSize(s[2])} ${theme.resolveSize(s[3])}`;
+      o.borderImageSlice =  Array.isArray(b) ? `${b[0]} ${b[1]} ${b[2]} ${b[3]} fill` : `${b} fill`;
+      o.borderImageWidth = Array.isArray(s) ? `${theme.resolveSize(s[0])} ${theme.resolveSize(s[1])} ${theme.resolveSize(s[2])} ${theme.resolveSize(s[3])}` : `${theme.resolveSize(s)}`;
       o.borderImageRepeat = 'stretch';
     } else {
       o.backgroundImage = solveUrl(props.backgroundImage);

+ 4 - 5
src/components/theme/ThemeDefine.ts

@@ -270,16 +270,15 @@ function isRealSize(inValue: ThemeSizeType|undefined) {
 }
 /**
  * 数字单位的处理
- * @param inValue 
- * @param theme 
- * @param themeType 
+ * @param inValue 输入值
+ * @param forceUnit 强制单位,默认为defaultSizeUnit即系统配置单位
  * @returns 
  */
-export function resolveSize(inValue: ThemeSizeType|undefined) : string|undefined {
+export function resolveSize(inValue: ThemeSizeType|undefined, forceUnit = defaultSizeUnit) : string|undefined {
   if (inValue == undefined)
     return undefined;
   if (isNumbrSize(inValue))
-    return `${inValue}${defaultSizeUnit}`;
+    return `${inValue}${forceUnit}`;
   if (isRealSize(inValue as string))
     return inValue as string;
   if (inValue == 'fill')

+ 7 - 0
src/pages.json

@@ -46,6 +46,13 @@
       }
     },
     {
+      "path": "pages/home/light/help/new",
+      "style": {
+        "navigationBarTitleText": "新手上路",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
       "path": "pages/home/light/submit",
       "style": {
         "navigationBarTitleText": "点亮村落",

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

@@ -105,8 +105,6 @@ const props = defineProps<{
 }>();
 
 const regionLoader = useSimpleDataLoader(async () => {
-  console.log('regionLoader', props.city);
-  
   if (!props.city)
     return [];
   return (await CommonContent.searchRegion(props.city)).map(p => ({
@@ -115,8 +113,6 @@ const regionLoader = useSimpleDataLoader(async () => {
   }));
 }, false);
 const mapLoader = useSimpleDataLoader<MapMarker[]>(async () => {
-  console.log('mapLoader', selectedRegion.value);
-  
   mapCtx.removeMarkers({
     markerIds: Array.from(villageData.keys()),
   })

+ 4 - 3
src/pages/home/index.vue

@@ -25,7 +25,7 @@
         <Width :width="150" />
       </FlexRow>
     </FlexCol>
-    <Height height="200px" />
+    <Height height="150px" />
     <FlexCol :gap="20" align="center">
       <FlexRow justify="space-between" align="center" width="100%">
         <SearchBar v-model="searchKeywords" placeholder="搜索" :innerStyle="{
@@ -42,7 +42,7 @@
         src="https://xy.wenlvti.net/app_static/images/home/BannerIndex.png" 
         width="700rpx" 
         height="200px"
-        mode="aspectFill" 
+        mode="aspectFit" 
         :innerStyle="{
           border: '2px solid #fff',
           borderRadius: '20rpx',
@@ -108,7 +108,7 @@
     </FlexRow>
 
     <HomeTitle title="最新动态" />
-    <Construction>
+    <Construction text="测试数据,没有接口!">
       <FlexCol gap="gap.lg">
         <FlexRow 
           v-for="item in activityLoader.content.value" :key="item.id"
@@ -121,6 +121,7 @@
           <Avatar 
             :url="item.head"
             :size="80"
+            :round="false"
             :radius="10"
           />
           <Text :text="item.content" fontConfig="contentText" :innerStyle="{ flex: 1 }" />

+ 7 - 0
src/pages/home/light/help/new.vue

@@ -0,0 +1,7 @@
+<template>
+  <Construction text="等待设计《新手上路页面》" />
+</template>
+
+<script setup lang="ts">
+import Construction from '@/common/components/Construction.vue';
+</script>

+ 1 - 1
src/pages/home/light/submit.vue

@@ -213,7 +213,7 @@ async function addSubmit() {
     addFormLoading.value = true;
     addFormModel.value.villageId = querys.value.villageId;
     await VillageApi.claimVallage(addFormModel.value as VillageClaimInfo);
-    toast({ content: '认领成功' });
+    toast({ content: '提交成功' });
     finishedMode.value = 'claim';
     step.value = 'finished';
   } catch (e) {

+ 53 - 0
src/pages/home/village/composeabe/Follow.ts

@@ -0,0 +1,53 @@
+import FollowVillageApi from "@/api/light/FollowVillageApi";
+import { useReqireLogin } from "@/common/composeabe/RequireLogin";
+import { useVillageStore } from "@/store/village";
+import { computed } from "vue";
+import { toast, confirm } from "@/components/dialog/CommonRoot";
+
+export function useFollow() {
+
+  const { requireLogin } = useReqireLogin();
+  const villageStore = useVillageStore();
+  const isFollowed = computed(() => {
+    return villageStore.myFollowVillages.some(p => p.id === villageStore.currentVillage?.id);
+  });
+
+  async function onFollow() {
+    if (!villageStore.currentVillage) 
+      return;
+    requireLogin(async () => {
+      try {
+        await FollowVillageApi.followVillage(villageStore.currentVillage!.id);
+        villageStore.myFollowVillages.push(villageStore.currentVillage!);
+        toast('关注成功');
+      } catch {
+        toast('关注失败');
+      }
+    }, '登录后才能关注村庄哦');
+  }
+  function onUnFollow() {
+    if (!villageStore.currentVillage) return;
+    confirm({
+      title: '取消关注',
+      content: '确定取消关注该村庄吗?',
+      confirmText: '取消关注',
+      cancelText: '取消',
+    }).then(async (res) => {
+      if (res) {
+        try { 
+          await FollowVillageApi.unfollowVillage(villageStore.currentVillage!.id);
+          villageStore.myFollowVillages = villageStore.myFollowVillages.filter(p => p.id !== villageStore.currentVillage!.id);
+          toast('取消关注成功');
+        } catch {
+          toast('取消关注失败');
+        }
+      }
+    });
+  }
+
+  return {
+    isFollowed,
+    onFollow,
+    onUnFollow,
+  };
+}

+ 66 - 34
src/pages/home/village/index.vue

@@ -1,41 +1,55 @@
 <template>
-  <FlexCol v-if="showSwitch" position="absolute" :left="0" :top="0">
-    <StatusBarSpace />
-    <Button
-      @click="showMyFollowPopup = true"
-      icon="https://xy.wenlvti.net/app_static/images/home/IconSwitch.png"
-      :text="villageStore.currentVillage?.name || '未选择村庄'"
-      type="custom"
-      color="transparent"
-    />
-  </FlexCol>
-  <FlexCol v-if="villageStore.currentVillage" gap="gap.md">
-    <FlexRow center :padding="[0,30]" gap="gap.md">
-      <HomeLargeTitle title="村社名片" :active="tab === 'card'" @click="tab = 'card'" />
-      <HomeLargeTitle title="乡源树" :active="tab === 'tree'" @click="tab = 'tree'">
-        <template #icon>
-          <Image src="https://xy.wenlvti.net/app_static/images/village/TreeIconAmin.gif" :width="45" :height="46" mode="heightFix" />
-        </template>
-      </HomeLargeTitle>
-    </FlexRow>
-    <Card v-if="tab === 'card'" />
-    <Tree v-if="tab === 'tree'" />
-    <Popup 
-      v-model:show="showMyFollowPopup" 
-      closeable
-      position="bottom"
-      round
-      size="80vh"
-    >
-      <VillageMyFollow @goDetails="onSelectVillage" />
-    </Popup>
-    <Height :height="150" />
-  </FlexCol>
-  <Around v-else />
+  <CommonRoot>
+    <FlexCol v-if="showSwitch" position="absolute" :left="0" :top="0">
+      <StatusBarSpace />
+      <Button
+        @click="showMyFollowPopup = true"
+        icon="https://xy.wenlvti.net/app_static/images/home/IconSwitch.png"
+        :text="villageStore.currentVillage?.name || '未选择村庄'"
+        type="custom"
+        color="transparent"
+      />
+    </FlexCol>
+    <FlexCol v-if="villageStore.currentVillage" gap="gap.md">
+      <FlexCol v-if="!isLight" center :padding="[60,0]" gap="gap.md">
+        <Image src="https://xy.wenlvti.net/app_static/images/home/BadgeNew.png" :width="320" mode="widthFix" />
+        <Text text="本村暂未点亮" fontConfig="primaryTitle" />
+        <Text text="您可以申请成为志愿者,点亮村庄" fontConfig="contentText" />
+        <Height :height="20" />
+        <FlexRow gap="gap.lg">
+          <FrameButton :text="isFollowed ? '已关注' : '先关注村庄'" @click="isFollowed ? onUnFollow() : onFollow()" />
+          <FrameButton text="去申请点亮" @click="handleGoApply()" primary />
+        </FlexRow>
+      </FlexCol>
+      <template v-else>
+        <FlexRow center :padding="[0,30]" gap="gap.md">
+          <HomeLargeTitle title="村社名片" :active="tab === 'card'" @click="tab = 'card'" />
+          <HomeLargeTitle title="乡源树" :active="tab === 'tree'" @click="tab = 'tree'">
+            <template #icon>
+              <Image src="https://xy.wenlvti.net/app_static/images/village/TreeIconAmin.gif" :width="45" :height="46" mode="heightFix" />
+            </template>
+          </HomeLargeTitle>
+        </FlexRow>
+        <Card v-if="tab === 'card'" />
+        <Tree v-if="tab === 'tree'" />
+      </template>
+      <Popup 
+        v-model:show="showMyFollowPopup" 
+        closeable
+        position="bottom"
+        round
+        size="80vh"
+      >
+        <VillageMyFollow @goDetails="onSelectVillage" />
+      </Popup>
+      <Height :height="150" />
+    </FlexCol>
+    <Around v-else />
+  </CommonRoot>
 </template>
 
 <script setup lang="ts">
-import { onMounted, ref } from 'vue';
+import { computed, onMounted, ref } from 'vue';
 import { useVillageStore } from '@/store/village';
 import FlexCol from '@/components/layout/FlexCol.vue';
 import Height from '@/components/layout/space/Height.vue';
@@ -50,10 +64,16 @@ import Popup from '@/components/dialog/Popup.vue';
 import VillageMyFollow from '../components/VillageMyFollow.vue';
 import type { VillageListItem } from '@/api/light/LightVillageApi';
 import Around from './recommed/around.vue';
+import Text from '@/components/basic/Text.vue';
+import FrameButton from '@/common/components/FrameButton.vue';
+import CommonRoot from '@/components/dialog/CommonRoot.vue';
+import { navTo } from '@/components/utils/PageAction';
+import { useFollow } from './composeabe/Follow';
 
 const tab = ref('card');
 const villageStore = useVillageStore();
 const showMyFollowPopup = ref(false);
+const { isFollowed, onFollow, onUnFollow } = useFollow();
 
 const props = withDefaults(defineProps<{
   showSwitch?: boolean;
@@ -61,6 +81,10 @@ const props = withDefaults(defineProps<{
   showSwitch: false,
 });
 
+const isLight = computed(() => {
+  return villageStore.currentVillage?.isLight ?? false;
+});
+
 onMounted(() => {
   if (!props.showSwitch) {
     uni.setNavigationBarTitle({
@@ -69,6 +93,14 @@ onMounted(() => {
   }
 });
 
+function handleGoApply() {
+  navTo('/pages/home/light/submit', {
+    villageId: villageStore.currentVillage?.id ?? undefined,
+    unit: (villageStore.currentVillage?.city as string) + (villageStore.currentVillage?.district as string) + (villageStore.currentVillage?.township as string),
+    regionId: villageStore.currentVillage?.region ?? undefined,
+  });
+}
+
 function onSelectVillage(village: VillageListItem) {
   villageStore.setCurrentVillage(village);
   showMyFollowPopup.value = false;

+ 152 - 131
src/pages/home/village/introd/card.vue

@@ -5,7 +5,7 @@
     <BackgroundBox 
       color1="#eecaa0"
       color2="white"
-      color2Position="45%"
+      color2Position="85%"
       color3="white"
       radius="radius.lg"
       direction="column"
@@ -19,47 +19,79 @@
           <Text :text="villageInfoLoader.content.value?.title" fontConfig="primaryTitle" />
           <Text :text="villageInfoLoader.content.value?.address" fontConfig="contentText" />
         </FlexCol>
-        <FlexCol gap="gap.md">
-          <Button icon="https://xy.wenlvti.net/app_static/images/village/IconUser.png" radius="radius.lgr" :padding="[10, 30]" backgroundColor="white">申请管理者</Button>
-          <Text :text="`${villageInfoLoader.content.value?.applyCount} 个乡源果可申请`" fontConfig="secondText" />
-        </FlexCol>
+        <FlexRow center gap="gap.lg">
+          <Button 
+            icon="https://xy.wenlvti.net/app_static/images/village/IconJoin.png" 
+            radius="radius.lgr"
+            @click="isFollowed ? onUnFollow() : onFollow()"
+          >
+            {{ isFollowed ? '已关注' : '关注' }}
+          </Button>
+          <Button 
+            icon="https://xy.wenlvti.net/app_static/images/village/IconFollow.png"
+            radius="radius.lgr"
+            @click="handleGoJoin()"
+          >
+            {{ isJoined ? '已加入' : '加入' }}
+          </Button>
+        </FlexRow>
       </FlexRow>
+
+      <!-- 状态与申请 -->
+      <Construction text="没有接口,数据不正确">
+        <FlexRow 
+          backgroundColor="background.tertiary" 
+          radius="radius.md" 
+          padding="space.md" 
+          justify="space-between" 
+        >
+          <FlexCol gap="gap.md"> 
+            <Text :text="`${villageInfoLoader.content.value?.applyCount} 个乡源果可申请`" fontConfig="secondText" />  
+            <Text :text="`存储空间内存:${villageInfoLoader.content.value?.sizeText}`" fontConfig="secondText" />
+          </FlexCol>
+          <FlexCol gap="gap.md">
+            <Button icon="https://xy.wenlvti.net/app_static/images/village/IconUser.png" radius="radius.lgr" :padding="[10, 30]" backgroundColor="white">申请管理者</Button>
+          </FlexCol>
+        </FlexRow>
+      </Construction>
       
       <!-- 图片 -->
-      <FlexRow v-if="villageInfoLoader.content.value" justify="space-between">
-        <Image 
-          v-for="index of 3" 
-          :key="index" 
-          :src="villageInfoLoader.content.value.images[index - 1]" 
-          radius="radius.md"
-          :width="200"
-          :height="140"
-          mode="aspectFill"
-          defaultImage=""
-          touchable
-        >
-          <template #empty>
-            <Touchable 
-              direction="column" 
-              position="absolute" 
-              inset="0" 
-              center
-              backgroundColor="background.primary"
-            >
-              <Icon name="add" size="fontSize.md" />
-              <Text text="上传封面" fontConfig="secondText" />
-            </Touchable>
-          </template>
-          <FlexCol 
-            v-if="index === 3 && villageInfoLoader.content.value.images.length > 3" 
-            position="absolute" inset="0" 
-            center 
-            backgroundColor="mask.default"
+      <Construction text="没有接口">
+        <FlexRow v-if="villageInfoLoader.content.value" justify="space-between">
+          <Image 
+            v-for="index of 3" 
+            :key="index" 
+            :src="villageInfoLoader.content.value.images[index - 1]" 
+            radius="radius.md"
+            :width="200"
+            :height="140"
+            mode="aspectFill"
+            defaultImage=""
+            touchable
           >
-            <Text :text="`+${villageInfoLoader.content.value.images.length - 3}`" fontSize="fontSize.lg" color="white" />
-          </FlexCol>
-        </Image>
-      </FlexRow>
+            <template #empty>
+              <Touchable 
+                direction="column" 
+                position="absolute" 
+                inset="0" 
+                center
+                backgroundColor="background.primary"
+              >
+                <Icon name="add" size="fontSize.md" />
+                <Text text="上传封面" fontConfig="secondText" />
+              </Touchable>
+            </template>
+            <FlexCol 
+              v-if="index === 3 && villageInfoLoader.content.value.images.length > 3" 
+              position="absolute" inset="0" 
+              center 
+              backgroundColor="mask.default"
+            >
+              <Text :text="`+${villageInfoLoader.content.value.images.length - 3}`" fontSize="fontSize.lg" color="white" />
+            </FlexCol>
+          </Image>
+        </FlexRow>
+      </Construction>
 
       <!-- 地址 -->
       <FlexRow align="center" gap="gap.sm">
@@ -74,56 +106,46 @@
         }" 
       />
 
-      <FlexRow center gap="gap.lg">
-        <Button 
-          icon="https://xy.wenlvti.net/app_static/images/village/IconJoin.png" 
-          radius="radius.lgr"
-          size="large"
-          :innerStyle="{ flexBasis: '25%' }"
-          @click="isFollowed ? onUnFollow() : onFollow()"
-        >
-          {{ isFollowed ? '已关注' : '关注' }}
-        </Button>
-        <Button 
-          icon="https://xy.wenlvti.net/app_static/images/village/IconFollow.png" 
-          size="large"
-          radius="radius.lgr"
-          :innerStyle="{ flexBasis: '25%' }"
-        >
-          加入
-        </Button>
-      </FlexRow>
-
-      <FlexRow justify="space-between" align="center">
-        <FlexRow center gap="gap.lg" flexBasis="50%">
-          <Text text="村社排名" fontConfig="contentText" />
-          <Text text="No." fontConfig="lightTitle" />
-          <Text :text="villageInfoLoader.content.value?.rankText" fontConfig="primaryTitle" />
+      <Construction text="没有接口,数据不正确">
+        <FlexRow justify="space-between" align="center">
+          <FlexRow center gap="gap.lg" flexBasis="50%">
+            <Text text="村社排名" fontConfig="contentText" />
+            <Text text="No." fontConfig="lightTitle" />
+            <Text :text="villageInfoLoader.content.value?.rankText" fontConfig="primaryTitle" />
+          </FlexRow>
+          <FlexRow center gap="gap.lg" flexBasis="50%">
+            <Text text="村社等级" fontConfig="contentText" />
+            <Text :text="villageInfoLoader.content.value?.levelText" fontConfig="primaryTitle" />
+          </FlexRow>
         </FlexRow>
-        <FlexRow center gap="gap.lg" flexBasis="50%">
-          <Text text="村社等级" fontConfig="contentText" />
-          <Text :text="villageInfoLoader.content.value?.levelText" fontConfig="primaryTitle" />
-        </FlexRow>
-      </FlexRow>
 
-      <FlexRow backgroundColor="background.tertiary" radius="radius.md" :padding="[30, 20]">
-        <FlexCol center gap="gap.sm" flexBasis="25%">
-          <Text text="乡源光" fontConfig="secondText" />
-          <Text :text="villageInfoLoader.content.value?.light" fontConfig="importantTitle" />
-        </FlexCol>
-        <Divider type="vertical" />
-        <FlexCol center gap="gap.sm" flexBasis="25%">
-          <Text text="乡源人数" fontConfig="contentText" />
-          <Text :text="villageInfoLoader.content.value?.memberCount" fontConfig="importantTitle" />
-        </FlexCol>
-        <Divider type="vertical" />
-        <FlexCol center gap="gap.sm" flexBasis="25%">
-          <Text text="关注人数" fontConfig="contentText" />
-          <Text :text="villageInfoLoader.content.value?.followerCount" fontConfig="importantTitle" />
-        </FlexCol>
-        <Divider type="vertical" />
-        <Button :padding="0" type="text" size="small" textColor="text.title" text="新手上路" rightIcon="arrow-right" />
-      </FlexRow>
+        <FlexRow backgroundColor="background.tertiary" radius="radius.md" :padding="[30, 20]">
+          <FlexCol center gap="gap.sm" flexBasis="25%">
+            <Text text="乡源光" fontConfig="secondText" />
+            <Text :text="villageInfoLoader.content.value?.light" fontConfig="importantTitle" />
+          </FlexCol>
+          <Divider type="vertical" />
+          <FlexCol center gap="gap.sm" flexBasis="25%">
+            <Text text="乡源人数" fontConfig="contentText" />
+            <Text :text="villageInfoLoader.content.value?.memberCount" fontConfig="importantTitle" />
+          </FlexCol>
+          <Divider type="vertical" />
+          <FlexCol center gap="gap.sm" flexBasis="25%">
+            <Text text="关注人数" fontConfig="contentText" />
+            <Text :text="villageInfoLoader.content.value?.followerCount" fontConfig="importantTitle" />
+          </FlexCol>
+          <Divider type="vertical" />
+          <Button 
+            :padding="0" 
+            type="text" 
+            size="small" 
+            textColor="text.title"
+            text="新手上路" 
+            rightIcon="arrow-right" 
+            @click="handleGoNew()"
+          />
+        </FlexRow>
+      </Construction>
     </BackgroundBox>
 
     <!-- 排行榜 -->
@@ -143,7 +165,8 @@
           :padding="[15, 20]"
           :innerStyle="{ marginRight: '20rpx' }"
         >
-          <Text text="120乡源果兑换" fontConfig="contentText" color="white" />
+          <Icon name="https://xy.wenlvti.net/app_static/images/village/IconHistory.png" :size="30" />
+          <Text text="共编村史" fontConfig="contentText" color="white" />
         </BackgroundImageButton>
       </template>
     </HomeTitle>
@@ -161,7 +184,7 @@
         <GridItem title="文艺活动" icon="https://xy.wenlvti.net/app_static/images/village/IconLargeActivity.png" touchable @click="handleGoCollect('trip')" />
         <GridItem title="非遗展示" icon="https://xy.wenlvti.net/app_static/images/village/IconLargeShow.png" touchable @click="handleGoCollect('ich')" />
         <GridItem title="民俗风采" icon="https://xy.wenlvti.net/app_static/images/village/IconLargeFolkloreVibe.png" touchable @click="handleGoCollect('custom')" />
-        <GridItem title="文化志愿者" icon="https://xy.wenlvti.net/app_static/images/village/IconLargeVolunteer.png" touchable @click="toast('TODO')" />
+        <GridItem title="乡贤故事" icon="https://xy.wenlvti.net/app_static/images/village/IconGoods.png" touchable @click="toast('TODO')" />
       </Grid>
     </ProvideVar>
 
@@ -228,6 +251,8 @@ import { useSimplePageListLoader } from '@/components/composeabe/loader/SimplePa
 import { useAuthStore } from '@/store/auth';
 import { useSimpleDataLoader } from '@/components/composeabe/loader/SimpleDataLoader';
 import { useVillageStore } from '@/store/village';
+import { useReqireLogin } from '@/common/composeabe/RequireLogin';
+import { useFollow } from '../composeabe/Follow';
 import { confirm, toast } from '@/components/utils/DialogAction';
 import { ArrayUtils } from '@imengyu/imengyu-utils';
 import { navTo } from '@/components/utils/PageAction';
@@ -254,18 +279,22 @@ import IndexCommonImageItem from '@/common/components/parts/IndexCommonImageItem
 import FollowVillageApi from '@/api/light/FollowVillageApi';
 import LightVillageApi from '@/api/light/LightVillageApi';
 import SimplePageListLoader from '@/components/loader/SimplePageListLoader.vue';
-import { useReqireLogin } from '@/common/composeabe/RequireLogin';
+import Construction from '@/common/components/Construction.vue';
+import VillageApi from '@/api/inhert/VillageApi';
 
 const authStore = useAuthStore();
 const { requireLogin } = useReqireLogin();
 const villageStore = useVillageStore();
+const { isFollowed, onFollow, onUnFollow } = useFollow();
 const villageInfoLoader = useSimpleDataLoader(async () => {
   const village = villageStore.currentVillage;  
+  console.log(village);
   return {
     title: village?.name || '',
     desc: village?.desc || '',
     address: village?.address,
     applyCount: village?.applyCount || 0,
+    sizeText: village?.sizeText || 0,
     levelText: village?.levelText as string || '',
     rankText: village?.rankText as string || '',
     light: village?.light as number || 0,
@@ -277,12 +306,6 @@ const villageInfoLoader = useSimpleDataLoader(async () => {
   };
 });
 
-watch(() => villageStore.currentVillage, () => {
-  villageInfoLoader.reload();
-  recommendLoader.reload();
-  villageUserRankListLoader.reload();
-}, { immediate: true });
-
 const rankActiveTag = ref('乡源果');
 const listActiveTag = ref('广场');
 
@@ -311,46 +334,26 @@ watch(rankActiveTag, () => {
   villageUserRankListLoader.reload();
 });
 
-const isFollowed = computed(() => {
-  return villageStore.myFollowVillages.some(p => p.id === villageStore.currentVillage?.id);
+const meIsVolunteer = ref(false);
+const myJoinedVillagesLoader = useSimpleDataLoader(async () => {
+  try {
+    try {
+      await VillageApi.getVolunteerInfo();
+      meIsVolunteer.value = true;
+    } catch (error) {
+      meIsVolunteer.value = false;
+      return [];
+    }
+    return await VillageApi.getClaimedVallageList();
+  } catch (error) {
+    return [];
+  }
 });
+
 const isJoined = computed(() => {
-  return false;
+  return myJoinedVillagesLoader.content.value?.some(p => p.id === villageStore.currentVillage?.id) ?? false;
 });
 
-async function onFollow() {
-  if (!villageStore.currentVillage) 
-    return;
-  requireLogin(async () => {
-    try {
-      await FollowVillageApi.followVillage(villageStore.currentVillage!.id);
-      villageStore.myFollowVillages.push(villageStore.currentVillage!);
-      toast('关注成功');
-    } catch {
-      toast('关注失败');
-    }
-  }, '登录后才能关注村庄哦');
-}
-function onUnFollow() {
-  if (!villageStore.currentVillage) return;
-  confirm({
-    title: '取消关注',
-    content: '确定取消关注该村庄吗?',
-    confirmText: '取消关注',
-    cancelText: '取消',
-  }).then(async (res) => {
-    if (res) {
-      try { 
-        await FollowVillageApi.unfollowVillage(villageStore.currentVillage!.id);
-        villageStore.myFollowVillages = villageStore.myFollowVillages.filter(p => p.id !== villageStore.currentVillage!.id);
-        toast('取消关注成功');
-      } catch {
-        toast('取消关注失败');
-      }
-    }
-  });
-}
-
 const recommendLoader = useSimplePageListLoader(20, async (page, pageSize) => {
   return await LightVillageApi.getMessages(page, pageSize, {
     villageId: villageStore.currentVillage?.id ?? undefined,
@@ -361,6 +364,18 @@ watch(listActiveTag, () => {
   recommendLoader.reload();
 });
 
+function handleGoJoin() {
+  if (isJoined.value)
+    return;
+  navTo('/pages/home/light/submit', {
+    villageId: villageStore.currentVillage?.id ?? undefined,
+    unit: (villageStore.currentVillage?.city as string) + (villageStore.currentVillage?.district as string) + (villageStore.currentVillage?.township as string),
+    regionId: villageStore.currentVillage?.region ?? undefined,
+  });
+}
+function handleGoNew() {
+  navTo('/pages/home/light/help/new');
+}
 function handleGoCollect(taskName: string) {
   navTo('/pages/dig/forms/task', {
     villageId: villageStore.currentVillage?.id ?? undefined,
@@ -391,4 +406,10 @@ function handleGoPost(id: number) {
     id: id,
   });
 }
+
+watch(() => villageStore.currentVillage, () => {
+  villageInfoLoader.reload();
+  recommendLoader.reload();
+  villageUserRankListLoader.reload();
+}, { immediate: true });
 </script>

+ 6 - 1
src/pages/index.vue

@@ -38,7 +38,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue';
+import { onMounted, ref } from 'vue';
 import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app';
 import { useTheme } from '@/components/theme/ThemeDefine';
 import StatusBarSpace from '@/components/layout/space/StatusBarSpace.vue';
@@ -51,6 +51,7 @@ import HomeIndex from './home/index.vue';
 import DiscoverIndex from './home/discover/index.vue';
 import VillageIndex from './home/village/index.vue';
 import CommonRoot from '@/components/dialog/CommonRoot.vue';
+import { isDevEnv } from '@/common/config/AppCofig';
 
 const tabIndex = ref(0);
 const themeContext = useTheme();
@@ -76,4 +77,8 @@ onShareTimeline(() => {
     imageUrl: 'https://mn.wenlvti.net/app_static/xiangyuan/images/index-post.jpg',
   }
 })
+onMounted(() => {
+  if (isDevEnv)
+    tabIndex.value = 1;
+})
 </script>