Explorar el Código

refactor(math): 重构数学首页布局与逻辑

1.  调整页面布局为左侧导航+右侧卡片的双栏结构
2.  替换旧的接口调用与数据处理逻辑,新增分页加载支持
3.  移除冗余的弹窗选择器,改用侧边栏直接切换分类
4.  优化样式与响应式适配,替换原有横向滚动为纵向网格布局
408249787@qq.com hace 1 semana
padre
commit
58b6c9a6e5
Se han modificado 2 ficheros con 399 adiciones y 175 borrados
  1. 226 0
      pages/math/index copy.uvue
  2. 173 175
      pages/math/index.uvue

+ 226 - 0
pages/math/index copy.uvue

@@ -0,0 +1,226 @@
+<script lang="ts" setup>
+import { user } from '@/.cool'
+import { ref, onMounted, computed } from 'vue'
+import { fetchSubjectAppInfo } from '@/services/subject/info'
+import { type SubjectCatalogResult, querySubjectCatalog } from '@/services/subject/catalog'
+import { type SubjectCourseResult, querySubjectCourse } from '@/services/subject/course'
+
+import Progress from '../catalog/components/progress.uvue'
+import Back from '@/components/back.uvue'
+import Lock from '@/components/lock.uvue'
+import Loading from '@/components/loading.uvue'
+import { config } from '@/config'
+import { router } from "@/.cool";
+import { dict } from '@/.cool/store'
+const isLoading = ref(true)
+const visible = ref<boolean>(false)
+const dataList = ref<SubjectCatalogResult[]>([])
+const catalog = ref<SubjectCatalogResult>()
+const record = ref<any>()
+const courseList = ref<SubjectCourseResult[]>()
+async function getDataList() {
+  const id = dict.getValueByLabelMapByType('index_subject_id')['数学']
+  const res = await querySubjectCatalog({
+    subjectId: id, dataScope: {
+      sortBy: 'asc',
+      sortName: 'sortNum',
+    }
+  })
+  record.value = res
+  dataList.value = res.catalogList || []
+  catalog.value = res?.catalogList?.[0]
+  courseList.value = res?.courseList || []
+}
+onMounted(async () => {
+  try {
+    await getDataList()
+    isLoading.value = false
+  } catch (err) {
+    console.log(err);
+    isLoading.value = false
+  }
+})
+const cardsScrollView = ref<any>(null)
+const scrollLeft = ref<any>(0)
+async function handleSelect(val: SubjectCatalogResult) {
+  catalog.value = val
+  visible.value = false
+  uni.createSelectorQuery().select(`.category-${val.id}`).boundingClientRect().exec(async (rect) => {
+    if (cardsScrollView.value && rect[0]) {
+      cardsScrollView.value.scrollTo({
+        left: scrollLeft.value + rect[0].left - dict.getWindowHeight() / 2, // 减去顶部偏移
+        animated: true
+      })
+    }
+  })
+}
+function debouncedOnScroll(e: any) {
+  console.log(e);
+  scrollLeft.value = e.detail.scrollLeft
+  dataList.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.left <= (dict.getWindowHeight() / 2) && rect.left > 0) {
+          catalog.value = category
+          return
+        }
+      }
+    })
+  })
+}
+function handleDetail(item: SubjectCatalogResult) {
+  // if (!item.payFlag && !item.trialPlay) {
+  //   uni.showToast({
+  //     title: '请先购买',
+  //     icon: 'none'
+  //   })
+  //   return
+  // }
+  router.push({
+    path: "/pages/english/detail",
+    query: {
+      id: item.id,
+    }
+  });
+}
+const userInfo = computed(() => user.info.value?.userInfo)
+</script>
+
+<template>
+  <Loading v-show="isLoading" />
+  <cl-page v-show="!isLoading">
+    <Back />
+    <image mode="aspectFill" src="https://oss.xiaoxiongcode.com/static/home/math-bg.png" alt=""
+      class="w-full h-full object-cover" />
+    <!-- 精灵图动画 -->
+    <cl-image src="https://oss.xiaoxiongcode.com/static/home/3.gif" mode="heightFix"
+      class="!absolute bottom-0 left-0 !w-[44vh] !h-[55vh] z-[1]" />
+    <view>
+
+    </view>
+    <!-- 顶部右侧光标签 -->
+    <view class="light-tag" @tap="visible = true">
+      <image class="light-icon" v-if="catalog?.fileList?.[0]?.url" :src="config.baseUrl + catalog?.fileList?.[0]?.url">
+      </image>
+      <text class="light-text">{{ catalog?.name }}</text>
+      <cl-icon name="arrow-left-right-line" color="primary"></cl-icon>
+    </view>
+    <view class="boxs">
+      <scroll-view class="scroll-view_H" direction="horizontal" :show-scrollbar="false" ref="cardsScrollView"
+        @scroll="debouncedOnScroll">
+        <view class="scroll-view-item_H bg-[white]" v-for="course in courseList || []"
+          :class="`category-${course.catalogId}`" :key="course.id" @tap="handleDetail(course)">
+          <cl-image :src="course?.ossIconPath" mode="heightFix"
+            class="!w-full !h-[26vh] mb-[2px] rounded-xl"></cl-image>
+          <cl-text ellipsis :pt="{
+            className: '!text-[4vh] !font-bold'
+          }">{{
+            course.sortNum }}、{{
+              course.mainTitle }}</cl-text>
+          <cl-text ellipsis color="#666" :pt="{
+            className: '!text-[3vh]'
+          }">{{
+            course.assistantTitle }}</cl-text>
+          <!-- <view>
+            <Progress :progress="30" />
+          </view> -->
+          <Lock v-if="userInfo?.memberLevel === 'default'" :record="course" type="vip" />
+        </view>
+      </scroll-view>
+    </view>
+    <!-- <view class="footer">
+      <view>
+        <cl-image src="https://oss.xiaoxiongcode.com/static/home/4.png" mode="heightFix"
+          class=" !h-[40px] mb-[2px] rounded-xl"></cl-image>
+        <text class="text-[14px] text-white font-bold text-stroke-custom">虚拟实验</text>
+      </view>
+      <view>
+        <cl-image src="https://oss.xiaoxiongcode.com/static/home/5.png" mode="heightFix"
+          class=" !h-[40px] mb-[2px] rounded-xl"></cl-image>
+        <text class="text-[14px] text-white font-bold text-stroke-custom">我的收获</text>
+      </view>
+      <view>
+        <cl-image src="https://oss.xiaoxiongcode.com/static/home/6.png" mode="heightFix"
+          class=" !h-[40px] mb-[2px] rounded-xl"></cl-image>
+        <text class="text-[14px] text-white font-bold text-stroke-custom">学习报告</text>
+      </view>
+    </view> -->
+    <cl-popup v-model="visible" :show-header="false" direction="center" :size="600">
+      <view class="p-4">
+        <cl-row :gutter="0">
+          <cl-col :span="6" v-for="item in dataList || []" :key="item.id" :pt="{
+            className: '!p-2'
+          }" @tap="handleSelect(item)">
+            <view class="select-item" :class="{ selected: item.id === catalog?.id }">
+              <image :src="config.baseUrl + item?.fileList?.[0]?.url" class="w-[30rpx] h-[30rpx] mb-[2px]"></image>
+              <text>{{ item.name }}</text>
+            </view>
+          </cl-col>
+        </cl-row>
+      </view>
+    </cl-popup>
+  </cl-page>
+</template>
+
+
+
+<style lang="scss" scoped>
+.boxs {
+  @apply w-[calc(100vw-52vh)] h-[50vh] absolute top-1/2 left-[50vh] z-[1];
+  transform: translateY(-50%);
+}
+
+.scroll-view_H {
+  width: 100%;
+  height: 100%;
+  flex-direction: row;
+}
+
+.scroll-view-item_H {
+  @apply w-[40vh] h-[45vh] mr-[20px] rounded-2xl border-[5px] border-[#1D4BD9] border-solid border-b-[10px] p-1 flex items-center justify-between pb-[20px] relative;
+}
+
+.light-tag {
+  @apply absolute top-3 left-1/2 z-[1] flex flex-row items-center bg-white px-3 py-2 font-bold rounded-full shadow-md;
+  transform: translateX(-50%);
+
+  .light-icon {
+    width: 20px;
+    height: 20px;
+    margin-right: 3px;
+  }
+
+  .light-text {
+    font-size: 16px;
+    min-width: 100px;
+    padding-right: 5px;
+  }
+}
+
+.select-item {
+  @apply flex items-center justify-center rounded-xl border-[3px] border-[#1D4BD9] border-solid border-b-[5px] px-4 py-2 font-bold;
+}
+
+.selected {
+  @apply border-green-500;
+}
+
+.footer {
+  @apply absolute bottom-2 right-5 z-[1] flex flex-row items-center justify-center gap-4;
+}
+
+.text-stroke-custom {
+  color: white;
+  text-shadow:
+    /* 左上角投影 */
+    -1px -1px 0 #1D4BD9,
+    /* 右上角投影 */
+    1px -1px 0 #1D4BD9,
+    /* 左下角投影 */
+    -1px 1px 0 #1D4BD9,
+    /* 右下角投影 */
+    1px 1px 0 #1D4BD9;
+
+}
+</style>

+ 173 - 175
pages/math/index.uvue

@@ -1,221 +1,219 @@
-<script lang="ts" setup>
-import { user } from '@/.cool'
-import { ref, onMounted, computed } from 'vue'
-import { fetchSubjectAppInfo } from '@/services/subject/info'
-import type { SubjectCatalogResult } from '@/services/subject/catalog'
-import type { SubjectCourseResult } from '@/services/subject/course'
-
-import Progress from '../catalog/components/progress.uvue'
+<script setup lang='ts'>
 import Back from '@/components/back.uvue'
-import Lock from '@/components/lock.uvue'
 import Loading from '@/components/loading.uvue'
+import { ref, onMounted, nextTick } from 'vue'
+import { type SubjectCatalogResult, querySubjectCatalog } from '@/services/subject/catalog'
+import { type SubjectCourseResult, getSubjectCoursePage } from '@/services/subject/course'
 import { config } from '@/config'
-import { router } from "@/.cool";
 import { dict } from '@/.cool/store'
+
 const isLoading = ref(true)
-const visible = ref<boolean>(false)
 const dataList = ref<SubjectCatalogResult[]>([])
 const catalog = ref<SubjectCatalogResult>()
-const record = ref<any>()
 const courseList = ref<SubjectCourseResult[]>()
+const cardsScrollView = ref<any>(null)
+const pageNum = ref(1)
 async function getDataList() {
   const id = dict.getValueByLabelMapByType('index_subject_id')['数学']
-  const res = await fetchSubjectAppInfo({ id, delFlag: false, })
-  record.value = res
-  dataList.value = res.catalogList || []
-  catalog.value = res?.catalogList?.[0]
-  courseList.value = res?.courseList || []
+  const res = await querySubjectCatalog({
+    subjectId: id, dataScope: {
+      sortBy: 'asc',
+      sortName: 'sortNum',
+    }
+  })
+  dataList.value = res || []
+  catalog.value = res?.[0] as SubjectCatalogResult
+  handleSelect(catalog.value)
 }
-onMounted(async () => {
-  try {
-    await getDataList()
-    isLoading.value = false
-  } catch (err) {
-    console.log(err);
-    isLoading.value = false
-  }
-})
-const cardsScrollView = ref<any>(null)
-const scrollLeft = ref<any>(0)
 async function handleSelect(val: SubjectCatalogResult) {
   catalog.value = val
-  visible.value = false
-  uni.createSelectorQuery().select(`.category-${val.id}`).boundingClientRect().exec(async (rect) => {
-    if (cardsScrollView.value && rect[0]) {
-      cardsScrollView.value.scrollTo({
-        left: scrollLeft.value + rect[0].left - dict.getWindowHeight() / 2, // 减去顶部偏移
-        animated: true
-      })
-    }
+  pageNum.value = 1
+  await getInfo()
+  cardsScrollView.value.scrollTo({
+    top: 0, // 减去顶部偏移
+    animated: true
   })
 }
-function debouncedOnScroll(e: any) {
-  console.log(e);
-  scrollLeft.value = e.detail.scrollLeft
-  dataList.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.left <= (dict.getWindowHeight() / 2) && rect.left > 0) {
-          catalog.value = category
-          return
-        }
-      }
-    })
+async function getInfo() {
+  const res = await getSubjectCoursePage({
+    catalogId: catalog.value?.id, pageNum: pageNum.value, dataScope: {
+      sortBy: 'asc',
+      sortName: 'sort_num',
+    },
   })
+  courseList.value = res.rows || []
 }
-function handleDetail(item: SubjectCatalogResult) {
-  // if (!item.payFlag && !item.trialPlay) {
-  //   uni.showToast({
-  //     title: '请先购买',
-  //     icon: 'none'
-  //   })
-  //   return
-  // }
-  router.push({
-    path: "/pages/english/detail",
-    query: {
-      id: item.id,
-    }
-  });
+async function handleScrollToLower() {
+
 }
-const userInfo = computed(() => user.info.value?.userInfo)
+onMounted(async () => {
+  await getDataList()
+  isLoading.value = false
+})
 </script>
-
 <template>
   <Loading v-show="isLoading" />
   <cl-page v-show="!isLoading">
     <Back />
+    <!-- 顶部标题栏 -->
     <image mode="aspectFill" src="https://oss.xiaoxiongcode.com/static/home/math-bg.png" alt=""
       class="w-full h-full object-cover" />
-    <!-- 精灵图动画 -->
-    <cl-image src="https://oss.xiaoxiongcode.com/static/home/3.gif" mode="heightFix"
-      class="!absolute bottom-0 left-0 !w-[44vh] !h-[55vh] z-[1]" />
-    <view>
-
-    </view>
-    <!-- 顶部右侧光标签 -->
-    <view class="light-tag" @tap="visible = true">
-      <image class="light-icon" v-if="catalog?.fileList?.[0]?.url" :src="config.baseUrl + catalog?.fileList?.[0]?.url">
-      </image>
-      <text class="light-text">{{ catalog?.name }}</text>
-      <cl-icon name="arrow-left-right-line" color="primary"></cl-icon>
-    </view>
-    <view class="boxs">
-      <scroll-view class="scroll-view_H" direction="horizontal" :show-scrollbar="false" ref="cardsScrollView"
-        @scroll="debouncedOnScroll">
-        <view class="scroll-view-item_H bg-[white]" v-for="course in courseList || []"
-          :class="`category-${course.catalogId}`" :key="course.id" @tap="handleDetail(course)">
-          <cl-image :src="course?.ossIconPath" mode="heightFix"
-            class="!w-full !h-[26vh] mb-[2px] rounded-xl"></cl-image>
-          <cl-text ellipsis :pt="{
-            className: '!text-[4vh] !font-bold'
-          }">{{
-            course.sortNum }}、{{
-              course.mainTitle }}</cl-text>
-          <cl-text ellipsis color="#666" :pt="{
-            className: '!text-[3vh]'
-          }">{{
-            course.assistantTitle }}</cl-text>
-          <!-- <view>
-            <Progress :progress="30" />
-          </view> -->
-          <Lock v-if="userInfo?.memberLevel === 'default'" :record="course" type="vip" />
-        </view>
-      </scroll-view>
-    </view>
-    <!-- <view class="footer">
-      <view>
-        <cl-image src="https://oss.xiaoxiongcode.com/static/home/4.png" mode="heightFix"
-          class=" !h-[40px] mb-[2px] rounded-xl"></cl-image>
-        <text class="text-[14px] text-white font-bold text-stroke-custom">虚拟实验</text>
+    <view class="content">
+      <!-- 左侧导航菜单 -->
+      <view class="w-[20vw] h-[100vh] bg-[#116FE988] pt-[70px] pb-[20px] px-[20px]">
+        <scroll-view direction="vertical" :show-scrollbar="false" class="sidebar" @scrolltolower="handleScrollToLower">
+          <view v-for="category in dataList" :key="category.id" class="sidebar-item"
+            :class="{ active: catalog?.id === category.id }" @tap="handleSelect(category)">
+            <!-- <cl-image :src="category?.ossIconPath" mode="heightFix" class="!h-[28px]"></cl-image> -->
+            {{ category.name }}
+          </view>
+        </scroll-view>
       </view>
