Kaynağa Gözat

适配视频课程页面

shmily1213 16 saat önce
ebeveyn
işleme
e4cc9cacd3

+ 31 - 1
src/api/modules/study.ts

@@ -1,6 +1,6 @@
 import { ApiResponse, ApiResponseList } from "@/types";
 import flyio from "../flyio";
-import type { Batch, ClassKnowledgeRecord, DirectedSchool, Examinee, ExamPaper, ExamPaperSubmit, FavoriteQuestion, FavoriteQuestionListRequestDTO, GetExamPaperRequestDTO, Knowledge, KnowledgeListRequestDTO, KnowledgeRecord, OpenExamineeRequestDTO, PaperWork, PaperWorkRecord, PaperWorkRecordDetail, PaperWorkRecordQuery, PracticeHistory, PracticeRecord, SimulatedRecord, SimulationExamSubject, SimulationTestInfo, StudentExamRecord, StudentPlanStudyRecord, StudentSubject, StudentVideoRecord, StudyPlan, Subject, SubjectListRequestDTO, TeachClass, VideoStudy, WrongBookQuestion, WrongBookQuestionRequestDTO } from "@/types/study";
+import type { Batch, ClassKnowledgeRecord, DirectedSchool, Examinee, ExamPaper, ExamPaperSubmit, FavoriteQuestion, FavoriteQuestionListRequestDTO, GetExamPaperRequestDTO, Knowledge, KnowledgeListRequestDTO, KnowledgeRecord, OpenExamineeRequestDTO, PaperWork, PaperWorkRecord, PaperWorkRecordDetail, PaperWorkRecordQuery, PracticeHistory, PracticeRecord, SimulatedRecord, SimulationExamSubject, SimulationTestInfo, StudentExamRecord, StudentPlanStudyRecord, StudentSubject, StudentVideoRecord, StudyPlan, Subject, SubjectListRequestDTO, TeachClass, VideoCourse, VideoCourseKnowledge, VideoCourseRequestDTO, VideoCourseSubject, VideoCourseSubjectRequestDTO, VideoStudy, WrongBookQuestion, WrongBookQuestionRequestDTO } from "@/types/study";
 import { EnumPaperWorkState } from "@/common/enum";
 
 /**
@@ -350,3 +350,33 @@ export function getWrongBookList(params: WrongBookQuestionRequestDTO) {
 export function deleteWrongQuestion(questionId: number) {
   return flyio.post('/front/v2/wrongBook/deleteWrongQuestion', null, { params: { questionId } }) as Promise<ApiResponse<any>>;
 }
+
+
+/**
+ * 获取视频课程科目列表
+ * @param params 
+ * @returns 
+ */
+export function getVideoCourseSubjects(params: VideoCourseSubjectRequestDTO) {
+  return flyio.get('/front/videoCourse/subjects', params) as Promise<ApiResponseList<VideoCourseSubject>>;
+}
+
+/**
+ * 获取视频课程知识点
+ * @param params 
+ * @returns 
+ */
+export function getVideoCourseKnowledges(subject: number) {
+  return flyio.get('/front/videoCourse/knowledges', { subject }) as Promise<ApiResponseList<VideoCourseKnowledge[]>>;
+}
+
+/**
+ * 获取视频课程列表
+ * @param params 
+ * @returns 
+ */
+export function getVideoCourseList(params: VideoCourseRequestDTO) {
+  return flyio.get('/front/videoCourse/video/list', params) as Promise<ApiResponseList<VideoCourse>>;
+}
+
+

+ 5 - 1
src/common/routes.ts

