index.vue 13.1 KB
<template>
  <view class="min-h-screen flex flex-col bg-white pb-16" style="background-color: #F9FAFB;">
    <!-- Hero section with family name and background image -->
    <view class=" bg-white rounded-xl shadow-md mx-4 mt-4 relative" style="height: 30vh;">
      <image :src="familyCover" mode="aspectFill" alt="Family background" class="w-full h-full object-cover rounded-xl" />
      <view class="absolute inset-0 flex flex-col justify-end rounded-xl bg-black bg-opacity-20">
        <view class="m-5">
          <view v-if="familyOwner" class="absolute top-4 right-4 text-white flex items-center" @click="goToProfile">
            <Setting size="24" />
            <text class="ml-2">家庭设置</text>
          </view>
          <h1 class="text-white text-2xl font-bold">{{ familyName }}</h1>
          <p class="text-white opacity-90">{{ familySlogn }}</p>
        </view>
      </view>
    </view>

    <!-- 微信步数授权组件 -->
    <WeRunAuth
      ref="weRunAuthRef"
      :auto-check="false"
      @auth-change="handleAuthChange"
      @steps-synced="handleStepsSynced"
      @sync-failed="handleSyncFailed"
    >
      <template #default>
        <!-- 合并的大卡片:今日步数 + 积分收集器 + 拍照留念 -->
        <template v-if="!showTotalPointsOnly">
          <PointsCollector
            ref="pointsCollectorRef"
            height="30vh"
            :total-points="finalTotalPoints"
            :pending-points="pendingPoints"
            :family-id="family_id"
            :is-owner="familyOwner"
            @collection-complete="handleCollectionComplete"
          >
            <!-- 头部:今日步数 -->
            <template #header>
              <view class="flex justify-between items-center mb-1 relative">
                <span class="text-gray-500">今日</span>
                <!-- 积分规则提示 -->
                <view class="points-rule-tip" @tap="handleGoToPointsRule">
                  <!-- <view class="tip-icon">💡</view> -->
                  <text class="tip-text">积分攻略</text>
                </view>
              </view>
              <view class="flex justify-between items-center">
                <view class="flex items-baseline">
                  <span class="text-4xl font-bold">
                    {{ todaySteps?.toLocaleString() }}
                  </span>
                  <span class="ml-1 text-gray-500">步</span>
                </view>
                <view class="flex gap-2">
                  <view class="bg-blue-500 text-white px-4 py-2 rounded-full text-sm" @click="handleCollectAll">
                    一键收取
                  </view>
                </view>
              </view>
            </template>

            <!-- 底部:拍照留念 -->
            <template #footer>
              <view @tap="openCamera" class="w-full bg-blue-500 text-white py-3 rounded-lg flex flex-col items-center justify-center">
                <view class="flex items-center justify-center">
                  <Photograph size="20" class="mr-2" />
                  拍照留念,奖励积分
                </view>
              </view>
            </template>
          </PointsCollector>
        </template>
        <template v-else>
          <TotalPointsDisplay
            :total-points="finalTotalPoints"
            :is-owner="familyOwner"
            height="20vh"
          >
            <!-- 头部:今日步数 -->
            <template #header>
              <view class="flex justify-between items-center mb-1 relative">
                <span class="text-gray-500">今日</span>
                <!-- 积分规则提示 -->
                <view class="points-rule-tip" @tap="handleGoToPointsRule">
                  <!-- <view class="tip-icon">💡</view> -->
                  <text class="tip-text">积分攻略</text>
                </view>
              </view>
              <view class="flex justify-between items-center">
                <view class="flex items-baseline">
                  <span class="text-4xl font-bold">
                    {{ todaySteps?.toLocaleString() }}
                  </span>
                  <span class="ml-1 text-gray-500">步</span>
                </view>
                <view class="flex gap-2">
                  <view class="bg-blue-500 text-white px-4 py-2 rounded-full text-sm" @click="handleCollectAll">
                    一键收取
                  </view>
                </view>
              </view>
            </template>

            <!-- 底部:拍照留念 -->
            <template #footer>
              <view @tap="openCamera" class="w-full bg-blue-500 text-white py-3 rounded-lg flex flex-col items-center justify-center">
                <view class="flex items-center justify-center">
                  <Photograph size="20" class="mr-2" />
                  拍照留念,奖励积分
                </view>
              </view>
            </template>
          </TotalPointsDisplay>
        </template>

        <!-- Family step ranking -->
        <view class="p-5 bg-white rounded-xl shadow-md mx-4">
          <view class="flex justify-between items-center mb-4">
            <h2 class="font-medium text-lg">今日家庭步数排行</h2>
            <span class="text-sm text-gray-500">
              总计 {{ getTotalSteps() }} 步
            </span>
          </view>
          <view class="grid grid-cols-4 gap-2">
            <view v-for="member in familyMembers" :key="member.user_id" class="flex flex-col items-center">
              <image :src="member.avatar_url || defaultAvatar" mode="aspectFill" :alt="member.role" class="w-16 h-16 rounded-full mb-1" />
              <span class="text-sm text-gray-700">
                {{ member?.today_step?.toLocaleString() }}步
              </span>
            </view>
          </view>
        </view>
      </template>
    </WeRunAuth>

    <!-- 排行榜卡片 -->
    <RankingCard ref="rankingCardRef" :onViewMore="openFamilyRank" />

    <!-- Family album -->
    <FamilyAlbum />

    <BottomNav />

    <!-- 广告遮罩层 -->
    <AdOverlay
      :ad-image-url="adObj.adImageUrl"
      :target-page="adObj.targetPage"
      :storage-key="adObj.storageKey"
      @close="handleAdClose"
      @click="handleAdClick"
    />
  </view>
