Explorar o código

📦 村落页细节优化

快乐的梦鱼 hai 3 semanas
pai
achega
ca2b8600c4

+ 35 - 2
src/components/feedback/BubbleBox.vue

@@ -12,6 +12,7 @@
     <SimpleTransition name="bubble-box" :show="showState" :duration="200">
       <template #show="{ classNames }">
         <view class="nana-bubble-box-popup-mask" @click="hide" />
+        <view class="nana-bubble-box-holder-position" @click="hideAndEmitClickOnHolder" />
         <FlexView
           position="absolute"
           :direction="direction"
@@ -34,6 +35,8 @@
           <view 
             class="nana-bubble-box-arrow" 
             :style="{ 
+              marginTop: theme.resolveThemeSize(arrowOffsetY),
+              marginLeft: theme.resolveThemeSize(arrowOffsetX),
               borderWidth: theme.resolveThemeSize(arrowWidth),
               borderColor: backgroundColor,
               borderRightColor: 'transparent',
@@ -153,6 +156,16 @@ export interface BubbleBoxProps {
    */
   arrowWidth?: number,
   /**
+   * 气泡框箭头X轴偏移
+   * @default 0
+   */
+  arrowOffsetX?: number|string,
+  /**
+   * 气泡框箭头X轴偏移
+   * @default 0
+   */
+  arrowOffsetY?: number|string,
+  /**
    * 气泡框圆角半径
    * @default 12
    */
@@ -182,6 +195,8 @@ const props = withDefaults(defineProps<BubbleBoxProps>(), {
   radius: () => propGetThemeVar('BubbleBoxRadius', 12),
 });
 
+const emit = defineEmits(['show', 'hide', 'clickOnHolder']);
+
 const backgroundColor = computed(() => theme.resolveThemeColor(props.backgroundColor));
 const showState = ref(false);
 const lock = ref(false);
@@ -193,16 +208,20 @@ function handleItemClick(item: BubbleBoxItem) {
 function handleClick() {
   if (lock.value) return;
   if (props.trigger === 'click' && !props.disabled)  {
-     enterLock();
+    enterLock();
     showState.value = !showState.value;
+    emit(showState.value ? 'show' : 'hide');
   }
 }
 function handleHover(show: boolean) {
   if (lock.value) return;
-  if (props.trigger === 'hover' && !props.disabled)
+  if (props.trigger === 'hover' && !props.disabled) {
     showState.value = show;
+    emit(show ? 'show' : 'hide');
+  }
 }
 
+
 function enterLock() {
   lock.value = true;
   setTimeout(() => {
@@ -211,12 +230,18 @@ function enterLock() {
 }
 function show() { 
   showState.value = true;
+  emit('show');
   enterLock();
 }
 function hide() { 
   showState.value = false; 
+  emit('hide');
   enterLock();
 }
+function hideAndEmitClickOnHolder() {
+  hide();
+  emit('clickOnHolder');
+}
 
 const innerStyle = computed(() => {
   const horzLayout = selectStyleType(props.crossPosition, 'center', {
@@ -340,6 +365,14 @@ defineOptions({
       opacity: 0;
     }
   }
+  .nana-bubble-box-holder-position {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    z-index: 1001;
+  }
   .nana-bubble-box-popup-mask {
     position: fixed;
     top: 0;

+ 78 - 0
src/components/feedback/BubbleTip.vue

@@ -0,0 +1,78 @@
+<template>
+  <BubbleBox 
+    ref="followBubbleBoxRef" 
+    v-bind="props"
+    @clickOnHolder="emit('contentClick');hideTip()"
+  >
+    <template #content>
+      <FlexRow justify="space-between" align="center">
+        <Text :text="content" v-bind="contentTextProps" :wrap="false" touchable @click="emit('contentClick');hideTip()" />
+        <IconButton v-if="closeButton" v-bind="closeButtonProps" :icon="closeButton" @click="emit('close');hideTip()" />
+      </FlexRow>
+    </template>
+    <slot />
+  </BubbleBox>
+</template>
+
+<script setup lang="ts">
+import Text, { type TextProps } from '../basic/Text.vue';
+import BubbleBox, { type BubbleBoxProps } from './BubbleBox.vue';
+import FlexRow from '../layout/FlexRow.vue';
+import IconButton, { type IconButtonProps } from '../basic/IconButton.vue';
+import { onMounted, ref, watch } from 'vue';
+
+export interface BubbleTiProps extends BubbleBoxProps {
+  show?: boolean;
+  content?: string;
+  contentTextProps?: TextProps;
+  closeButton?: string;
+  closeButtonProps?: IconButtonProps;
+}
+
+const props = withDefaults(defineProps<BubbleTiProps>(), {
+  content: '',
+  contentTextProps: () => ({
+    color: 'white',
+    fontConfig: 'contentText',
+  }),
+  closeButton: 'close',
+  closeButtonProps: () => ({
+    size: 26,
+    color: 'white',
+  }),
+  backgroundColor: 'rgba(0,0,0,0.9)',
+  radius: 10,
+});
+
+const emit = defineEmits(['contentClick', 'close', 'update:show']);
+
+const followBubbleBoxRef = ref<InstanceType<typeof BubbleBox>>();
+
+watch(() => props.show, (newVal) => {
+  if (newVal)
+    followBubbleBoxRef.value?.show();
+  else 
+    followBubbleBoxRef.value?.hide();
+});
+
+function hideTip() {
+  emit('update:show', false);
+}
+
+onMounted(() => {
+  setTimeout(() => {
+    if (props.show)
+      followBubbleBoxRef.value?.show();
+  }, 300);
+});
+
+defineOptions({
+  options: {
+    virtualHost: true,
+    styleIsolation: "shared",
+  }
+})
+</script>
+
+<style lang="scss">
+</style>

+ 7 - 3
src/pages/dig/forms/list-ordinary.vue

@@ -352,9 +352,13 @@ const { querys } = useLoadQuerys({
   villageId: 0,  
   title: '',
 }, async (querys) => {
-  //普通用户进入预览模式
-  await getIsVolunteer();
-  canCollect.value = await getCanCollect(querys.villageId);
+  try {
+    //普通用户进入预览模式
+    await getIsVolunteer();
+    canCollect.value = await getCanCollect(querys.villageId);
+  } catch {
+    canCollect.value = false;
+  }
 
   function pushCatalogWithCurrentCatalog(catalog: VillageCatalogListItem) {
     if (catalog.collectModuleId === querys.collectModuleId) {

+ 2 - 2
src/pages/home/dig.vue

@@ -23,11 +23,11 @@
         <CommonDivider />
         <Text 
           textAlign="center"
-          text="认领属于你的村,参与建设家乡 或者先随便看看,了解更多乡村故事" 
+          text="认领属于你的村,参与建设家乡 或者先随便看看,了解更多乡村故事" 
           fontConfig="contentSpeicalText" 
         />
         <FlexRow justify="space-around" gap="gap.md">
-          <FrameButton primary text="去认领村庄" @click="navTo('/pages/home/light/submit-map', {
+          <FrameButton primary text="去认领" @click="navTo('/pages/home/light/submit-map', {
             city: currentCity,
           })" width="220rpx" />
         </FlexRow>

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

@@ -10,7 +10,7 @@
             <FlexCol center :gap="20">
               <Icon name="smile-filling" :size="80" />
               <Text 
-                text="感谢您为本村做出贡献,点亮村!不过在这之前您需要注册基本信息成为志愿者,这可以帮助您更好的为村做出贡献,领取奖励等。请完善以下信息" 
+                text="感谢您为本村做出贡献,点亮村!不过在这之前您需要注册基本信息成为志愿者,这可以帮助您更好的为村做出贡献,领取奖励等。请完善以下信息" 
                 textAlign="center" 
                 fontConfig="contentSpeicalText" 
               />

+ 2 - 2
src/pages/home/village/composeabe/Follow.ts

@@ -23,13 +23,13 @@ export function useFollow() {
       } catch {
         toast('关注失败');
       }
-    }, '登录后才能关注村哦');
+    }, '登录后才能关注村哦');
   }
   function onUnFollow() {
     if (!villageStore.currentVillage) return;
     confirm({
       title: '取消关注',
-      content: '确定取消关注该村吗?',
+      content: '确定取消关注该村吗?',
       confirmText: '取消关注',
       cancelText: '取消',
     }).then(async (res) => {

+ 2 - 2
src/pages/home/village/dialogs/IntroClamTip.vue

@@ -8,12 +8,12 @@
     <FlexCol gap="gap.lg" padding="padding.md">
       <Text 
         textAlign="center"
-        text="认领属于你的村庄,参与建设家乡 或者先随便看看,了解更多乡村故事" 
+        text="认领你的村社,参与建设家乡 或者先随便看看,了解更多乡村故事" 
         fontConfig="contentSpeicalText" 
       />
       <FlexRow justify="space-around" gap="gap.md">
         <FrameButton text="随便看看" @click="show = false; emit('cancel')" width="220rpx" />
-        <FrameButton primary text="认领村" @click="show = false; emit('apply')" width="220rpx" />
+        <FrameButton primary text="认领村" @click="show = false; emit('apply')" width="220rpx" />
       </FlexRow>
     </FlexCol>
   </CommonDialog>

+ 1 - 1
src/pages/home/village/dialogs/PostIndex.vue

@@ -57,7 +57,7 @@ const postList = [
   {
     image: 'https://xy.wenlvti.net/app_static/images/village/IconLargeWrite.png',
     name: '挖掘采编',
-    desc: '如果您是专业文史志愿者,可以在这里编写、挖掘村的历史,让家乡文化被看见、被记住、被传承',
+    desc: '如果您是专业文史志愿者,可以在这里编写、挖掘村的历史,让家乡文化被看见、被记住、被传承',
     onClick: () => {
       show.value = false;
       emit('goDig');

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

@@ -154,7 +154,7 @@ defineExpose({
   onPageBack: (name: string, data: Record<string, unknown>) => {
     if (data.needRefresh) {
       galleryLoader.reload();
-      callPrevOnPageBack('refreshVillageGrallery', { needRefresh: true }, -1);
+      callPrevOnPageBack('refreshVillageGrallery', { needRefresh: true }, 1);
     }
   },
 });

+ 38 - 10
src/pages/home/village/index.vue

@@ -3,14 +3,24 @@
     <FlexCol v-if="showSwitch" position="absolute" :left="0" :top="0" :right="0" backgroundColor="rgba(255,255,255,0.5)">
       <StatusBarSpace />
       <FlexRow gap="gap.md">
-        <Button
-          icon="https://xy.wenlvti.net/app_static/images/home/IconSwitch.png"
-          :text="villageStore.currentVillage?.name || '未选择村庄'"
-          :textColor="topTab === 'village' ? 'text.titleLight' : 'text.content'"
-          color="transparent"
-          type="custom" 
-          @click="showMyFollowPopup = true"
-        />
+        <BubbleTip
+          v-model:show="showFollowTip"
+          position="bottom"
+          crossPosition="left"
+          arrowOffsetX="-50rpx"
+          content="点此可以查看我关注的村社"
+          @contentClick="showMyFollowPopup = true;handleCloseFollowTip()"
+          @close="handleCloseFollowTip"
+        >
+          <Button
+            icon="https://xy.wenlvti.net/app_static/images/home/IconSwitch.png"
+            :text="villageStore.currentVillage?.name || '未选择村社'"
+            :textColor="topTab === 'village' ? 'text.titleLight' : 'text.content'"
+            color="transparent"
+            type="custom" 
+            @click="showMyFollowPopup = true"
+          />
+        </BubbleTip>
         <Button 
           type="custom" 
           color="transparent" 
@@ -24,10 +34,10 @@
       <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" />
+        <Text text="等你的加入,快来为家乡建设做贡献吧" fontConfig="contentText" />
         <Height :height="20" />
         <FlexRow gap="gap.lg">
-          <FrameButton :text="isFollowed ? '已关注' : '先关注村'" @click="isFollowed ? onUnFollow() : onFollow()" />
+          <FrameButton :text="isFollowed ? '已关注' : '先关注村'" @click="isFollowed ? onUnFollow() : onFollow()" />
           <FrameButton text="去申请点亮" @click="handleLight" primary />
         </FlexRow>
       </FlexCol>
@@ -114,6 +124,8 @@ import HomeLargeTitle from '@/common/components/parts/HomeLargeTitle.vue';
 import Around from './recommed/around.vue';
 import FrameButton from '@/common/components/FrameButton.vue';
 import JoinDialog from './dialogs/JoinDialog.vue';
+import BubbleTip from '@/components/feedback/BubbleTip.vue';
+import MemoryTimeOut from '@/components/composeabe/MemoryTimeOut';
 
 const topTab = ref<'village' | 'around'>('village');
 const tab = ref('card');
@@ -137,6 +149,18 @@ const isLight = computed(() => {
   return villageStore.currentVillage?.isLight ?? false;
 });
 
+const showFollowTipTimeout = new MemoryTimeOut('FollowTip', 1000 * 3600 * 2);//2h
+const showFollowTip = ref(false);
+
+function handleShowFollowTip() {
+  if (isFollowed.value && showFollowTipTimeout.isTimeout()) {
+    showFollowTip.value = true;
+  }
+}
+function handleCloseFollowTip() {
+  showFollowTipTimeout.recordTime();
+}
+
 function handleLight() {
   joinDialog.value?.show();
 }
@@ -154,6 +178,10 @@ watch(() => villageStore.currentVillage, (newVal) => {
   if (newVal && topTab.value === 'around')
     topTab.value = 'village';
   tab.value = 'card';
+  handleShowFollowTip();
+});
+watch(() => isFollowed.value, (newVal) => {
+  handleShowFollowTip();
 });
 
 onMounted(async () => {

+ 32 - 7
src/pages/home/village/introd/card.vue

@@ -19,13 +19,20 @@
           <Text :text="villageInfoLoader.content.value?.title" fontConfig="primaryTitle" />
         </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()"
+          <BubbleTip
+            v-model:show="showFollowTip"
+            content="关注我,方便下次进入"
+            @contentClick="isFollowed ? undefined : onFollow()"
+            @close="handleCloseFollowTip"
           >
-            {{ isFollowed ? '已关注' : '关注' }}
-          </Button>
+            <Button 
+              icon="https://xy.wenlvti.net/app_static/images/village/IconJoin.png" 
+              radius="radius.lgr"
+              @click="isFollowed ? onUnFollow() : onFollow()"
+            >
+              {{ isFollowed ? '已关注' : '关注' }}
+            </Button>
+          </BubbleTip>
           <Button 
             icon="https://xy.wenlvti.net/app_static/images/village/IconFollow.png"
             radius="radius.lgr"
@@ -272,6 +279,9 @@ import WxButton from '@/components/basic/WxButton.vue';
 import Height from '@/components/layout/space/Height.vue';
 import IconButton from '@/components/basic/IconButton.vue';
 import Button from '@/components/basic/Button.vue';
+import BubbleBox from '@/components/feedback/BubbleBox.vue';
+import BubbleTip from '@/components/feedback/BubbleTip.vue';
+import MemoryTimeOut from '@/components/composeabe/MemoryTimeOut';
 
 const authStore = useAuthStore();
 const { getIsVolunteer } = useUserTools();
@@ -289,6 +299,7 @@ const villageInfoLoader = useSimpleDataLoader(async () => {
   } catch {
     isJoined.value = false;
   }
+  handleShowFollowTip();
   return {
     title: village?.name || '',
     desc: village?.desc || '',
@@ -335,6 +346,19 @@ const villageUserRankListLoader = useSimpleDataLoader(async () => {
   return res
 });
 
+const showFollowTipTimeout = new MemoryTimeOut('FollowTip', 1000 * 3600);//1h
+const showFollowTip = ref(false);
+
+function handleShowFollowTip() {
+  if (!isFollowed.value && showFollowTipTimeout.isTimeout()) {
+    showFollowTip.value = true;
+  }
+}
+function handleCloseFollowTip() {
+  showFollowTipTimeout.recordTime();
+}
+
+
 watch(rankActiveTag, () => {
   villageUserRankListLoader.reload();
 });
@@ -394,8 +418,9 @@ watch(() => villageStore.currentVillage, () => {
 
 defineExpose({
   onPageBack: (name: string, data: Record<string, unknown>) => {
-    if (name === 'refreshVillageGrallery' && data.needRefresh) {
+    if (name === 'refreshVillageGrallery') {
       villageGalleryRef.value?.refresh();
+
     } else if (name === 'showVillageUpgrade') {
       upgradeRef.value?.show();
     }

+ 2 - 2
src/pages/home/village/volunteer/detail.vue

@@ -22,7 +22,7 @@
               <Tag :text="infoLoader.content.value.level + '级'" />
             </FlexRow>
             <FlexRow align="center" gap="gap.md">
-              <Text text="加入村" fontConfig="contentText" />
+              <Text text="加入村" fontConfig="contentText" />
               <Tag :text="(infoLoader.content.value.villageName as string)" />
             </FlexRow>
             <Divider />
@@ -122,7 +122,7 @@ const infoGrids = computed(() => {
     },
     {
       label: '文化积分',
-      unit: '/',
+      unit: '/',
       value: infoLoader.content.value?.points as number || 0,
       logo: 'https://xy.wenlvti.net/app_static/images/home/volunteer/IconLikes.png',
     },