index.vue 6.34 KB
<template>
  <view class="min-h-screen bg-[#F9FAFB] pb-[200rpx] flex flex-col items-center">
    <!-- Header -->
    <NavHeader title="我的" />

    <!-- User Info Card -->
    <!-- Width: 353px -> 706rpx, Height: 124px -> 248rpx -->
    <!-- Background image from design -->
    <view
      class="w-[706rpx] h-[248rpx] mt-[40rpx] bg-white rounded-[24rpx] flex items-center px-[40rpx]"
      @tap="go('/pages/avatar/index')"
    >
      <!-- Avatar -->
      <view class="w-[160rpx] h-[160rpx] rounded-full overflow-hidden border-2 border-white shadow-sm shrink-0">
        <img class="w-full h-full object-cover" :src="userInfo?.avatar_url || defaultAvatar" />
      </view>

      <!-- Info -->
      <view class="ml-[32rpx] flex-1 flex flex-col justify-center">
        <text class="text-[36rpx] font-bold text-gray-800 mb-[8rpx]">{{ userInfo?.name || '加载中...' }}</text>
        <text class="text-[28rpx] text-gray-500 mb-[4rpx]">工号: {{ userInfo?.employee_no || '--' }}</text>
        <text class="text-[24rpx] text-gray-400">点击修改头像</text>
      </view>

      <!-- Arrow -->
      <IconFont name="rect-right" size="20" color="#9CA3AF" />
    </view>

    <!-- Menu List -->
    <!-- Width: 353px -> 706rpx, Radius: 12px -> 24rpx, Padding: 16px -> 32rpx -->
    <view class="w-[706rpx] bg-white rounded-[24rpx] p-[32rpx] mt-[32rpx]">
      <view
        v-for="(item, index) in menuItems"
        :key="index"
        class="flex flex-col"
        @tap="handleMenuClick(item)"
      >
        <view class="flex items-center justify-between py-[24rpx]">
          <view class="flex items-center">
            <!-- Icon Size: 40px -> 80rpx. Using IconFont to match request, centered in a box if needed, or just large icon -->
            <!-- Design had 40px images. I'll use 32px (64rpx) IconFont for balance or 40px if needed. -->
            <view class="w-[80rpx] h-[80rpx] bg-blue-50 rounded-[16rpx] flex items-center justify-center mr-[24rpx]">
              <IconFont :name="item.icon" size="24" color="#2563EB" />
            </view>
            <text class="text-[32rpx] text-gray-800">{{ item.title }}</text>
          </view>
          <IconFont name="rectRight" size="16" color="#9CA3AF" />
        </view>
        <!-- Separator -->
        <view v-if="index < menuItems.length - 1" class="h-[2rpx] bg-gray-100 w-full"></view>
      </view>
    </view>

    <!-- Logout Button -->
    <view class="w-[730rpx] rounded-[24rpx] p-[32rpx] mt-[32rpx]">
      <view
        class="flex items-center justify-center py-[20rpx] px-[32rpx] rounded-[16rpx] border-[2rpx] border-[#FEE2E2] bg-[#FEF2F2] active:opacity-70"
        @tap="handleLogout"
      >
        <IconFont name="issue" size="18" color="#EF4444" class="mr-[12rpx]" />
        <text class="text-[28rpx] text-[#EF4444] font-medium">退出登录</text>
      </view>
    </view>

    <!-- TabBar -->
    <TabBar current="me" />
  </view>
</template>

<script setup>
import { ref } from 'vue'
import { useGo } from '@/hooks/useGo'
import { mainStore } from '@/stores/main'
import { useUserStore } from '@/stores/user'
import IconFont from '@/components/IconFont.vue'
import TabBar from '@/components/TabBar.vue'
import NavHeader from '@/components/NavHeader.vue'
import Taro, { useLoad, useDidShow } from '@tarojs/taro'
import { getProfileAPI } from '@/api/user'
import defaultAvatar from '@/assets/images/icon/avatar.svg'

const go = useGo()
const store = mainStore()
const userStore = useUserStore()

/**
 * @description 用户信息(响应式)
 * @type {import('vue').Ref<{id?: number, name?: string, avatar_url?: string}|null>}
 */
const userInfo = ref(null)

/**
 * @description 获取用户个人信息
 * @description 进入页面时调用,401 自动跳转登录页(由 request.js 拦截器处理)
 * @returns {Promise<void>}
 */
const fetchUserProfile = async () => {
  try {
    const res = await getProfileAPI()
    if (res.code === 1 && res.data?.user) {
      // 更新响应式数据
      userInfo.value = res.data.user
      // 更新全局状态
      store.changeUserInfo(res.data.user)
    } else {
      // 接口返回失败(非 401,因为 401 已被 request.js 拦截器处理)
      console.warn('获取用户信息失败:', res.msg)
    }
  } catch (err) {
    console.error('获取用户信息异常:', err)
  }
}

/**
 * @description 页面加载时获取用户信息(首次进入)
 */
useLoad(() => {
  fetchUserProfile()
})

/**
 * @description 页面显示时刷新用户信息(从其他页面返回时)
 * @description 例如:从头像设置页面保存后返回,需要刷新显示新头像
 */
useDidShow(() => {
  fetchUserProfile()
})

const menuItems = [
  { title: '我的计划书', icon: 'order', path: '/pages/plan/index' },
  { title: '我的收藏', icon: 'star', path: '/pages/favorites/index' },
  { title: '帮助中心', icon: 'service', path: '/pages/help-center/index' },
  { title: '意见反馈', icon: 'edit', path: '/pages/feedback-list/index' }
]

const handleMenuClick = (item) => {
  if (item.path) {
    go(item.path)
  } else if (item.action === 'toast') {
    Taro.showToast({
      title: '功能开发中',
      icon: 'none'
    })
  }
}

/**
 * 退出登录
 * @description 调用 logoutAPI 解绑 openid,清除本地状态
 */
const handleLogout = async () => {
  Taro.showModal({
    title: '提示',
    content: '确定要退出登录吗?',
    confirmText: '确定',
    cancelText: '取消',
    confirmColor: '#EF4444',
    success: async (res) => {
      if (res.confirm) {
        // 显示加载提示
        Taro.showLoading({
          title: '退出中...',
          mask: true
        })

        try {
          // 调用 userStore 的 logout 方法(会调用 logoutAPI)
          await userStore.logout()

          // 清除 mainStore 中的用户信息
          store.changeUserInfo(null)

          Taro.hideLoading()

          // 跳转到首页
          Taro.reLaunch({
            url: '/pages/index/index'
          })

          Taro.showToast({
            title: '已退出登录',
            icon: 'success'
          })
        } catch (error) {
          Taro.hideLoading()
          console.error('退出登录失败:', error)
          Taro.showToast({
            title: error.message || '退出失败,请重试',
            icon: 'none'
          })
        }
      }
    }
  })
}
</script>

<style lang="less">
/* No custom CSS needed, all Tailwind */
</style>