</template>

<script setup>
import "./index.less";
import { ref, computed, onMounted } from 'vue';
import Taro, { useDidShow, useReady } from '@tarojs/taro';
import { Setting, Photograph, Category } from '@nutui/icons-vue-taro';
import BottomNav from '../../components/BottomNav.vue';
import TotalPointsDisplay from '@/components/TotalPointsDisplay.vue';
import PointsCollector from '@/components/PointsCollector.vue'
import WeRunAuth from '@/components/WeRunAuth.vue'
import RankingCard from '@/components/RankingCard.vue'
import AdOverlay from '@/components/AdOverlay.vue'
import FamilyAlbum from '@/components/FamilyAlbum.vue';
// 默认家庭封面图
const defaultFamilyCover = 'https://cdn.ipadbiz.cn/lls_prog/images/default-family-cover.png';
// 默认头像
const defaultAvatar = 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
// 接口信息
import { getFamilyDashboardAPI } from '@/api/family'

const todaySteps = ref(0);
const isWeRunAuthorized = ref(false);
const pointsCollectorRef = ref(null)
const weRunAuthRef = ref(null)
const rankingCardRef = ref(null)
const showTotalPointsOnly = ref(false)
const finalTotalPoints = ref(0)
const pendingPoints = ref([]) // 待收集的积分数据

const familyName = ref('')
const familySlogn = ref('')
const familyCover = ref('')
const familyOwner = ref(false);

// 广告遮罩层数据
const adObj = ref({})

/**
 * 触发积分收集组件的一键收取
 */
const handleCollectAll = () => {
  if (pointsCollectorRef.value) {
    pointsCollectorRef.value.collectAll()
  }
}

/**
 * 处理积分收集完成事件
 * @param {number} points - 最终的积分
 */
const handleCollectionComplete = (points) => {
  finalTotalPoints.value = points;
  showTotalPointsOnly.value = true;
};

/**
 * 处理微信步数授权状态变化
 * @param {boolean} authorized - 授权状态
 */
const handleAuthChange = (authorized) => {
  isWeRunAuthorized.value = authorized
  console.log('微信步数授权状态:', authorized)
}

/**
 * 处理微信步数同步完成事件
 */
const handleStepsSynced = async () => {
  console.log('微信步数同步完成')
  // 步数同步完成后,重新获取家庭数据
  const { code, data } = await getFamilyDashboardAPI();
  if (code) {
    // 更新页面数据
    pendingPoints.value = data.pending_points || [];
    familyMembers.value = data.step_ranking || [];
    showTotalPointsOnly.value = !data.pending_points?.length;
    finalTotalPoints.value = data.family.total_points || 0;
    familyName.value = data.family.name;
    familySlogn.value = data.family.note;
    familyCover.value = data.family.avatar_url || defaultFamilyCover;
    familyOwner.value = data.family.is_my;
    todaySteps.value = data.my_today_step;
    totalFamilySteps.value = data.family_today_step;
  }
}

/**
 * 处理微信步数同步失败事件
 * @param {Object} data - 失败返回的数据
 */
