Forráskód Böngészése

feat: 添加测试页面并完善支付流程

- 新增测试页面及相关路由配置
- 在web-view页面添加状态管理逻辑
- 新增微信支付查询和取消接口
- 完善支付流程,增加支付状态查询和订单取消功能
- 优化课程详情页的状态处理和页面跳转逻辑
whj 4 napja
szülő
commit
5c761ea282
6 módosított fájl, 264 hozzáadás és 7 törlés
  1. 23 5
      components/lock.uvue
  2. 7 0
      pages.json
  3. 17 2
      pages/catalog/detail.uvue
  4. 5 0
      pages/catalog/web-view.uvue
  5. 206 0
      pages/test/index.uvue
  6. 6 0
      services/user.ts

+ 23 - 5
components/lock.uvue

@@ -1,5 +1,5 @@
 <script setup lang='ts'>
-import { wechatPay, wechatPayRequest } from '@/services/user'
+import { wechatPay, wechatPayRequest, wechatPayQuery, wechatPayCancel } from '@/services/user'
 import { ref } from 'vue'
 const props = defineProps({
   record: {
@@ -11,6 +11,15 @@ const visible = ref(false)
 function handleOpen() {
   visible.value = true
 }
+async function getPayStatus(outTradeNo: string) {
+  const res = await wechatPayQuery({
+    outTradeNo
+  })
+  if (res.code !== 1000) {
+    getPayStatus(outTradeNo)
+  }
+}
+
 const handlePay = async () => {
   console.log(props.record)
   try {
@@ -34,14 +43,23 @@ const handlePay = async () => {
           title: "支付成功",
           icon: 'success'
         });
+        // 支付成功后,查询支付结果
+        getPayStatus(outTradeNo)
       },
       fail: (err) => {
         console.error("err", err);
         uni.hideLoading();
-        uni.showToast({
-          title: "支付失败",
-          icon: 'error'
-        });
+        // 支付失败后,取消订单
+        wechatPayCancel({
+          outTradeNo
+        }).then(res => {
+          if (res.code === 200) {
+            uni.showToast({
+              title: "订单已取消",
+              icon: 'success'
+            });
+          }
+        })
       }
     });
   } catch (err: any) {

+ 7 - 0
pages.json

@@ -89,6 +89,13 @@
 				"navigationStyle": "custom",
 				"disableScroll": true
 			}
