Browse Source

refactor: 重构学科页面,统一分页加载与布局风格

1. 替换旧的课程查询接口为分页接口,实现滚动加载更多
2. 统一所有学科页面的左右布局侧边栏+卡片列表样式
3. 删除冗余的math页面备份文件,整理代码逻辑
4. 修复排序参数名不一致的问题,统一使用sort_num
408249787@qq.com 1 tuần trước cách đây
mục cha
commit
f022c44f0c
6 tập tin đã thay đổi với 640 bổ sung728 xóa
  1. 3 3
      pages/catalog/index.uvue
  2. 201 162
      pages/chinese/index.uvue
  3. 188 163
      pages/english/index.uvue
  4. 0 226
      pages/math/index copy.uvue
  5. 53 10
      pages/math/index.uvue
  6. 195 164
      pages/mix/index.uvue

+ 3 - 3
pages/catalog/index.uvue

@@ -2,7 +2,7 @@
 import { ref, onMounted } 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 { type SubjectCourseResult, getSubjectCoursePage } from '@/services/subject/course'
 import Lock from '@/components/lock.uvue'
 import Progress from './components/progress.uvue'
 import Back from '@/components/back.uvue'
@@ -61,13 +61,13 @@ function handleDetail(item: SubjectCatalogResult) {
 async function handleSelect(val: SubjectCatalogResult) {
   catalog.value = val
   visible.value = false
-  const res = await querySubjectCourse({
+  const res = await getSubjectCoursePage({
     catalogId: val.id, dataScope: {
       sortBy: 'asc',
       sortName: 'sortNum',
     },
   })
-  courseList.value = res || []
+  courseList.value = res.rows || []
   // uni.createSelectorQuery().select(`.category-${val.id}`).boundingClientRect().exec(async (rect) => {
   //   if (cardsScrollView.value && rect[0]) {
   //     cardsScrollView.value.scrollTo({

+ 201 - 162
pages/chinese/index.uvue

@@ -1,53 +1,69 @@
-<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 Progress from '../catalog/components/progress.uvue'
-import type { SubjectCourseResult } from '@/services/subject/course'
+<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, computed } 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'
+import { router, debounce, user } from "@/.cool";
+import Lock from '@/components/lock.uvue'
+
 const isLoading = ref(true)
-const visible = ref<boolean>(false)
 const dataList = ref<SubjectCatalogResult[]>([])
 const catalog = ref<SubjectCatalogResult>()
-const courseList = ref<SubjectCourseResult[]>()
-const record = ref<any>()
+const courseList = ref<SubjectCourseResult[]>([])
+const cardsScrollView = ref<any>(null)
+const pageNum = ref(1)
+const loading = ref(false)
+const finish = ref(false)
 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: 'sort_num',
+    }
+  })
+  dataList.value = res || []
+  catalog.value = res?.[0] as SubjectCatalogResult
+  handleSelect(catalog.value)
 }
-const userInfo = computed(() => user.info.value?.userInfo)
-
-onMounted(async () => {
-  try {
-    await getDataList()
-    isLoading.value = false
-  } catch (err) {
-    console.log(err);
-    isLoading.value = false
-  }
-})
 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
-      })
-    }
+  courseList.value = []
+  finish.value = false
+  pageNum.value = 1
+  await getInfo()
+  cardsScrollView.value.scrollTo({
+    top: 0, // 减去顶部偏移
+    animated: true
   })
 }
+async function getInfo() {
+  loading.value = true
+  const res = await getSubjectCoursePage({
+    catalogId: catalog.value?.id, pageSize: 12, pageNum: pageNum.value, dataScope: {
+      sortBy: 'asc',
+      sortName: 'sort_num',
+    },
+  })
+  courseList.value = [...courseList.value, ...(res.rows || [])]
+  loading.value = false
+  finish.value = res.pages === pageNum.value
+}
+async function handleScrollToLower() {
+  if (loading.value || finish.value) {
+    return
+  }
+  pageNum.value++
+  await getInfo()
+}
+const handleScrollToLowerDebounce = debounce(handleScrollToLower, 1000)
+onMounted(async () => {
+  await getDataList()
+  isLoading.value = false
+})
 function handleDetail(item: SubjectCatalogResult) {
   // if (!item.payFlag && !item.trialPlay) {
   //   uni.showToast({
@@ -63,160 +79,183 @@ function handleDetail(item: SubjectCatalogResult) {
     }
   });
 }