-      <view>
-        <cl-image src="https://oss.xiaoxiongcode.com/static/home/5.png" mode="heightFix"
-          class=" !h-[40px] mb-[2px] rounded-xl"></cl-image>
-        <text class="text-[14px] text-white font-bold text-stroke-custom">我的收获</text>
-      </view>
-      <view>
-        <cl-image src="https://oss.xiaoxiongcode.com/static/home/6.png" mode="heightFix"
-          class=" !h-[40px] mb-[2px] rounded-xl"></cl-image>
-        <text class="text-[14px] text-white font-bold text-stroke-custom">学习报告</text>
-      </view>
-    </view> -->
-    <cl-popup v-model="visible" :show-header="false" direction="center" :size="600">
-      <view class="p-4">
-        <cl-row :gutter="0">
-          <cl-col :span="6" v-for="item in dataList || []" :key="item.id" :pt="{
-            className: '!p-2'
-          }" @tap="handleSelect(item)">
-            <view class="select-item" :class="{ selected: item.id === catalog?.id }">
-              <image :src="config.baseUrl + item?.fileList?.[0]?.url" class="w-[30rpx] h-[30rpx] mb-[2px]"></image>
-              <text>{{ item.name }}</text>
+      <!-- 右侧卡片网格 -->
+      <view class="cards-container">
+        <view class="header">
+          {{ catalog?.name }}
+        </view>
+        <scroll-view direction="vertical" :show-scrollbar="false" class="cards" ref="cardsScrollView"
+          @scroll="onScroll">
+          <view class="grid grid-cols-4 gap-4">
+            <view class="card" v-for="card in courseList" :key="card.id" :class="`category-${card.catalogId}`"
+              ref="categoryRefs">
+              <view class="w-full h-[11vw] rounded-xl overflow-hidden">
+                <image :src="card?.ossIconPath" mode="aspectFill" class="w-full h-full object-cover"></image>
+              </view>
+              <cl-text ellipsis :pt="{
+                className: '!text-[1.5vw] !font-bold'
+              }">{{
+                card.sortNum }}、{{
+                  card.mainTitle }}</cl-text>
+              <cl-text ellipsis color="#666" :pt="{
+                className: '!text-[1.2vw]'
+              }">{{
+                card.assistantTitle }}</cl-text>
             </view>