+		},
+		{
+			"path": "pages/test/index",
+			"style": {
+				"navigationStyle": "custom",
+				"disableScroll": true
+			}
 		}
 	],
 	"globalStyle": {

+ 17 - 2
pages/catalog/detail.uvue

@@ -122,9 +122,15 @@ onShow(() => {
   const prevPage = pages[pages.length - 1];
   console.log('prevPage', prevPage)
   status.value = (prevPage as any)?.status || 'wait'
+  if (status.value === 'test') {
+    if (progress.value === 2) {
+      router.back()
+    }
+    (prevPage as any).status = 'wait'
+  }
   if (status.value === 'success') {
-    progress2.value++
-      ; (prevPage as any).status = 'wait'
+    progress2.value++;
+    (prevPage as any).status = 'wait'
   }
   isLoading.value = false
 
@@ -175,6 +181,15 @@ watchEffect(() => {
         data.value.videoSrc = course.value?.detailItem?.expandVideoPath
         break
     }
+  } else if (progress.value === 2) {
+    data.value.webviewSrc = course.value?.testItem?.animationPath
+    router.push({
+      path: "/pages/catalog/web-view",
+      query: {
+        src: data.value.webviewSrc,
+        progress: progress2.value,
+      }
+    });
   }
 })
 const timers = ref(100)

+ 5 - 0
pages/catalog/web-view.uvue

@@ -26,6 +26,11 @@ function handleLoad() {
   console.log('handleLoad')
   isLoading.value = false
 }
+onShow(() => {
+  var pages = getCurrentPages();
+  const prevPage = pages[pages.length - 2];
+  (prevPage as any).status = "test"
+})
 </script>
 <template>
   <Loading v-show="isLoading" />

+ 206 - 0
pages/test/index.uvue

@@ -0,0 +1,206 @@
+<script setup lang='ts'>
+import Back from '@/components/back.uvue'
+import Loading from '@/components/loading.uvue'
+import { ref, onMounted, nextTick } from 'vue'
+import { type SubjectKnowledgeCardResult, getSubjectKnowledgeCard } from '@/services/subject/card'
+import { fetchSubjectConfigInfo } from '@/services/subject/info'
+import type { SubjectCatalogResult } from '@/services/subject/catalog'
+import { config } from '@/config'
+import { dict } from '@/.cool/store'
+
+const activeCategory = ref<string>()
+const activeTab = ref<string>('knowledge')
+const isLoading = ref(true)
+const categories = ref<SubjectCatalogResult[]>([])
+const cards = ref<SubjectKnowledgeCardResult[]>([])
+const cardsScrollView = ref<any>(null)
+const categoryRefs = ref<any>()
+async function getDataList() {
+  const id = dict.getValueByLabelMapByType('index_subject_id')['物理']
+  const res = await getSubjectKnowledgeCard({ subjectId: id })
+  cards.value = res.userCardList || []
+  categories.value = res.catalogList || []
+}
+function handleSelect(categoryId: string) {
+  activeCategory.value = categoryId
+  uni.createSelectorQuery().select(`.category-${categoryId}`).boundingClientRect().exec((rect) => {
+    if (cardsScrollView.value && rect[0]) {
+      cardsScrollView.value.scrollTo({
+        top: rect[0].top - 70, // 减去顶部偏移
+        animated: true
+      })
+    }
+  })
+}
+
+async function onScroll(e: any) {
+  categories.value.forEach(async (category) => {
+    const selector = `.category-${category.id}`
+    await uni.createSelectorQuery().selectAll(selector).boundingClientRect().exec((rects) => {
+      // 检查元素是否在可视区域内
+      for (const rect of rects[0]) {
+        if (rect.top <= 150 && rect.bottom >= 150) {
+          activeCategory.value = category.id
+        }
+      }
+    })
+  })
+}
+onMounted(async () => {
+  await getDataList()
+  isLoading.value = false
+  nextTick(() => {
+    handleSelect(categories.value[0].id as string)
+  })
+})
+</script>
+<template>
+  <Loading v-show="isLoading" />
+  <cl-page v-show="!isLoading">
+    <Back />
+    <!-- 顶部标题栏 -->
+    <view class="content">
+      <!-- 左侧导航菜单 -->
+      <scroll-view direction="vertical" :show-scrollbar="false" class="sidebar">
+        <view v-for="category in categories" :key="category.id" class="sidebar-item"
+          :class="{ active: activeCategory === category.id }" @tap="handleSelect(category.id as string)">
+          <cl-image :src="config.baseUrl + category?.fileList?.[0]?.url" mode="heightFix" class="!h-[28px]"></cl-image>
+          <text class="sidebar-text">{{ category.name }}</text>
+        </view>
+      </scroll-view>
+      <!-- 右侧卡片网格 -->
+      <view class="cards-container">
+        <view class="header">
+          <view class="title-tabs">
+            <view class="tab" :class="{ active: activeTab === 'knowledge' }" @tap="activeTab = 'knowledge'">知识卡
+            </view>
+            <view class="tab" :class="{ active: activeTab === 'honor' }" @tap="activeTab = 'honor'">荣誉</view>
+          </view>
+        </view>
+        <scroll-view direction="vertical" :show-scrollbar="false" class="cards" ref="cardsScrollView"
+          @scroll="onScroll">
+          <view class="grid grid-cols-5 gap-4">
+            <view class="card" v-for="card in cards" :key="card.id" :class="`category-${card.catalogId}`"
+              ref="categoryRefs">
+              <image v-if="card.userCardId" :src="config.baseUrl + card.iconPath" class="w-full h-full" />
+              <image v-else src="https://oss.xiaoxiongcode.com/static/home/21.png" class="w-full h-full" />
+              <view class="card-title">{{ card.cardName }}</view>
+            </view>
+          </view>
+        </scroll-view>
+      </view>
+    </view>
+  </cl-page>
+</template>
+<style lang="scss" scoped>
+.header {
+  @apply absolute left-1/2 top-5;
+  transform: translateX(-50%);
+  text-align: center;
+
+  .title-tabs {
+    @apply flex flex-row items-center justify-center rounded-full;
+    background-color: #9AD2FA;
+
+    .tab {
+      @apply rounded-full;
+      padding: 5px 10px;
+      font-size: 16px;
+      transition: all 0.3s ease;
+
+      &.active {
+        background-color: white;
+        color: #1E88E5;
+        font-weight: bold;
+      }
+    }
+  }
+}
+
+.content {
+  display: flex;
+  flex-direction: row;
+
+  .sidebar {
+    width: 200px;
+    background-color: #116FE9;
+    padding: 20px;
+    padding-top: 70px;
+    height: 100vh;
+
+    .sidebar-item {
+      @apply text-white rounded-full py-1 cursor-pointer flex flex-row items-center;
+      margin-bottom: 8px;
+      transition: all 0.3s ease;
+      font-size: 12px;
+      width: 100%;
+
+      &.active {
+        background-color: rgba(255, 255, 255, 1);
+        color: #1E88E5;
+        font-weight: bold;
+      }
+
+      .sidebar-icon {
+        font-size: 28px;
+        margin-right: 2px;
+      }
+    }
+  }
+
+  .cards-container {
+    @apply relative;
+    flex: 1;
+    width: 100%;
+    padding: 20px;
+    padding-top: 70px;
+    background: linear-gradient(0deg, #2EB2FD, #0B85F4);
+
+    .cards {
+      height: calc(100vh - 90px);
+    }
+
+    .category-section {
+      margin-bottom: 40px;
+
+      .category-title {
+        @apply text-white font-bold text-xl mb-4;
+      }
+    }
+
+    .card {
+      // background-color: rgba(255, 255, 255, 0.9);
+      // border-radius: 16px;
+      // padding: 16px;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      // box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+      aspect-ratio: 3/4;
+
+      .card-content {
+        width: 100%;
+        height: 120px;
+        background-color: #E3F2FD;
+        border-radius: 12px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        margin-bottom: 12px;
+        aspect-ratio: 3/4;
+
+        .question-mark {
+          font-size: 60px;
+          font-weight: bold;
+          color: #1E88E5;
+        }
+      }
+
+      .card-title {
+        @apply text-center font-bold absolute left-0 w-full text-[#0E3E87] text-[1.5vw];
+        bottom: 13%;
+      }
+    }
+  }
+}
+</style>

+ 6 - 0
services/user.ts

@@ -72,4 +72,10 @@ export function wechatPayRequest(parameter: any) {
 }
 export function exchangeCode(params) {
   return usePost(`/upms/users/exchange/code`, params) as Promise<any>
+}
+export function wechatPayQuery(parameter: any) {
+  return useGet(`/wechat/pay/query/pay/result`, parameter) as Promise<any>
+}
+export function wechatPayCancel(parameter: any) {
+  return usePut(`/wechat/pay/user/cancel/pay/${parameter.outTradeNo}`, parameter) as Promise<any>
 }