-const cardsScrollView = ref<any>(null)
-const scrollLeft = ref<any>(0)
-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
-        }
-      }
-    })
-  })
-}
+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/语文/图层 4.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]" :class="`category-${course.catalogId}`"
-          v-for="course in courseList || []" :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 class="content">
+      <!-- 左侧导航菜单 -->
+      <view class="w-[20vw] h-[100vh] bg-[#33333388] pt-[70px] pb-[20px] px-[20px]">
+        <scroll-view direction="vertical" :show-scrollbar="false" class="sidebar">
+          <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> -->
+            <cl-text ellipsis :pt="{
+              className: 'we'
+            }"> {{ category.name }}</cl-text>
+          </view>
+        </scroll-view>
       </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="handleScrollToLowerDebounce">
+          <view class="grid grid-cols-4 gap-4">
+            <view class="card" v-for="card in courseList" :key="card.id" :class="`category-${card.catalogId}`"
+              @tap="handleDetail(card)">
+              <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>
+              <Lock v-if="userInfo?.memberLevel === 'default'" :record="card" type="vip" />
             </view>
-          </cl-col>
-        </cl-row>
+          </view>
+          <cl-loadmore :loading="loading" :finish="finish"></cl-loadmore>
+        </scroll-view>
       </view>
-    </cl-popup>
+    </view>
   </cl-page>
 </template>
+<style lang="scss" scoped>
+.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;
 
-<style lang="scss" scoped>
-.boxs {
-  @apply w-[calc(100vw-52vh)] h-[50vh] absolute top-1/2 left-[50vh] z-[1];
-  transform: translateY(-50%);
+      &.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-[10px] relative;
-}
+  .sidebar {
+    height: calc(100vh - 90px);
 
-.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-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;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      width: 100%;
 
-  .light-icon {
-    width: 20px;
-    height: 20px;
-    margin-right: 3px;
-  }
+      .we {
+        color: #fff !important;
+        font-size: 1.6vw !important;
+      }
+
+      &.active {
+        background-color: rgba(255, 255, 255, 1);
+        color: #1E88E5;
+        font-weight: bold;
+
+        .we {
+          color: #333 !important;
+        }
+      }
 
-  .light-text {
-    font-size: 16px;
-    min-width: 100px;
-    padding-right: 5px;
+      .sidebar-icon {
+        font-size: 28px;
+        margin-right: 2px;
+      }
+    }
   }
-}
 