const handleSyncFailed = (data) => {
  console.log('微信步数同步失败:', data)
}

/**
 * 计算总步数(包含用户步数和家庭成员步数)
 * @returns {string} 格式化后的总步数
 */
const getTotalSteps = () => {
  return familyMembers.value.reduce((sum, member) => sum + member.today_step, 0).toLocaleString()
}

const familyMembers = ref([]);

const goToProfile = () => {
  Taro.navigateTo({ url: '/pages/EditFamily/index' });
};

/**
 * 打开拍照上传页面
 */
const openCamera = () => {
  Taro.navigateTo({ url: '/pages/UploadMedia/index' });
}

/**
 * 打开活动排行榜
 */
const openFamilyRank = () => {
  Taro.navigateTo({ url: '/pages/FamilyRank/index' });
}

/**
 * 处理积分规则点击事件
 */
const handleGoToPointsRule = () => {
  Taro.navigateTo({
    url: '/pages/PointsList/index'
  })
}

/**
 * 处理广告遮罩层关闭事件
 */
const handleAdClose = () => {
  console.log('广告遮罩层已关闭')
}

/**
 * 处理广告遮罩层点击事件
 * @param {string} targetPage - 跳转的目标页面
 */
const handleAdClick = (targetPage) => {
  console.log('广告被点击,跳转到:', targetPage)
  // 如果跳转路径是欢迎页和首页,不跳转直接关闭弹框
  if (targetPage === '/pages/Dashboard/index' || targetPage === '/pages/Welcome/index') {
    handleAdClose()
    return
  }
  // 跳转到目标页面
  Taro.navigateTo({ url: targetPage });
}

const family_id = ref('');
const totalFamilySteps = ref(0);

useDidShow(async () => {
  // 直接获取家庭首页数据,同时判断是否加入家庭
  const { code, data } = await getFamilyDashboardAPI();
  if (code) {
    // 获取家庭ID
    family_id.value = data.family.id;

    // 设置页面数据(数据已经在getFamilyDashboardAPI中获取到了)
    pendingPoints.value = data.pending_points || [];
    familyMembers.value = data.step_ranking || [];
    showTotalPointsOnly.value = !data.pending_points?.length;
    finalTotalPoints.value = data.family.total_points || 0;
    familyName.value = data.family.name;
    familySlogn.value = data.family.note;
    familyCover.value = data.family.avatar_url || defaultFamilyCover;
    familyOwner.value = data.family.is_my;
    todaySteps.value = data.my_today_step;
    totalFamilySteps.value = data.family_today_step;

    // 正常刷新微信步数数据
    if (weRunAuthRef.value) {
      weRunAuthRef.value.checkAuthStatus(true);
    }

    // 刷新排行榜数据
    if (rankingCardRef.value) {
      rankingCardRef.value.refreshData();
    }
  } else {
    // 如果没有加入家庭(code为0),跳转到欢迎页面
    Taro.redirectTo({ url: '/pages/Welcome/index' });
    return; // 直接返回,不执行后续逻辑
  }

  // TODO: 获取广告信息
  adObj.value = {
    adImageUrl: 'https://cdn.ipadbiz.cn/lls_prog/images/%E5%8D%97%E4%BA%AC%E8%B7%AF%E5%95%86%E5%9C%88.jpeg',
    targetPage: '/pages/Dashboard/index',
    storageKey: 'dashboard_ad_overlay',
  }
})

useReady(async () => {
  // 版本更新检查
  if (!Taro.canIUse("getUpdateManager")) {
    Taro.showModal({
      title: "提示",
      content: "当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试",
      showCancel: false,
    });
    return;
  }

  // https://developers.weixin.qq.com/miniprogram/dev/api/base/update/UpdateManager.html
  const updateManager = Taro.getUpdateManager();

  updateManager.onCheckForUpdate((res) => {
    // 请求完新版本信息的回调
    if (res.hasUpdate) {
      updateManager.onUpdateReady(function () {
        Taro.showModal({
          title: "更新提示",
          content: "新版本已经准备好,是否重启应用?",
          success: function (res) {
            if (res.confirm) {
              // 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
              updateManager.applyUpdate();
            }
          },
        });
      });

      updateManager.onUpdateFailed(function () {
        // 新版本下载失败
        Taro.showModal({
          title: "更新提示",
          content: "新版本已上线,请删除当前小程序,重新搜索打开",
        });
      });
    }
  });
})
</script>