ProfilePage.vue 10.1 KB
<template>
  <AppLayout :right-content="rightContent">
    <div class="min-h-screen bg-gradient-to-br from-green-50 via-green-100/30 to-blue-50/30">
      <!-- User Profile Header with Enhanced Design -->
      <div class="relative pb-8 pt-6">
        <div class="absolute inset-0 bg-gradient-to-r from-green-500 to-blue-500 opacity-15"></div>
        <div class="relative z-10 flex flex-col items-center">
          <div class="mb-4 h-24 w-24 overflow-hidden rounded-full border-4 border-white shadow-lg">
            <img
              :src="profile.avatar || 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'"
              :alt="profile.name"
              class="h-full w-full object-cover"
              @error="handleImageError"
            />
          </div>
          <h2 class="mb-1 text-2xl font-bold">{{ profile.name || '匿名用户' }}</h2>
          <!-- <div class="flex items-center text-sm text-gray-600">
            <span>会员等级: 普通会员</span>
            <span class="mx-2">|</span>
            <span>ID: {{ profile.id }}</span>
          </div> -->
        </div>
      </div>

      <!-- Check-in Statistics -->
      <div class="mb-5 px-4 pt-4">
        <FrostedGlass class="rounded-xl p-4">
          <div class="mb-4 flex items-center justify-between">
            <h3 class="text-base font-semibold">打卡统计</h3>
            <!-- <span class="text-xs text-blue-500">查看更多</span> -->
          </div>

          <div class="grid items-center gap-2" style="grid-template-columns: 1fr 1fr 1fr auto">
            <div class="flex min-w-0 flex-col items-center">
              <div
                class="flex w-full items-baseline justify-center truncate px-0.5 text-center text-xl font-bold text-gray-800"
              >
                <span>{{ formatCheckInCount(checkIns?.total_days) }}</span>
                <span class="ml-1 text-xs font-normal">天</span>
              </div>
              <div class="mt-1 w-full truncate text-center text-xs text-gray-500">累计打卡</div>
            </div>
            <div class="flex min-w-0 flex-col items-center">
              <div
                class="flex w-full items-baseline justify-center truncate px-0.5 text-center text-xl font-bold text-green-600"
              >
                <span>{{ formatCheckInCount(checkIns?.consecutive_days) }}</span>
                <span class="ml-1 text-xs font-normal">天</span>
              </div>
              <div class="mt-1 w-full truncate text-center text-xs text-gray-500">连续打卡</div>
            </div>
            <div class="flex min-w-0 flex-col items-center">
              <div
                class="flex w-full items-baseline justify-center truncate px-0.5 text-center text-xl font-bold text-blue-600"
              >
                <span>{{ formatCheckInCount(checkIns?.longest_consecutive_days) }}</span>
                <span class="ml-1 text-xs font-normal">天</span>
              </div>
              <div class="mt-1 w-full truncate text-center text-xs text-gray-500">最长连续</div>
            </div>

            <div>
              <div @click="handleCheckin" class="cursor-pointer">
                <button
                  class="whitespace-nowrap rounded-full bg-gradient-to-r from-green-500 to-green-600 px-3 py-2 text-sm text-white shadow-sm"
                >
                  立即打卡
                </button>
              </div>
            </div>
          </div>
        </FrostedGlass>
      </div>

      <!-- User Menu Options -->
      <div class="px-4 pb-16">
        <FrostedGlass class="mb-5 overflow-hidden rounded-xl">
          <MenuItem
            v-for="item in menuItems1"
            :key="item.path"
            v-bind="item"
            @click="handleMenuClick(item.path)"
          />
        </FrostedGlass>

        <FrostedGlass class="mb-5 overflow-hidden rounded-xl">
          <MenuItem
            v-for="item in menuItems2"
            :key="item.path"
            v-bind="item"
            @click="handleMenuClick(item.path)"
          />
        </FrostedGlass>

        <FrostedGlass v-if="isTeacher" class="mb-5 overflow-hidden rounded-xl">
          <MenuItem
            v-for="item in menuItems4"
            :key="item.path"
            v-bind="item"
            @click="handleMenuClick(item.path)"
          />
        </FrostedGlass>

        <FrostedGlass class="mb-5 overflow-hidden rounded-xl">
          <MenuItem
            v-for="item in menuItems3"
            :key="item.path"
            v-bind="item"
            @click="handleMenuClick(item.path)"
          />
        </FrostedGlass>

        <!-- Version Info -->
        <div class="mb-4 text-center text-xs text-gray-400">生命力教育联盟教育</div>

        <!-- Logout Button -->
        <!-- <button
          @click="handleLogout"
          class="w-full bg-white/70 backdrop-blur-sm text-red-500 py-3 rounded-xl mb-6 font-medium shadow-sm"
        >
          退出登录
        </button> -->
      </div>
    </div>
  </AppLayout>

  <CheckInDialog
    v-model:show="showCheckInDialog"
    @check-in-success="handleCheckInSuccess"
    @check-in-data="handleCheckInData"
  />
</template>

