Просмотр исходного кода

新增用户VIP等级显示功能,优化会员购买流程,添加订单查询接口,提升用户体验

wuyi 3 месяцев назад
Родитель
Сommit
9e6c980802
4 измененных файлов с 176 добавлено и 4 удалено
  1. 12 0
      src/components/layout/Header.vue
  2. 7 0
      src/services/api.ts
  3. 25 0
      src/types/vip.ts
  4. 132 4
      src/views/Account.vue

+ 12 - 0
src/components/layout/Header.vue

@@ -2,10 +2,15 @@
 import { computed } from "vue";
 import { computed } from "vue";
 import { useRouter } from "vue-router";
 import { useRouter } from "vue-router";
 import { useUserStore } from "@/store/user";
 import { useUserStore } from "@/store/user";
+import { vipLevelToText, VipLevel } from "@/types/vip";
 
 
 const userStore = useUserStore();
 const userStore = useUserStore();
 const router = useRouter();
 const router = useRouter();
 const isLoggedIn = computed(() => !!userStore.token);
 const isLoggedIn = computed(() => !!userStore.token);
+const isVip = computed(() => {
+  const level = userStore.userInfo?.vipLevel;
+  return level && level !== VipLevel.GUEST && level !== VipLevel.FREE;
+});
 
 
 defineProps<{
 defineProps<{
   title?: string;
   title?: string;
@@ -45,6 +50,13 @@ const goToHome = () => {
               @click="$emit('switch-tab', 'account')"
               @click="$emit('switch-tab', 'account')"
             >
             >
               {{ userStore.userInfo?.name || "账号" }}
               {{ userStore.userInfo?.name || "账号" }}
+              <span
+                v-if="isVip"
+                class="ml-2 px-2 py-0.5 rounded bg-yellow-400/90 text-xs text-yellow-900 font-semibold align-middle"
+                style="vertical-align: middle"
+              >
+                {{ vipLevelToText(userStore.userInfo?.vipLevel) }}
+              </span>
             </button>
             </button>
           </template>
           </template>
           <button
           <button

+ 7 - 0
src/services/api.ts

@@ -137,6 +137,13 @@ export const purchaseMember = async (
   return res.data;
   return res.data;
 };
 };
 
 
+export const userQueryOrder = async (orderNo?: string): Promise<any> => {
+  const res = await api.get(`/payment/user/query`, {
+    params: { orderNo: orderNo || undefined },
+  });
+  return res.data;
+};
+
 /**
 /**
  * ===================== 视频相关接口 =====================
  * ===================== 视频相关接口 =====================
  */
  */

+ 25 - 0
src/types/vip.ts

@@ -19,3 +19,28 @@ export const canWatchFullVideo = (vipLevel: VipLevel): boolean => {
 export const getTrialDuration = (): number => {
 export const getTrialDuration = (): number => {
   return 5 * 60; // 5分钟
   return 5 * 60; // 5分钟
 };
 };
+
+export const vipLevelToText = (level: VipLevel | string): string => {
+  switch (level) {
+    case VipLevel.GUEST:
+      return "游客";
+    case VipLevel.FREE:
+      return "普通用户";
+    case VipLevel.HOURLY:
+      return "小时会员";
+    case VipLevel.DAILY:
+      return "日会员";
+    case VipLevel.WEEKLY:
+      return "周会员";
+    case VipLevel.MONTHLY:
+      return "月会员";
+    case VipLevel.QUARTERLY:
+      return "季会员";
+    case VipLevel.YEARLY:
+      return "年会员";
+    case VipLevel.LIFETIME:
+      return "永久会员";
+    default:
+      return String(level);
+  }
+};

+ 132 - 4
src/views/Account.vue

@@ -1,20 +1,28 @@
 <script setup lang="ts">
 <script setup lang="ts">
 import { useUserStore } from "@/store/user";
 import { useUserStore } from "@/store/user";
-import { computed, ref } from "vue";
-import { upgradeGuest, purchaseMember } from "@/services/api";
+import { computed, ref, onMounted } from "vue";
+import { upgradeGuest, purchaseMember, userQueryOrder } from "@/services/api";
+import { vipLevelToText, VipLevel } from "@/types/vip";
 
 
 const userStore = useUserStore();
 const userStore = useUserStore();
 const isLoggedIn = computed(() => !!userStore.token);
 const isLoggedIn = computed(() => !!userStore.token);
 const isGuest = computed(() => {
 const isGuest = computed(() => {
   return userStore.userInfo?.vipLevel === "guest";
   return userStore.userInfo?.vipLevel === "guest";
 });
 });
+const isVip = computed(() => {
+  const level = userStore.userInfo?.vipLevel;
+  return level && level !== VipLevel.GUEST && level !== VipLevel.FREE;
+});
 const emit = defineEmits(["show-login"]);
 const emit = defineEmits(["show-login"]);
 
 
 const showUpgradeDialog = ref(false);
 const showUpgradeDialog = ref(false);
 const showMembershipDialog = ref(false);
 const showMembershipDialog = ref(false);
 const showPaymentWaitingDialog = ref(false);
 const showPaymentWaitingDialog = ref(false);
 const showErrorDialog = ref(false);
 const showErrorDialog = ref(false);
+const showSuccessDialog = ref(false);
 const errorMessage = ref("");
 const errorMessage = ref("");
+const successMessage = ref("");
+const currentOrderNo = ref("");
 const upgradeForm = ref({
 const upgradeForm = ref({
   name: null,
   name: null,
   password: null,
   password: null,
@@ -51,6 +59,11 @@ const showError = (message: string) => {
   showErrorDialog.value = true;
   showErrorDialog.value = true;
 };
 };
 
 
+const showSuccess = (message: string) => {
+  successMessage.value = message;
+  showSuccessDialog.value = true;
+};
+
 const handleMembershipPurchase = async () => {
 const handleMembershipPurchase = async () => {
   if (!selectedPlan.value) {
   if (!selectedPlan.value) {
     alert("请选择会员套餐");
     alert("请选择会员套餐");
@@ -65,6 +78,9 @@ const handleMembershipPurchase = async () => {
     );
     );
 
 
     if (response.code === 1) {
     if (response.code === 1) {
+      // 保存订单号
+      currentOrderNo.value = response.out_trade_no;
+
       // 关闭购买弹窗
       // 关闭购买弹窗
       showMembershipDialog.value = false;
       showMembershipDialog.value = false;
 
 
@@ -123,6 +139,65 @@ const handleUpgrade = async () => {
     isLoading.value = false;
     isLoading.value = false;
   }
   }
 };
 };
+
+// 检查支付成功后的用户信息同步
+const checkPaymentSuccess = async () => {
+  const urlParams = new URLSearchParams(window.location.search);
+  const paymentSuccess = urlParams.get("pay");
+  console.log("paymentSuccess", paymentSuccess);
+
+  if (paymentSuccess === "true") {
+    try {
+      const response = await userQueryOrder();
+      if (response.status === 1) {
+        // 订单状态确认正确,同步用户信息
+        await userStore.sync();
+        showSuccess(`会员购买成功`);
+        currentOrderNo.value = "";
+      } else {
+        // 订单状态异常,仍同步用户信息
+        await userStore.sync();
+        showError(`${response.msg}`);
+      }
+
+      // 清除URL参数
+      const newUrl = window.location.pathname;
+      window.history.replaceState({}, document.title, newUrl);
+    } catch (error) {
+      console.error("同步用户信息失败:", error);
+      showError("会员购买失败");
+    }
+  }
+};
+
+// 查询订单状态
+const handleQueryOrder = async () => {
+  if (!currentOrderNo.value) {
+    showError("没有找到订单信息");
+    return;
+  }
+
+  try {
+    const response = await userQueryOrder(currentOrderNo.value);
+
+    if (response.status === 1) {
+      // 支付成功
+      await userStore.sync();
+      showPaymentWaitingDialog.value = false;
+      showSuccess("会员购买成功");
+      currentOrderNo.value = "";
+    } else {
+      showError(response.msg || "会员购买失败");
+    }
+  } catch (error) {
+    console.error("查询订单状态失败:", error);
+    showError("会员购买失败,请重试");
+  }
+};
+
+onMounted(() => {
+  checkPaymentSuccess();
+});
 </script>
 </script>
 
 
 <template>
 <template>
@@ -158,6 +233,13 @@ const handleUpgrade = async () => {
         </p>
         </p>
         <h2 class="text-base font-semibold text-white/90 truncate">
         <h2 class="text-base font-semibold text-white/90 truncate">
           {{ isLoggedIn ? userStore.userInfo?.name || "用户" : "点击登录账号" }}
           {{ isLoggedIn ? userStore.userInfo?.name || "用户" : "点击登录账号" }}
+          <span
+            v-if="isVip"
+            class="ml-2 px-2 py-0.5 rounded bg-yellow-400/90 text-xs text-yellow-900 font-semibold align-middle"
+            style="vertical-align: middle"
+          >
+            {{ vipLevelToText(userStore.userInfo?.vipLevel) }}
+          </span>
         </h2>
         </h2>
       </div>
       </div>
       <div v-if="isLoggedIn" class="flex gap-2">
       <div v-if="isLoggedIn" class="flex gap-2">
@@ -174,7 +256,7 @@ const handleUpgrade = async () => {
           :disabled="isGuest"
           :disabled="isGuest"
           :class="{ 'opacity-50 cursor-not-allowed': isGuest }"
           :class="{ 'opacity-50 cursor-not-allowed': isGuest }"
         >
         >
-          开通会员
+          {{ isVip ? "续订会员" : "开通会员" }}
         </button>
         </button>
       </div>
       </div>
       <button
       <button
@@ -432,7 +514,7 @@ const handleUpgrade = async () => {
             取消支付
             取消支付
           </button>
           </button>
           <button
           <button
-            @click="showPaymentWaitingDialog = false"
+            @click="handleQueryOrder"
             class="flex-1 px-4 py-2 bg-brand text-slate-900 rounded-lg hover:bg-brand/90 transition"
             class="flex-1 px-4 py-2 bg-brand text-slate-900 rounded-lg hover:bg-brand/90 transition"
           >
           >
             已完成支付
             已完成支付
@@ -486,6 +568,52 @@ const handleUpgrade = async () => {
         </button>
         </button>
       </div>
       </div>
     </div>
     </div>
+
+    <!-- 成功提示弹窗 -->
+    <div
+      v-if="showSuccessDialog"
+      class="fixed inset-0 bg-black/50 flex items-center justify-center z-50"
+      @click.self="showSuccessDialog = false"
+    >
+      <div
+        class="bg-surface border border-white/10 rounded-2xl p-6 w-full max-w-sm mx-4 text-center"
+      >
+        <!-- 成功图标 -->
+        <div class="mb-4">
+          <div
+            class="w-12 h-12 mx-auto bg-green-500/20 rounded-full flex items-center justify-center"
+          >
+            <svg
+              class="w-6 h-6 text-green-400"
+              fill="none"
+              stroke="currentColor"
+              viewBox="0 0 24 24"
+            >
+              <path
+                stroke-linecap="round"
+                stroke-linejoin="round"
+                stroke-width="2"
+                d="M5 13l4 4L19 7"
+              />
+            </svg>
+          </div>
+        </div>
+
+        <!-- 成功信息 -->
+        <h3 class="text-lg font-semibold text-white/90 mb-2">操作成功</h3>
+        <p class="text-sm text-white/70 mb-6">
+          {{ successMessage }}
+        </p>
+
+        <!-- 确认按钮 -->
+        <button
+          @click="showSuccessDialog = false"
+          class="w-full px-4 py-2 bg-brand text-slate-900 rounded-lg hover:bg-brand/90 transition"
+        >
+          确定
+        </button>
+      </div>
+    </div>
   </section>
   </section>
 </template>
 </template>
 <style scoped>
 <style scoped>