@@ -95,7 +95,11 @@ export const routes = {
   /**
    * 课程学习
    */
-  pageCourseStudy: '/pagesOther/pages/video-center/index/index',
+  pageCourseStudy: '/pagesStudy/pages/video/index/index',
+  /**
+ * 视频播放
+ */
+  pageCourseVideoPlay: '/pagesStudy/pages/video/play/play',
   /**
    * 组卷作业
    */

+ 2 - 2
src/composables/useQuestionBook.ts

@@ -61,7 +61,7 @@ const useQuestionBook = () => {
    */
   const cancelCollect = (id: number): Promise<boolean> => {
     return new Promise((resolve, reject) => {
-      uni.$ie.showConfirm({
+      uni.$ie.showModal({
         title: '提示',
         content: '确定取消收藏吗?',
       }).then(async confirm => {
@@ -99,7 +99,7 @@ const useQuestionBook = () => {
   }
   const deleteWrongQuestion = (id: number): Promise<boolean> => {
     return new Promise(async (resolve, reject) => {
-      uni.$ie.showConfirm({
+      uni.$ie.showModal({
         title: '提示',
         content: '确定删除错题吗?',
       }).then(async confirm => {

+ 110 - 107
src/main.ts

@@ -9,7 +9,7 @@ import './preload'
 import tool from '@/utils/uni-tool'
 import * as Pinia from 'pinia';
 import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
-import {useImage} from '@/hooks/useImage';
+import { useImage } from '@/hooks/useImage';
 
 // #ifndef VUE3
 import Vue from 'vue'
@@ -19,127 +19,130 @@ Vue.config.productionTip = false
 Vue.use(uvUiTools)
 App.mpType = 'app'
 const app = new Vue({
-    ...App
+  ...App
 })
 app.$mount()
 // #endif
 
 // #ifdef VUE3
-import {createSSRApp} from 'vue'
+import { createSSRApp } from 'vue'
 import "./static/style/tailwind.scss";
 
 export function createApp() {
-    const app = createSSRApp(App)
-    app.use(uvUiTools)
+  const app = createSSRApp(App)
+  app.use(uvUiTools)
 
-    uni.$ie = tool;
+  uni.$ie = tool;
 
-    uni.$uv.setConfig({
-        props: {
-            loadingPage: {
-                loadingText: {default: ''},
-                image: {default: '/static/logo/loading1.gif'},
-                class: {default: 'mx-loading-page'}
-            },
-            navbar: {
-                placeholder: {default: true},
-                clickHover: {default: true},
-                statusBarHeight: {default: 0}
-            },
-            statusBar: {
-                statusBarHeight: {default: 0}
-            },
-            tabs: {
-                activeStyle: {default: () => ({color: 'var(--primary-color)'})}
-            },
-            steps: {
-                activeColor: {default: 'var(--primary-color)'}
-            },
-            search: {
-                color: {default: 'var(--main-color)'},
-                actionStyle: {default: () => ({color: 'var(--primary-color)'})}
-            },
-            empty: {
-                icon: {default: '/static/icon-empty.png'},
-                height: {default: 140},
-                width: {default: 140},
-                text: {default: '暂无相关数据'}
-            },
-            icon: {
-                customClass: {
-                    default: ''
-                }
-            },
-            popup: {
-                theme: {
-                    default: 'theme-ie'
-                }
-            },
-            image: {
-                customClass: {
-                    default: ''
-                }
-            },
-            cell: {
-                disableHover: {
-                    default: false
-                }
-            },
-            collapseItem: {
-                padding: {
-                    default: '12px 15px;'
-                }
-            },
-            input: {
-                fontSize: {default: '30rpx'},
-                disabledColor: {default: 'var(--back-light)'},
-                customStyle: {
-                    default: () => ({
-                        height: '30px',
-                        paddingLeft: '40rpx',
-                        paddingRight: '40rpx',
-                        borderRadius: '24rpx'
-                    })
-                }
-            }
+  uni.$uv.setConfig({
+    props: {
+      loadingPage: {
+        loadingText: { default: '' },
+        image: { default: '/static/logo/loading1.gif' },
+        class: { default: 'mx-loading-page' }
+      },
+      navbar: {
+        placeholder: { default: true },
+        clickHover: { default: true },
+        statusBarHeight: { default: 0 }
+      },
+      statusBar: {
+        statusBarHeight: { default: 0 }
+      },
+      tabs: {
+        activeStyle: { default: () => ({ color: 'var(--primary-color)' }) }
+      },
+      steps: {
+        activeColor: { default: 'var(--primary-color)' }
+      },
+      search: {
+        color: { default: 'var(--main-color)' },
+        actionStyle: { default: () => ({ color: 'var(--primary-color)' }) }
+      },
+      empty: {
+        icon: { default: '/static/icon-empty.png' },
+        height: { default: 140 },
+        width: { default: 140 },
+        text: { default: '暂无相关数据' }
+      },
+      icon: {
+        customClass: {
+          default: ''
         }
-    })
-
-    const {resolvePath} = useImage();
-    uni.$zp = {
-        config: {
-            'default-page-size': 20,
-            'refresher-title-style': {
-                fontSize: '28rpx'
-            },
-            'loading-more-title-custom-style': {
-                fontSize: '26rpx'
-            },
-            // 底部安全区域以placeholder形式实现
-            'use-safe-area-placeholder': true
-            // 'empty-view-img-style': {
-            //   width: '364rpx',
-            //   height: '252rpx'
-            // },
-            // 'empty-view-img': resolvePath('/pagesStudy/static/image/icon-empty.png'),
-            // 'empty-view-title-style': {
-            //   color: '#B3B3B3',
-            //   fontSize: '30rpx',
-            //   marginTop: '40rpx'
-            // },
-            // 'empty-view-style': {
-            //   marginTop: '-200rpx'
-            // }
+      },
+      popup: {
+        theme: {
+          default: 'theme-ie'
+        }
+      },
+      image: {
+        customClass: {
+          default: ''
+        }
+      },
+      cell: {
+        disableHover: {
+          default: false
+        }
+      },
+      collapseItem: {
+        padding: {
+          default: '12px 15px;'
+        },
+        data: { default: null },
+        lazy: { default: false },
+        load: { default: null }
+      },
+      input: {
+        fontSize: { default: '30rpx' },
+        disabledColor: { default: 'var(--back-light)' },
+        customStyle: {
+          default: () => ({
+            height: '30px',
+            paddingLeft: '40rpx',
+            paddingRight: '40rpx',
+            borderRadius: '24rpx'
+          })
         }
+      }
     }
+  })
 
-    const pinia = Pinia.createPinia();
-    app.use(pinia);
-    pinia.use(piniaPluginPersistedstate);
-
-    return {
-        app
+  const { resolvePath } = useImage();
+  uni.$zp = {
+    config: {
+      'default-page-size': 20,
+      'refresher-title-style': {
+        fontSize: '28rpx'
+      },
+      'loading-more-title-custom-style': {
+        fontSize: '26rpx'
+      },
+      // 底部安全区域以placeholder形式实现
+      'use-safe-area-placeholder': true
+      // 'empty-view-img-style': {
+      //   width: '364rpx',
+      //   height: '252rpx'
+      // },
+      // 'empty-view-img': resolvePath('/pagesStudy/static/image/icon-empty.png'),
+      // 'empty-view-title-style': {
+      //   color: '#B3B3B3',
+      //   fontSize: '30rpx',
+      //   marginTop: '40rpx'
+      // },
+      // 'empty-view-style': {
+      //   marginTop: '-200rpx'
+      // }
     }
+  }
+
+  const pinia = Pinia.createPinia();
+  app.use(pinia);
+  pinia.use(piniaPluginPersistedstate);
+
+  return {
+    app
+  }
 }
 
 // #endif

+ 12 - 0
src/pages.json

@@ -387,6 +387,18 @@
           "style": {
             "navigationBarTitleText": ""
           }
+        },
+        {
+          "path": "pages/video/index/index",
+          "style": {
+            "navigationBarTitleText": ""
+          }
+        },
+        {
+          "path": "pages/video/play/play",
+          "style": {
+            "navigationBarTitleText": ""
+          }
         }
       ]
     }

+ 0 - 4
src/pagesStudy/components/question-book-item.vue

@@ -68,10 +68,6 @@ import type { Study } from '@/types';
 
 
 const props = defineProps({
-  showKnowledge: {
-    type: Boolean,
-    default: false
-  },
   showAnswer: {
     type: Boolean,
     default: false

+ 102 - 0
src/pagesStudy/pages/video/index/index.vue

@@ -0,0 +1,102 @@
+<template>
+  <ie-page>
+    <z-paging ref="paging" v-model="knowledgeList" :auto="false" :loading-more-enabled="false"
+      :safe-area-inset-bottom="true" :hide-no-more-by-limit="10" @query="handleQuery">
+      <template #top>
+        <ie-navbar title="视频课程" />
+        <uv-tabs :current="current" keyName="label" :list="subjects" :scrollable="false" @change="handleTabChange" />
+        <uv-line margin="0" />
+      </template>
+      <view class="h-[8px] bg-back" />
+      <uv-collapse ref="collapse" :border="false">
+        <uv-collapse-item v-for="item in knowledgeList" :key="item.code" :data="item" :lazy="true" :load="handleLoad">
+          <template #title="{ expanded, loading }">
+            <view class="flex items-center justify-between">
+              <view class="flex items-center gap-10">
+                <uv-icon name="arrow-right" size="14" color="#888" custom-class="transition-transform duration-300"
+                  :class="{ 'rotate-90': expanded }" />
+                <uv-loading-icon v-if="loading" mode="spinner" size="16"></uv-loading-icon>
+                <view>{{ item.label }}</view>
+              </view>
+            </view>
+          </template>
+          <template #right-icon>
+            <view></view>
+          </template>
+          <template #default="{ expanded }">
+            <view class="pl-20">
+              <view v-for="child in item.children" :key="child.name" class="flex items-center gap-20 mb-20">
+                <ie-image :src="child.img" custom-class="w-100 h-64" />
+                <view class="flex-1 min-w-1 ellipsis-1 text-28 text-fore-title">{{ child.name }}</view>
+                <view
+                  class="px-20 py-8 border border-solid border-primary rounded-full text-24 text-primary flex items-center gap-8"
+                  @click.stop="handlePlay(item, child)">
+                  <uv-icon name="play-circle" size="16" color="var(--primary-color)" />
+                  <text>播放</text>
+                </view>
+              </view>
+            </view>
+          </template>
+        </uv-collapse-item>
+      </uv-collapse>
+    </z-paging>
+  </ie-page>
+</template>
+
+<script lang="ts" setup>
+import { getVideoCourseSubjects, getVideoCourseKnowledges, getVideoCourseList } from '@/api/modules/study';
+import type { Study } from '@/types';
+import { useTransferPage } from '@/hooks/useTransferPage';
+
+const { transferTo, routes } = useTransferPage();
+const paging = ref<ZPagingInstance>();
+const subjects = ref<Study.VideoCourseSubject[]>([]);
+const current = ref(0);
+const knowledgeList = ref<Study.VideoCourseKnowledge[]>([]);
+
+const handleQuery = (page: number, size: number) => {
+  getVideoCourseKnowledges(subjects.value[current.value].code).then(res => {
+    paging.value?.completeByNoMore(res.rows, true);
+  });
+}
+const handleTabChange = (e: any) => {
+  current.value = e.index;
+  paging.value?.reload();
+}
+const handlePlay = (parent: Study.VideoCourseKnowledge, data: Study.VideoCourse) => {
+  transferTo(routes.pageCourseVideoPlay, {
+    data: {
+      knowledge: parent,
+      video: data,
+    }
+  });
+}
+const handleLoad = async (data: Study.VideoCourseKnowledge, callback: () => {}) => {
+  const queryParams = {
+    pageNum: 1,
+    pageSize: 1000,
+    subject: subjects.value[current.value].code,
+    knowledge: data.code,
+  } as Study.VideoCourseRequestDTO;
+  getVideoCourseList(queryParams).then(res => {
+    data.children = res.rows;
+    callback();
+  });
+}
+const loadData = async () => {
+  const queryParams = {
+    pageNum: 1,
+    pageSize: 1000,
+    type: 1,
+  } as Study.VideoCourseSubjectRequestDTO;
+  getVideoCourseSubjects(queryParams).then(res => {
+    subjects.value = res.rows;
+    setTimeout(() => {
+      paging.value?.reload();
+    }, 100);
+  });
+}
+onLoad(() => {
+  loadData();
+});
+</script>

+ 30 - 0
src/pagesStudy/pages/video/play/play.vue

@@ -0,0 +1,30 @@
+<template>
+  <ie-page>
+    <ie-navbar :title="pageTitle" />
+  </ie-page>
+</template>
+
+<script lang="ts" setup>
+import { useTransferPage } from '@/hooks/useTransferPage';
+import type { Study } from '@/types';
+
+const { prevData, transferBack } = useTransferPage();
+const currentVideo = ref < Study.VideoCourse > ();
+
+const videoList = computed(() => {
+  const { knowledge } = prevData.value as { knowledge: Study.VideoCourseKnowledge };
+  return knowledge.children;
+});
+const videoIndex = computed(() => {
+  return videoList.value.findIndex(item => item.aliId === currentVideo.value?.aliId);
+});
+const pageTitle = computed(() => {
+  return videoList.value[videoIndex.value].name;
+});
+
+onLoad(() => {
+  currentVideo.value = prevData.value.video;
+});
+</script>
+
+<style lang="scss"></style>

+ 4 - 2
src/pagesStudy/pages/wrong-book/wrong-book.vue

@@ -122,10 +122,12 @@ const handleChange = (e: any) => {
 const loadData = () => {
   getStudentSubject().then(res => {
     tabs.value = res.data;
-    paging.value?.reload();
+    setTimeout(() => {
+      paging.value?.reload();
+    }, 0);
   });
 }
-onLoad(() => {
+onMounted(() => {
   loadData();
 });
 </script>

+ 42 - 0
src/types/study.ts

@@ -608,3 +608,45 @@ export interface WrongBookQuestion {
   options: string[];
   answers: string[];
 }
+
+/**
+ * 视频课程科目请求参数
+ */
+export interface VideoCourseSubjectRequestDTO {
+  pageNum: number;
+  pageSize: number;
+  type: number;
+}
+/**
+ * 视频课程科目
+ */
+export interface VideoCourseSubject {
+  code: number;
+  label: string;
+}
+/**
+ * 视频课程知识点
+ */
+export interface VideoCourseKnowledge {
+  code: number;
+  label: string;
+  children: VideoCourse[];
+}
+/**
+ * 视频课程请求参数
+ */
+export interface VideoCourseRequestDTO {
+  pageNum: number;
+  pageSize: number;
+  subject: number;
+  knowledge: number;
+}
+/**
+ * 视频课程
+ */
+export interface VideoCourse {
+  aliId: string;
+  aliIdType: number;
+  img: string;
+  name: string;
+}

+ 19 - 3
src/uni_modules/uv-collapse/components/uv-collapse-item/uv-collapse-item.vue

@@ -8,14 +8,14 @@
 			:isLink="isLink"
 			:clickable="clickable"
 			:border="false"
-			@click="clickHandler"
+			@click="loadContent"
 			:arrowDirection="expanded ? 'up' : 'down'"
 			:disabled="disabled"
 		>
 			<!-- Vue 3 插槽语法,支持所有平台包括微信小程序 -->
 			<template v-slot:title>
 				<view>
-					<slot name="title" :expanded="expanded"></slot>
+					<slot name="title" :expanded="expanded" :loading="loading"></slot>
 				</view>
 			</template>
 			<template v-slot:icon>
@@ -91,7 +91,8 @@
 				parentData: {
 					accordion: false,
 					border: false
-				}
+				},
+        loading: false,
 			};
 		},
 		watch: {
@@ -195,6 +196,21 @@
 				})
 				// #endif
 			},
+      async loadContent() {
+        if (this.lazy && this.load) {
+          if (!this.inited) {
+            this.loading = true;
+            setTimeout(async () => {
+              await this.load(this.data, this.clickHandler);
+              this.loading = false;
+            }, 300);
+          } else {
+            this.clickHandler();
+          }
+        } else {
+          this.clickHandler();
+        }
+      },
 			// 点击collapsehead头部
 			async clickHandler() {
 				if (this.disabled || this.animating) return