<script setup>
import { ref, h } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import AppLayout from '@/components/layout/AppLayout.vue'
import FrostedGlass from '@/components/effects/FrostedGlass.vue'
import MenuItem from '@/components/common/MenuItem.vue'
import { useAuth } from '@/contexts/auth'
import CheckInDialog from '@/components/checkin/CheckInDialog.vue'
import { getUserInfoAPI } from '@/api/users'
import { showToast } from 'vant'
import { useTitle } from '@vueuse/core'
import { useImageLoader } from '@/composables/useImageLoader'
import { useUserInfo } from '@/composables/useUserInfo'

const router = useRouter()
const $route = useRoute()
useTitle($route.meta.title)

// 图片加载错误处理
const { handleImageError } = useImageLoader()

// 用户信息获取
const { userInfo, refreshUserInfo } = useUserInfo()

const profile = ref({})
const checkIns = ref({})
const isTeacher = ref(false)

const formatCheckInCount = count => {
  const num = Number(count) || 0
  return num > 99 ? '99+' : num
}

onMounted(async () => {
  const userData = await refreshUserInfo()
  if (userData) {
    profile.value = userData
    checkIns.value = {
      total_days: userData.total_days || 0,
      consecutive_days: userData.consecutive_days || 0,
      longest_consecutive_days: userData.longest_consecutive_days || 0,
    }
    isTeacher.value = userData.is_teacher
    // 处理消息中心的未读消息数量
    menuItems2.value.forEach(item => {
      if (item.path === '/profile/messages') {
        item.badge = +(userData?.unread_msg_count || 0)
      }
    })
  }
})

const showCheckInDialog = ref(false)

// 处理打卡成功
const handleCheckInSuccess = () => {
  checkIns.value.total_days++
  checkIns.value.consecutive_days++
  checkIns.value.longest_consecutive_days = Math.max(
    checkIns.value.longest_consecutive_days,
    checkIns.value.consecutive_days
  )
}

const checkinData = ref([])
const handleCheckInData = data => {
  checkinData.value = data
}

// Handle logout
const { logout } = useAuth()
const handleLogout = () => {
  logout()
  // window.location.href = import.meta.env.VITE_BASE || '/';
  // 返回首页
  router.replace({ path: '/' })
}

// Handle menu item click
const handleMenuClick = path => {
  if (path === '/profile/community') {
    // window.location.href = 'https://community.mlaj.com';
    showToast('功能暂未开放')
  } else if (path === '/profile/activities') {
    // showToast('功能暂未开放')
    window.location.href = 'https://wxm.behalo.cc/pages/tabBar/mine/application?type=2'
  } else if (path === '/profile/userinfo') {
    // showToast('功能暂未开放')
    window.location.href = 'https://wxm.behalo.cc/pages/student/student?token=&user_id='
  } else {
    router.push(path)
  }
}

// Handle check-in type click
const handleCheckInClick = path => {
  router.push(path)
}

// Right content component
const rightContent = h('div', { class: 'flex items-center' }, [
  h('button', { class: 'p-2' }, [
    h(
      'svg',
      {
        xmlns: 'http://www.w3.org/2000/svg',
        class: 'h-6 w-6 text-gray-700',
        fill: 'none',
        viewBox: '0 0 24 24',
        stroke: 'currentColor',
      },
      [
        h('path', {
          'stroke-linecap': 'round',
          'stroke-linejoin': 'round',
          'stroke-width': '2',
          d: 'M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9',
        }),
      ]
    ),
  ]),
])

// Menu items
const menuItems1 = [
  {
    icon: 'clock',
    title: '学习记录',
    path: '/profile/learning-records',
    badge: '',
  },
  {
    icon: 'wallet',
    title: '活动报名',
    path: '/profile/activities',
  },
  {
    icon: 'user',
    title: '人员信息',
    path: '/profile/userinfo',
  },
  {
    icon: 'book',
    title: '我的课程',
    path: '/profile/courses',
  },
  {
    icon: 'document',
    title: '课程订单',
    path: '/profile/orders',
  },
]

const menuItems2 = ref([
  {
    icon: 'wallet',
    title: '我的积分',
    path: '/profile/points',
  },
  {
    icon: 'heart',
    title: '我的收藏',
    path: '/profile/favorites',
  },
  // {
  //   icon: "chat",
  //   title: "我的圈子",
  //   path: "/profile/community",
  // },
  {
    icon: 'email',
    title: '我的消息',
    path: '/profile/messages',
    badge: '',
  },
])

const menuItems3 = ref([
  {
    icon: 'question',
    title: '帮助中心',
    path: '/profile/help',
  },
  {
    icon: 'settings',
    title: '设置',
    path: '/profile/settings',
  },
])

const menuItems4 = ref([
  {
    icon: 'document',
    title: '打卡管理',
    path: '/teacher/checkin',
  },
  {
    icon: 'user',
    title: '我的班级',
    path: '/teacher/myClass',
  },
  {
    icon: 'book',
    title: '作业管理',
    path: '/teacher/tasks',
  },
])

const handleCheckin = () => {
  if (checkinData.value.length) {
    showCheckInDialog.value = true
  } else {
    showToast('暂无打卡任务')
  }
}
</script>