App.vue 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. <script setup lang="ts">
  2. import { ref, onMounted, onBeforeUnmount, computed } from "vue";
  3. import Header from "@/components/layout/Header.vue";
  4. import Footer from "@/components/layout/Footer.vue";
  5. import Home from "@/views/Home.vue";
  6. import Purchased from "@/views/Purchased.vue";
  7. import Account from "@/views/Account.vue";
  8. import Favorite from "@/views/Favorite.vue";
  9. import LoginDialog from "@/components/LoginDialog.vue";
  10. import { useUserStore } from "@/store/user";
  11. type TabKey = "home" | "purchased" | "account" | "favorite";
  12. const active = ref<TabKey>("home");
  13. const showScrollTop = ref(false);
  14. const showLoginDialog = ref(false);
  15. const userStore = useUserStore();
  16. const isLoggedIn = computed(() => !!userStore.token);
  17. function handleScroll() {
  18. showScrollTop.value = window.scrollY > 320;
  19. }
  20. function scrollToTop() {
  21. window.scrollTo({ top: 0, behavior: "smooth" });
  22. }
  23. function switchTab(key: TabKey) {
  24. // 如果用户点击"已购买"但未登录,则显示登录弹窗
  25. if (key === "purchased" && !isLoggedIn.value) {
  26. showLoginDialog.value = true;
  27. return;
  28. }
  29. active.value = key;
  30. }
  31. function handleLoginSuccess() {
  32. // 登录成功后的处理
  33. if (active.value === "account") {
  34. // 如果在账号页面登录成功,刷新用户信息
  35. userStore.sync();
  36. }
  37. }
  38. onMounted(() => {
  39. window.addEventListener("scroll", handleScroll, { passive: true });
  40. // 如果有token,同步用户信息
  41. if (userStore.token) {
  42. userStore.sync().catch(() => {
  43. // 如果同步失败,可能是token过期,清除登录状态
  44. userStore.logout();
  45. });
  46. }
  47. });
  48. onBeforeUnmount(() => {
  49. window.removeEventListener("scroll", handleScroll);
  50. });
  51. </script>
  52. <template>
  53. <div
  54. class="min-h-screen bg-surface text-slate-100 selection:bg-brand/20 selection:text-brand"
  55. >
  56. <Header @show-login="showLoginDialog = true" @switch-tab="switchTab" />
  57. <main class="max-w-screen-sm mx-auto w-full px-4 pb-28 pt-3">
  58. <Home v-if="active === 'home'" />
  59. <Purchased v-else-if="active === 'purchased' && isLoggedIn" />
  60. <Account
  61. v-else-if="active === 'account'"
  62. @show-login="showLoginDialog = true"
  63. />
  64. <Favorite v-else-if="active === 'favorite'" />
  65. </main>
  66. <button
  67. v-show="showScrollTop"
  68. @click="scrollToTop"
  69. class="fixed right-4 bottom-28 z-40 h-11 w-11 rounded-full bg-brand text-slate-900 shadow-lg grid place-items-center active:scale-95 transition"
  70. aria-label="回到顶部"
  71. >
  72. <svg
  73. viewBox="0 0 24 24"
  74. width="22"
  75. height="22"
  76. fill="none"
  77. stroke="currentColor"
  78. stroke-width="2"
  79. class="text-slate-900"
  80. >
  81. <path d="m5 15 7-7 7 7" />
  82. </svg>
  83. </button>
  84. <Footer :active="active" @switch-tab="switchTab" />
  85. <LoginDialog
  86. v-model="showLoginDialog"
  87. @login-success="handleLoginSuccess"
  88. />
  89. </div>
  90. </template>