-.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-container {
+    @apply relative;
+    flex: 1;
+    width: 100%;
+    padding: 20px;
+    padding-top: 70px;
+    // background: linear-gradient(0deg, #2EB2FD, #0B85F4);
 
-.selected {
-  @apply border-green-500;
-}
+    .cards {
+      height: calc(100vh - 90px);
+    }
 
-.footer {
-  @apply absolute bottom-2 right-5 z-[1] flex flex-row items-center justify-center gap-4;
-}
+    .category-section {
+      margin-bottom: 40px;
 
-.text-stroke-custom {
-  color: white;
-  text-shadow:
-    /* 左上角投影 */
-    -1px -1px 0 #1D4BD9,
-    /* 右上角投影 */
-    1px -1px 0 #1D4BD9,
-    /* 左下角投影 */
-    -1px 1px 0 #1D4BD9,
-    /* 右下角投影 */
-    1px 1px 0 #1D4BD9;
+      .category-title {
+        @apply text-white font-bold text-xl mb-4;
+      }
+    }
 
+    .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>

+ 188 - 163
pages/english/index.uvue

@@ -1,53 +1,68 @@
-<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, computed } from 'vue'
+import { type SubjectCatalogResult, querySubjectCatalog } from '@/services/subject/catalog'
+import { type SubjectCourseResult, getSubjectCoursePage } from '@/services/subject/course'
 import { config } from '@/config'
 import { dict } from '@/.cool/store'
-import { router } from "@/.cool";
+import { router, debounce, user } from "@/.cool";
+import Lock from '@/components/lock.uvue'
+
 const isLoading = ref(true)
-const visible = ref<boolean>(false)
 const dataList = ref<SubjectCatalogResult[]>([])
 const catalog = ref<SubjectCatalogResult>()
-const record = ref<any>()
-const userInfo = computed(() => user.info.value?.userInfo)
-const courseList = ref<SubjectCourseResult[]>()
+const courseList = ref<SubjectCourseResult[]>([])
+const cardsScrollView = ref<any>(null)
+const pageNum = ref(1)
+const loading = ref(false)
+const finish = ref(false)
 async function getDataList() {
-  const res = await fetchSubjectAppInfo({ id: router.query().id, delFlag: false })
-  record.value = res
-  dataList.value = res.catalogList || []
-  catalog.value = res?.catalogList?.[0]
-  courseList.value = res?.courseList || []
+  const res = await querySubjectCatalog({
+    subjectId: router.query().id, dataScope: {
+      sortBy: 'asc',
+      sortName: 'sort_num',
+    }
+  })
+  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 scrollLeft = ref<any>(0)
-const cardsScrollView = ref<any>(null)
 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
-      })
-    }
+  courseList.value = []
+  finish.value = false
+  pageNum.value = 1
+  await getInfo()
+  cardsScrollView.value.scrollTo({
+    top: 0, // 减去顶部偏移
+    animated: true
+  })
+}
+async function getInfo() {
+  loading.value = true
+  const res = await getSubjectCoursePage({
+    catalogId: catalog.value?.id, pageSize: 12, pageNum: pageNum.value, dataScope: {
+      sortBy: 'asc',
+      sortName: 'sort_num',
+    },
   })
+  courseList.value = [...courseList.value, ...(res.rows || [])]
+  loading.value = false
+  finish.value = res.pages === pageNum.value
+}
+async function handleScrollToLower() {
+  if (loading.value || finish.value) {
+    return
+  }
+  pageNum.value++
+  await getInfo()
 }
+const handleScrollToLowerDebounce = debounce(handleScrollToLower, 1000)
+onMounted(async () => {
+  await getDataList()
+  isLoading.value = false
+})
 function handleDetail(item: SubjectCatalogResult) {
   // if (!item.payFlag && !item.trialPlay) {
   //   uni.showToast({
@@ -63,158 +78,168 @@ function handleDetail(item: SubjectCatalogResult) {
     }
   });
 }
-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
-        }
-      }
-    })
-  })
-}
+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/英语/图层 6.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" ref="cardsScrollView" :show-scrollbar="false"
-        @scroll="debouncedOnScroll">
-        <view class="scroll-view-item_H bg-[white]" :class="`category-${course.catalogId}`"
-          v-for="course in courseList || []" :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 class="content">
+      <!-- 左侧导航菜单 -->
+      <view class="w-[20vw] h-[100vh] bg-[#faaec588] pt-[70px] pb-[20px] px-[20px]">
+        <scroll-view direction="vertical" :show-scrollbar="false" class="sidebar">
+          <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 }} -->
+            <cl-text ellipsis :pt="{
+              className: 'we'
+            }"> {{ category.name }}</cl-text>
+          </view>
+        </scroll-view>
       </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="handleScrollToLowerDebounce">
+          <view class="grid grid-cols-4 gap-4">
+            <view class="card" v-for="card in courseList" :key="card.id" :class="`category-${card.catalogId}`"
+              @tap="handleDetail(card)">
+              <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>
+              <Lock v-if="userInfo?.memberLevel === 'default'" :record="card" type="vip" />
             </view>