-          </cl-col>
-        </cl-row>
+          </view>
+        </scroll-view>
       </view>
-    </cl-popup>
+    </view>
   </cl-page>
 </template>
-
-
-
 <style lang="scss" scoped>
-.boxs {
-  @apply w-[calc(100vw-52vh)] h-[50vh] absolute top-1/2 left-[50vh] z-[1];
-  transform: translateY(-50%);
+.header {
+  @apply absolute left-1/2 top-5 text-[#333] font-bold text-xl px-4 py-1 rounded-full;
+  transform: translateX(-50%);
+  text-align: center;
+  background-color: #fff;
+
+  .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;
+      }
+    }
+  }
 }
 
-.scroll-view_H {
+.content {
+  display: flex;
+  flex-direction: row;
+  position: absolute;
+  left: 0;
+  top: 0;
   width: 100%;
   height: 100%;
-  flex-direction: row;
-}
-
-.scroll-view-item_H {
-  @apply w-[40vh] h-[45vh] mr-[20px] rounded-2xl border-[5px] border-[#1D4BD9] border-solid border-b-[10px] p-1 flex items-center justify-between pb-[20px] relative;
-}
 
-.light-tag {
-  @apply absolute top-3 left-1/2 z-[1] flex flex-row items-center bg-white px-3 py-2 font-bold rounded-full shadow-md;
-  transform: translateX(-50%);
+  .sidebar {
+    height: calc(100vh - 90px);
+
+    .sidebar-item {
+      @apply text-white rounded-full py-1 px-2 cursor-pointer flex flex-row items-center;
+      margin-bottom: 8px;
+      transition: all 0.3s ease;
+      font-size: 1.5vw;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      width: 100%;
+
+      &.active {
+        background-color: rgba(255, 255, 255, 1);
+        color: #1E88E5;
+        font-weight: bold;
+      }
 
-  .light-icon {
-    width: 20px;
-    height: 20px;
-    margin-right: 3px;
+      .sidebar-icon {
+        font-size: 28px;
+        margin-right: 2px;
+      }
+    }
   }
 
-  .light-text {
-    font-size: 16px;
-    min-width: 100px;
-    padding-right: 5px;
-  }
-}
+  .cards-container {
+    @apply relative;
+    flex: 1;
+    width: 100%;
+    padding: 20px;
+    padding-top: 70px;
+    // background: linear-gradient(0deg, #2EB2FD, #0B85F4);
 
-.select-item {
-  @apply flex items-center justify-center rounded-xl border-[3px] border-[#1D4BD9] border-solid border-b-[5px] px-4 py-2 font-bold;
-}
+    .cards {
+      height: calc(100vh - 90px);
+    }
 
-.selected {
-  @apply border-green-500;
-}
+    .category-section {
+      margin-bottom: 40px;
 
-.footer {
-  @apply absolute bottom-2 right-5 z-[1] flex flex-row items-center justify-center gap-4;
-}
+      .category-title {
+        @apply text-white font-bold text-xl mb-4;
+      }
+    }
 
-.text-stroke-custom {
-  color: white;
-  text-shadow:
-    /* 左上角投影 */
-    -1px -1px 0 #1D4BD9,
-    /* 右上角投影 */
-    1px -1px 0 #1D4BD9,
-    /* 左下角投影 */
-    -1px 1px 0 #1D4BD9,
-    /* 右下角投影 */
-    1px 1px 0 #1D4BD9;
+    .card {
+      @apply rounded-2xl border-[3px] border-[#1D4BD9] border-solid border-b-[6px] relative p-1 bg-white pb-3;
+      // background-color: rgba(255, 255, 255, 0.9);
+      // border-radius: 16px;
+      // padding: 16px;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: space-between;
+      // box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+      aspect-ratio: 8/9;
+
+      .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>