-          </cl-col>
-        </cl-row>
+          </view>
+          <cl-loadmore :loading="loading" :finish="finish"></cl-loadmore>
+        </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;
+  white-space: nowrap;
 }
 
-.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;
-}
+  .sidebar {
+    height: calc(100vh - 90px);
 
-.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-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;
 
-  .light-icon {
-    width: 20px;
-    height: 20px;
-    margin-right: 3px;
-  }
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      width: 100%;
+
+      .we {
+        color: #fff !important;
+        font-size: 1.6vw important;
+      }
+
+      &.active {
+        background-color: rgba(255, 255, 255, 1);
+        color: #f3759b;
+        font-weight: bold;
+
+        .we {
+          color: #f3759b !important;
+        }
+      }
 
-  .light-text {
-    font-size: 16px;
-    min-width: 100px;
-    padding-right: 5px;
+      .sidebar-icon {
+        font-size: 28px;
+        margin-right: 2px;
+      }
+    }
   }
-}
 
-.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-container {
+    @apply relative;
+    flex: 1;
+    width: 100%;
+    padding: 20px;
+    padding-top: 70px;
+    // background: linear-gradient(0deg, #2EB2FD, #0B85F4);
 
-.selected {
-  @apply border-green-500;
-}
+    .cards {
+      height: calc(100vh - 90px);
+    }
 
-.footer {
-  @apply absolute bottom-2 right-5 z-[1] flex flex-row items-center justify-center gap-4;
-}
+    .category-section {
+      margin-bottom: 40px;
 
-.text-stroke-custom {
-  color: white;
-  text-shadow:
-    /* 左上角投影 */
-    -1px -1px 0 #1D4BD9,
-    /* 右上角投影 */
-    1px -1px 0 #1D4BD9,
-    /* 左下角投影 */
-    -1px 1px 0 #1D4BD9,
-    /* 右下角投影 */
-    1px 1px 0 #1D4BD9;
+      .category-title {
+        @apply text-white font-bold text-xl mb-4;
+      }
+    }
+
+    .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>

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

@@ -1,226 +0,0 @@
-<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>

+ 53 - 10
pages/math/index.uvue

@@ -1,24 +1,28 @@
 <script setup lang='ts'>
 import Back from '@/components/back.uvue'
 import Loading from '@/components/loading.uvue'
-import { ref, onMounted, nextTick } from 'vue'
+import { ref, onMounted, computed } from 'vue'
 import { type SubjectCatalogResult, querySubjectCatalog } from '@/services/subject/catalog'
 import { type SubjectCourseResult, getSubjectCoursePage } from '@/services/subject/course'
 import { config } from '@/config'
 import { dict } from '@/.cool/store'
+import { router, debounce, user } from "@/.cool";
+import Lock from '@/components/lock.uvue'
 
 const isLoading = ref(true)
 const dataList = ref<SubjectCatalogResult[]>([])
 const catalog = ref<SubjectCatalogResult>()
-const courseList = ref<SubjectCourseResult[]>()
+const courseList = ref<SubjectCourseResult[]>([])
 const cardsScrollView = ref<any>(null)
 const pageNum = ref(1)
+const loading = ref(false)
+const finish = ref(false)
 async function getDataList() {
   const id = dict.getValueByLabelMapByType('index_subject_id')['数学']
   const res = await querySubjectCatalog({
     subjectId: id, dataScope: {
       sortBy: 'asc',
-      sortName: 'sortNum',
+      sortName: 'sort_num',
     }
   })
   dataList.value = res || []
@@ -27,6 +31,8 @@ async function getDataList() {
 }
 async function handleSelect(val: SubjectCatalogResult) {
   catalog.value = val
+  courseList.value = []
+  finish.value = false
   pageNum.value = 1
   await getInfo()
   cardsScrollView.value.scrollTo({
@@ -35,21 +41,45 @@ async function handleSelect(val: SubjectCatalogResult) {
   })
 }
 async function getInfo() {
+  loading.value = true
   const res = await getSubjectCoursePage({
-    catalogId: catalog.value?.id, pageNum: pageNum.value, dataScope: {
+    catalogId: catalog.value?.id, pageSize: 12, pageNum: pageNum.value, dataScope: {
       sortBy: 'asc',
       sortName: 'sort_num',
     },
   })
-  courseList.value = res.rows || []
+  courseList.value = [...courseList.value, ...(res.rows || [])]
+  loading.value = false
+  finish.value = res.pages === pageNum.value
 }
 async function handleScrollToLower() {
-
+  if (loading.value || finish.value) {
+    return
+  }
+  pageNum.value++
+  await getInfo()
 }
+const handleScrollToLowerDebounce = debounce(handleScrollToLower, 1000)
 onMounted(async () => {
   await getDataList()
   isLoading.value = false
 })
+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" />
@@ -61,11 +91,13 @@ onMounted(async () => {
     <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">
+        <scroll-view direction="vertical" :show-scrollbar="false" class="sidebar">
           <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 }}
+            <cl-text ellipsis :pt="{
+              className: 'we'
+            }"> {{ category.name }}</cl-text>
           </view>
         </scroll-view>
       </view>
@@ -75,10 +107,10 @@ onMounted(async () => {
           {{ catalog?.name }}
         </view>
         <scroll-view direction="vertical" :show-scrollbar="false" class="cards" ref="cardsScrollView"
-          @scroll="onScroll">
+          @scroll="handleScrollToLowerDebounce">
           <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">
+              @tap="handleDetail(card)">
               <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>
@@ -91,8 +123,10 @@ onMounted(async () => {
                 className: '!text-[1.2vw]'
               }">{{
                 card.assistantTitle }}</cl-text>
+              <Lock v-if="userInfo?.memberLevel === 'default'" :record="card" type="vip" />
             </view>
           </view>
+          <cl-loadmore :loading="loading" :finish="finish"></cl-loadmore>
         </scroll-view>
       </view>
     </view>
@@ -146,10 +180,19 @@ onMounted(async () => {
       white-space: nowrap;
       width: 100%;
 
+      .we {
+        color: #fff !important;
+        font-size: 1.6vw !important;
+      }
+
       &.active {
         background-color: rgba(255, 255, 255, 1);
         color: #1E88E5;
         font-weight: bold;
+
+        .we {
+          color: #1E88E5 !important;
+        }
       }
 
       .sidebar-icon {

+ 195 - 164
pages/mix/index.uvue

@@ -1,69 +1,76 @@
-<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, computed } 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'
+import { router, debounce, user } from "@/.cool";
+import Lock from '@/components/lock.uvue'
+
 const isLoading = ref(true)
-const visible = ref<boolean>(false)
 const dataList = ref<SubjectCatalogResult[]>([])
 const catalog = ref<SubjectCatalogResult>()
-const record = ref<any>()
-const userInfo = computed(() => user.info.value?.userInfo)
-const courseList = ref<SubjectCourseResult[]>()
+const courseList = ref<SubjectCourseResult[]>([])
+const cardsScrollView = ref<any>(null)
+const pageNum = ref(1)
+const loading = ref(false)
+const finish = ref(false)
 async function getDataList() {
-  const res = await fetchSubjectAppInfo({ id: router.query().id, delFlag: false })
-  record.value = res
-  dataList.value = res.catalogList || []
-  catalog.value = res?.catalogList?.[0]
-  courseList.value = res?.courseList || []
+  const res = await querySubjectCatalog({
+    subjectId: router.query().id, dataScope: {
+      sortBy: 'asc',
+      sortName: 'sort_num',
+    }
+  })
+  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
-      })
-    }
+  courseList.value = []
+  finish.value = false
+  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() {
+  loading.value = true
+  const res = await getSubjectCoursePage({
+    catalogId: catalog.value?.id, pageSize: 12, pageNum: pageNum.value, dataScope: {
+      sortBy: 'asc',
+      sortName: 'sort_num',
+    },
   })
+  courseList.value = [...courseList.value, ...(res.rows || [])]
+  loading.value = false
+  finish.value = res.pages === pageNum.value
+}
+async function handleScrollToLower() {
+  if (loading.value || finish.value) {
+    return
+  }
+  pageNum.value++
+  await getInfo()
 }
+const handleScrollToLowerDebounce = debounce(handleScrollToLower, 1000)
+onMounted(async () => {
+  await getDataList()
+  isLoading.value = false
+})
 function handleDetail(item: SubjectCatalogResult) {
-
+  // if (!item.payFlag && !item.trialPlay) {
+  //   uni.showToast({
+  //     title: '请先购买',
+  //     icon: 'none'
+  //   })
+  //   return
+  // }
   router.push({
     path: "/pages/english/detail",
     query: {
@@ -71,144 +78,168 @@ function handleDetail(item: SubjectCatalogResult) {
     }
   });
 }
+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/语文/图层 4.png" alt=""
+    <!-- 顶部标题栏 -->
+    <image mode="aspectFill" src="https://oss.xiaoxiongcode.com/static/百科/bg.jpg" 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>
-          <text class="text-[3vh] text-[#666]">{{
-            course.assistantTitle }}</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 class="content">
+      <!-- 左侧导航菜单 -->
+      <view class="w-[20vw] h-[100vh] bg-[#a9e3f2bb] pt-[70px] pb-[20px] px-[20px]">
+        <scroll-view direction="vertical" :show-scrollbar="false" class="sidebar">
+          <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 }} -->
+            <cl-text ellipsis :pt="{
+              className: 'we'
+            }"> {{ category.name }}</cl-text>
+          </view>
+        </scroll-view>
       </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="handleScrollToLowerDebounce">
+          <view class="grid grid-cols-4 gap-4">
+            <view class="card" v-for="card in courseList" :key="card.id" :class="`category-${card.catalogId}`"
+              @tap="handleDetail(card)">
+              <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>
+              <Lock v-if="userInfo?.memberLevel === 'default'" :record="card" type="vip" />
             </view>
-          </cl-col>
-        </cl-row>
+          </view>
+          <cl-loadmore :loading="loading" :finish="finish"></cl-loadmore>
+        </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;
+  white-space: nowrap;
 }
 
-.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;
-}
+  .sidebar {
+    height: calc(100vh - 90px);
 
-.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-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;
 
-  .light-icon {
-    width: 20px;
-    height: 20px;
-    margin-right: 3px;
-  }
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      width: 100%;
 
-  .light-text {
-    font-size: 16px;
-    min-width: 100px;
-    padding-right: 5px;
-  }
-}
+      .we {
+        color: #fff !important;
+        font-size: 1.6vw !important;
+      }
 
-.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;
-}
+      &.active {
+        background-color: rgba(255, 255, 255, 1);
+        color: #f3759b;
+        font-weight: bold;
 
-.selected {
-  @apply border-green-500;
-}
+        .we {
+          color: #1899b9 !important;
+        }
+      }
 
-.footer {
-  @apply absolute bottom-2 right-5 z-[1] flex flex-row items-center justify-center gap-4;
-}
+      .sidebar-icon {
+        font-size: 28px;
+        margin-right: 2px;
+      }
+    }
+  }
 
-.text-stroke-custom {
-  color: white;
-  text-shadow:
-    /* 左上角投影 */
-    -1px -1px 0 #1D4BD9,
-    /* 右上角投影 */
-    1px -1px 0 #1D4BD9,
-    /* 左下角投影 */
-    -1px 1px 0 #1D4BD9,
-    /* 右下角投影 */
-    1px 1px 0 #1D4BD9;
+  .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 {
+      @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>