فهرست منبع

Merge branch 'iteration-create' into online

lex 1 سال پیش
والد
کامیت
29ab7e68e5

+ 7 - 1
src/router/routes-common.ts

@@ -152,12 +152,18 @@ export default [
       },
       {
         path: '/co-ai',
+        component: () => import('@/views/co-ai/baseIndex'),
+        meta: {
+          title: 'AI学练'
+        }
+      },
+      {
+        path: '/co-ai-detail',
         component: () => import('@/views/co-ai/index'),
         meta: {
           title: 'AI学练'
         }
       },
-
       {
         path: '/courseware-play',
         component: () => import('@/views/courseware-play/index'),

+ 7 - 0
src/views/co-ai/api.ts

@@ -15,3 +15,10 @@ export const api_musicSheetPage = (params: any) => {
     data: params
   });
 };
+
+/**
+ * 曲谱分类列表
+ */
+export const api_musicTagTree = () => {
+  return request.get('/edu-app/musicTag/tree');
+};

+ 305 - 0
src/views/co-ai/baseIndex.module.less

@@ -0,0 +1,305 @@
+.container {
+  position: relative;
+  // width: 100vw;
+  height: 100vh;
+  overflow: hidden;
+  background: url('../../common/images/icon_bg.png') no-repeat center center / cover;
+  display: flex;
+  flex-direction: column;
+  padding: 61px 18px 18px 37px;
+}
+
+.back {
+  position: fixed;
+  left: 15px;
+  top: 17px;
+  width: 31px;
+  height: 31px;
+
+  &>img {
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+  }
+
+  &:active {
+    opacity: 0.8;
+  }
+}
+
+.centerSearch {
+  position: absolute;
+  top: 15px;
+  right: 18px;
+
+  // padding: 6px 9px;
+  width: 300px;
+
+  :global {
+
+    .van-search {
+      width: 100%;
+      z-index: 1;
+      padding: 0px 0px;
+    }
+
+    .van-field__control::-webkit-input-placeholder {
+      font-size: 12px;
+    }
+  }
+
+}
+
+
+
+.section {
+  display: flex;
+  // padding: 0 18px 0 27px;
+  height: calc(100vh - 79px);
+
+  .directorySection {
+    width: 111px;
+    // background: #B5E1FF;
+    background: url('./image/d-top.png') no-repeat top center;
+    background-size: contain;
+    border-radius: 18px;
+    margin-right: 15px;
+
+
+
+
+    .directionc {
+      overflow-y: auto;
+      overflow-x: hidden;
+      margin-top: 23px;
+      padding: 12px 7px 12px;
+      background: #B5E1FF;
+      border-radius: 0 0 18px 18px;
+      height: calc(100% - 24px);
+
+      &::-webkit-scrollbar {
+        width: 0;
+        display: none;
+      }
+    }
+
+    .dirItem {
+      text-align: center;
+      font-size: 13px;
+      font-weight: 500;
+      color: #131415;
+      padding: 0 7px;
+      height: 33px;
+      line-height: 33px;
+      text-align: center;
+      white-space: nowrap;
+      width: 100%;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      margin-bottom: 5px;
+
+      &.active {
+        background-color: #fff;
+        background: #FEF8EF;
+        border-radius: 6px;
+        color: #0CA2EA;
+      }
+    }
+  }
+}
+
+.content {
+  overflow-y: auto;
+  overflow-x: hidden;
+
+  &::-webkit-scrollbar {
+    width: 0;
+    display: none;
+  }
+
+  // display: flex;
+  flex: 1;
+  // background-color: #FFFFFF;
+  border-radius: 18px;
+  padding: 0;
+  background: rgba(255, 255, 255, 0.8);
+  border-radius: 18px;
+  border: 2px solid rgba(255, 255, 255, 0.9);
+  // border-image: linear-gradient(134deg, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0.9)) 2 2;
+
+  .wrap {
+    position: relative;
+    display: flex;
+    flex-wrap: wrap;
+    padding-top: 18px;
+    transition: all .3s ease;
+    // gap: 0 32px;
+    // padding-left: 16px;
+    // height: 100%;
+    // padding: 20px 63px 0;
+
+    &.emtpyWrap {
+      width: 100%;
+      padding-top: 0;
+    }
+  }
+
+  :global {
+    .van-empty {
+      padding-bottom: 0 !important;
+    }
+
+    .van-empty__description {
+      font-size: 14px;
+      color: #fff;
+    }
+
+    .van-empty__image {
+      width: 200px;
+      height: 200px;
+    }
+  }
+}
+
+.wrapfirstI {
+  // width: 25%;
+}
+
+.wrapItem {
+  position: relative;
+  width: 132px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 0 14px;
+  flex-shrink: 0;
+  z-index: 1;
+  margin-bottom: 18px;
+
+  .item {
+    position: relative;
+    margin-bottom: 15px;
+    width: 100%;
+    height: 144px;
+    background-color: #edeff2;
+    box-shadow: 0 5px 14px rgba(0, 0, 0, 0.4);
+
+    img {
+      position: absolute;
+    }
+
+    &::before {
+      content: '';
+      position: absolute;
+      top: 4px;
+      right: -4px;
+      width: 4px;
+      height: calc(100% - 8px);
+      background-color: #c5c5c5;
+      z-index: 1;
+    }
+
+    &::after {
+      content: '';
+      position: absolute;
+      top: 2px;
+      right: -2px;
+      width: 4px;
+      height: calc(100% - 4px);
+      background-color: #e7e7e7;
+      z-index: 2;
+    }
+  }
+
+  .name {
+    font-size: 13px;
+    font-weight: 400;
+    color: #131415;
+    line-height: 16px;
+    text-align: left;
+  }
+
+  .favoriteBtn {
+    position: absolute;
+    top: 113px;
+    right: 20px;
+    z-index: 3;
+  }
+}
+
+.containerImg {
+  position: relative;
+  display: block;
+  width: 100%;
+  height: 170px;
+
+  &::before {
+    content: '';
+    position: absolute;
+    top: 4px;
+    right: -4px;
+    width: 4px;
+    height: calc(100% - 8px);
+    background-color: #c5c5c5;
+    z-index: 1;
+  }
+
+  &::after {
+    content: '';
+    position: absolute;
+    top: 2px;
+    right: -2px;
+    width: 4px;
+    height: calc(100% - 4px);
+    background-color: #e7e7e7;
+    z-index: 2;
+  }
+}
+
+.cover {
+  position: relative;
+  z-index: 3;
+  display: block;
+  width: 100%;
+  height: 170px;
+  background-color: #edeff2;
+  background-image: url('./image/icon_default.svg');
+  background-repeat: no-repeat;
+  background-position: center center;
+
+  &::after {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    z-index: 2;
+    background-repeat: no-repeat;
+    background-image: linear-gradient(to right,
+        rgba(0, 0, 0, 0.2) 0,
+        rgba(255, 255, 255, 0.08) 0%,
+        transparent 0.5%),
+      linear-gradient(to right,
+        rgba(0, 0, 0, 0.1) 0.3%,
+        rgba(255, 255, 255, 0.09) 1.1%,
+        transparent 1.3%);
+    background-size: 50% 100%, 50% 100%;
+    background-position: 0% top, 9% top;
+  }
+
+  :global {
+    img {
+      width: 100%;
+      height: 100%;
+      opacity: 0;
+      transition: opacity 0.3s ease-in-out;
+    }
+  }
+
+  &.loaded {
+    img {
+      opacity: 1;
+    }
+  }
+}

+ 285 - 0
src/views/co-ai/baseIndex.tsx

@@ -0,0 +1,285 @@
+import {
+  computed,
+  defineComponent,
+  onMounted,
+  reactive,
+  ref,
+  watch
+} from 'vue';
+import styles from './baseIndex.module.less';
+import icon_back from './image/icon_back.svg';
+import { Button, Space, Tab, Tabs, showConfirmDialog } from 'vant';
+import {
+  api_lessonCoursewareFavoriteRemove,
+  api_lessonCoursewareFavoriteSave
+} from '../courseware-list/api';
+import { NImage } from 'naive-ui';
+import { state } from '@/state';
+import TheFavorite from '@/components/the-favorite';
+import { useRouter } from 'vue-router';
+import { postMessage } from '@/helpers/native-message';
+import MEmpty from '@/components/m-empty';
+import MSearch from '@/components/m-search';
+import { api_musicSheetCategoriesPage, api_musicTagTree } from './api';
+import queryString from 'query-string';
+import { useRect } from '@vant/use';
+import { nextTick } from 'process';
+
+export default defineComponent({
+  name: 'baseIndex-list',
+  setup() {
+    const router = useRouter();
+    const popoverShow = ref(false);
+    const bookVersionList = ref([] as any);
+    // 返回
+    const goback = () => {
+      postMessage({ api: 'goBack' });
+    };
+    const forms = reactive({
+      // currentGradeNum: null,
+      musicTagId: null,
+      keyword: null,
+      page: 1,
+      rows: 999
+      // type: 'COURSEWARE'
+    });
+    const isShowGuide = ref(false);
+    const data = reactive({
+      list: [] as any[],
+      loading: false,
+      favoriteList: [] as any[],
+      details: [] as any[],
+      bookData: {} as any,
+      showBook: false,
+      book: {} as DOMRect
+    });
+
+    const wrapRef = ref();
+    const wrapItemRef = ref([] as any);
+
+    const getTanentList = async () => {
+      try {
+        const { data } = await api_musicTagTree();
+        bookVersionList.value = data || [];
+        if (bookVersionList.value.length > 0) {
+          forms.musicTagId = bookVersionList.value[0].id;
+        }
+      } catch {
+        //
+      }
+    };
+
+    const getList = async () => {
+      data.loading = true;
+      const { musicTagId, ...music } = forms;
+      const res = await api_musicSheetCategoriesPage({
+        musicTagIds: [musicTagId],
+        subjectId: state.user.data?.subjectId || '',
+        ...music
+        // currentGradeNum: forms.currentGradeNum ? forms.currentGradeNum : ''
+      });
+      if (res?.code === 200 && Array.isArray(res?.data?.rows)) {
+        data.list = res.data.rows.map((item: any) => {
+          item.load = false;
+          item.key = Date.now() + item.id;
+          return item;
+        });
+      }
+      data.loading = false;
+      isShowGuide.value = true;
+
+      nextTick(() => {
+        // 设置样式
+        const wrap = useRect(wrapRef);
+        const wrapItem = useRect(wrapItemRef.value[0]);
+        if (wrapItem.width > 0) {
+          wrapRef.value.style.width =
+            Math.floor(wrap.width / wrapItem.width) * wrapItem.width + 'px';
+          wrapRef.value.style.margin = '0 auto';
+        }
+      });
+    };
+    onMounted(async () => {
+      data.loading = true;
+      await getTanentList();
+      await getList();
+      data.loading = false;
+
+      // if (wrapItemRef.value && wrapItemRef.length > 0) {
+      //   console.log(wrapItemRef[0], '121');
+      // }
+    });
+
+    const handleFavorite = async (item: any) => {
+      if (item.favoriteFlag) {
+        await api_lessonCoursewareFavoriteSave({
+          lessonCoursewareId: item.id
+        });
+      } else {
+        await api_lessonCoursewareFavoriteRemove({
+          lessonCoursewareId: item.id
+        });
+
+        getList();
+      }
+    };
+
+    let timer: any = null;
+    const dubounce = (fn: any, delay: number = 300) => {
+      if (timer) {
+        clearTimeout(timer);
+      }
+      timer = setTimeout(fn, delay);
+    };
+
+    const onDetail = (item: any) => {
+      // router.push({
+      //   path: '/co-ai-detail',
+      //   query: {
+      //     musicTagId: forms.musicTagId,
+      //     id: item.id,
+      //     name: item.name
+      //   }
+      // });
+      const query = queryString.stringify({
+        musicTagId: forms.musicTagId,
+        id: item.id,
+        name: item.name
+      });
+      const url =
+        location.origin + location.pathname + '#/co-ai-detail?' + query;
+      console.log('🚀 ~ url:', url);
+      postMessage({
+        api: 'openWebView',
+        content: {
+          url,
+          orientation: 0,
+          isHideTitle: false,
+          c_orientation: 0 // 0 横屏 1 竖屏
+        }
+      });
+    };
+
+    return () => (
+      <div class={styles.container}>
+        {/* <div class={styles.head} style={{ opacity: data.showBook ? 0 : '' }}>
+          <div class={styles.back} onClick={goback}>
+            <img src={icon_back} />
+          </div>
+        </div> */}
+        <div class={styles.back} onClick={goback}>
+          <img src={icon_back} />
+        </div>
+
+        <div class={styles.centerSearch}>
+          <div id="coai-0">
+            <MSearch
+              class={[
+                'searchNotice'
+                // data.searchNoticeShow ? styles.searchNoticeShow : ''
+              ]}
+              shape="round"
+              background="transparent"
+              clearable={false}
+              placeholder="请输入关键字"
+              // modelValue={forms.keyword}
+              // onFocus={() => (data.searchNoticeShow = false)}
+              // onBlur={val => {
+              //   musicForms.keyword = val?.trim() || '';
+              //   requestAnimationFrame(() => {
+              //     requestAnimationFrame(() => {
+              //       if (musicForms.keyword) {
+              //         data.searchNoticeShow = true;
+              //       }
+              //     });
+              //   });
+              // }}
+              onSearch={val => {
+                forms.keyword = val;
+                getList();
+              }}
+            />
+          </div>
+        </div>
+
+        <div class={styles.section}>
+          <div class={styles.directorySection}>
+            <div class={styles.directionc}>
+              {bookVersionList.value.map((item: any) => (
+                <p
+                  class={[
+                    styles.dirItem,
+                    forms.musicTagId == item.id ? styles.active : ''
+                  ]}
+                  onClick={() => {
+                    forms.musicTagId = item.id;
+                    getList();
+                  }}>
+                  {item.name}
+                </p>
+              ))}
+            </div>
+          </div>
+
+          <div class={styles.content}>
+            <div
+              class={[
+                styles.wrap,
+                data.list.length <= 0 && !data.loading ? styles.emtpyWrap : ''
+              ]}
+              ref={wrapRef}>
+              {data.list.map((item, index) => {
+                return (
+                  <div class={styles.wrapfirstI}>
+                    <div
+                      ref={(el: any) => (wrapItemRef.value[index] = el)}
+                      class={[
+                        styles.wrapItem,
+                        data.bookData.id === item.id && data.showBook
+                          ? styles.wrapItemHide
+                          : ''
+                      ]}
+                      key={item.key}
+                      onClick={() => onDetail(item)}>
+                      <div class={styles.item}>
+                        <NImage
+                          data-id={item.id}
+                          {...{ id: index == 0 ? 'courseware-0' : '' }}
+                          class={[styles.cover, item.load ? styles.loaded : '']}
+                          objectFit="cover"
+                          src={item.coverImg}
+                          onLoad={() => {
+                            item.load = true;
+                          }}
+                          onError={() => {
+                            item.load = true;
+                          }}
+                        />
+                      </div>
+
+                      <div class={styles.name}>{item.name}</div>
+                      {/* <div
+                      id={index === 0 ? 'courseware-1' : ''}
+                      class={styles.favoriteBtn}
+                      onClick={(e: Event) => {
+                        e.stopPropagation();
+                        item.favoriteFlag = !item.favoriteFlag;
+                        dubounce(() => handleFavorite(item));
+                      }}>
+                      <TheFavorite isFavorite={item.favoriteFlag} />
+                    </div> */}
+                    </div>
+                  </div>
+                );
+              })}
+
+              {data.list.length <= 0 && !data.loading && (
+                <MEmpty image="list" description="暂无数据" />
+              )}
+            </div>
+          </div>
+        </div>
+      </div>
+    );
+  }
+});

BIN
src/views/co-ai/image/d-top.png


+ 76 - 26
src/views/co-ai/index.module.less

@@ -5,7 +5,7 @@
   width: 31px;
   height: 31px;
 
-  & > img {
+  &>img {
     width: 100%;
     height: 100%;
     object-fit: cover;
@@ -16,26 +16,62 @@
   }
 }
 
+.musicCFixed {
+  position: fixed;
+  top: 17px;
+  left: 55px;
+
+  .musicName {
+    font-size: 15px;
+    font-weight: 600;
+    color: #FFFFFF;
+    line-height: 33px;
+  }
+}
+
+.popoverMusic {
+  width: 160px;
+
+  // border: none;
+  --van-popover-action-width: 100%;
+
+  :global {
+    .van-popover__action {
+      // display: flex;
+      // justify-content: center;
+      // align-items: center;
+      // padding: 0;
+      // width: 100%;
+      // height: 36px;
+      // color: #999;
+      // font-size: 13px;
+    }
+
+    .van-popover__content {
+      max-height: 70vh;
+      overflow-x: hidden;
+      overflow-y: auto;
+    }
+  }
+}
+
 .container {
-  background: url('../../common/images/icon_bg.png') no-repeat center center /
-    cover;
-  padding: 47px 12px 20px 54px;
+  background: url('../../common/images/icon_bg.png') no-repeat center center / cover;
+  padding: 63px 18px 18px 37px;
   height: 100vh;
   overflow: hidden;
 }
 
 .content {
   display: flex;
-  height: calc(100vh - 67px);
+  height: calc(100vh - 81px);
   overflow: hidden;
 }
 
 .opacityBg {
-  background: linear-gradient(
-    134deg,
-    rgba(255, 255, 255, 0.75) 0%,
-    rgba(255, 255, 255, 0.34) 100%
-  );
+  background: linear-gradient(134deg,
+      rgba(255, 255, 255, 0.75) 0%,
+      rgba(255, 255, 255, 0.34) 100%);
   border-radius: 18px;
   border: 2px solid #fff;
 }
@@ -43,7 +79,7 @@
 .leftContent {
   position: relative;
   display: flex;
-  width: 45%;
+  width: 37%;
 }
 
 .leftBg {
@@ -64,11 +100,9 @@
   top: 0;
   width: calc(100% - 12px);
   height: 100%;
-  background: linear-gradient(
-    134deg,
-    rgba(255, 255, 255, 0.75) 0%,
-    rgba(255, 255, 255, 0.34) 100%
-  );
+  background: linear-gradient(134deg,
+      rgba(255, 255, 255, 0.75) 0%,
+      rgba(255, 255, 255, 0.34) 100%);
   border-radius: 18px;
   border: 2px solid #fff;
 }
@@ -134,9 +168,11 @@
   padding-right: 12px;
   height: 100%;
   overflow: hidden;
+
   .centerSearch {
     padding: 6px 9px;
   }
+
   :global {
     #coai-0 {
       .van-search {
@@ -144,6 +180,7 @@
         z-index: 1;
         padding: 0px 0px;
       }
+
       .van-field__control::-webkit-input-placeholder {
         font-size: 12px;
       }
@@ -163,10 +200,12 @@
     display: none;
   }
 }
+
 .searchNotice {
   position: fixed;
   padding: 0;
   pointer-events: none;
+
   :global {
     .van-notice-bar {
       padding: 0;
@@ -174,6 +213,7 @@
     }
   }
 }
+
 .searchNoticeShow {
   :global {
     .van-field__control {
@@ -181,6 +221,7 @@
     }
   }
 }
+
 .musicItem {
   position: relative;
   display: flex;
@@ -287,7 +328,7 @@
   background: #fff;
   overflow: hidden;
   border: none;
-  width: 55%;
+  width: 63%;
   border-radius: 18px;
 }
 
@@ -303,7 +344,8 @@
 .staff {
   width: 100%;
 }
-.rightBox{
+
+.rightBox {
   width: 100%;
   height: 100%;
   border-radius: 18px;
@@ -315,19 +357,17 @@
   bottom: 0;
   left: 0;
   right: 0;
-  padding: 0 8px 18px 8px;
+  padding: 0 18px 18px 18px;
   display: flex;
   align-items: flex-end;
   height: 78px;
   background: #fff;
-  background-image: linear-gradient(
-    180deg,
-    rgba(255, 255, 255, 0) 0%,
-    #c1eeff 100%
-  );
+  background-image: linear-gradient(180deg,
+      rgba(255, 255, 255, 0) 0%,
+      #c1eeff 100%);
   border-radius: 0 0 18px 18px;
 
-  & > img {
+  &>img {
     margin: 0 4px;
     height: 30px;
     transition: 0.3s;
@@ -336,11 +376,21 @@
       transform: scale(0.6);
     }
   }
+
+  :global {
+    .van-popover__wrapper {
+      display: flex;
+      align-items: center;
+      margin-right: 12px;
+    }
+  }
+
   .rightBtnsRight {
     transition: 0.3s;
     margin: 0 4px;
     padding: 5px 10px 0;
     margin-left: auto;
+
     img {
       height: 30px;
       animation: scaleBtn 1s ease-in-out infinite;
@@ -386,4 +436,4 @@
       padding: 0;
     }
   }
-}
+}

+ 124 - 18
src/views/co-ai/index.tsx

@@ -1,5 +1,6 @@
 import {
   TransitionGroup,
+  computed,
   defineComponent,
   nextTick,
   onMounted,
@@ -12,9 +13,11 @@ import MSearch from '@/components/m-search';
 import icon_play from '@/common/images/icon_play.svg';
 import {
   Empty,
+  Icon,
   List,
   Loading,
   NoticeBar,
+  Popover,
   Popup,
   showLoadingToast,
   showToast
@@ -38,29 +41,35 @@ import Coaiguide from '@/custom-plugins/guide-page/coai-guide';
 import { usePageVisibility } from '@vant/use';
 import TheVip from '@/components/the-vip';
 import request from '@/helpers/request';
+import { useRoute } from 'vue-router';
 export default defineComponent({
   name: 'co-ai',
   setup() {
+    const route = useRoute();
     const categorForms = reactive({
       page: 1,
       rows: 999,
-      subjectId: state.user.data?.subjectId || ''
+      subjectId: state.user.data?.subjectId || '',
+      musicTagIds: route.query.musicTagId ? [route.query.musicTagId] : []
     });
     const musicForms = reactive({
       page: 1,
       rows: 20,
       status: 1,
       keyword: '', // 关键词
-      musicSheetCategoriesId: ''
+      musicSheetCategoriesId: route.query.id as any
     });
     const titles = rows;
     const data = reactive({
+      musicSheetCategoriesName: route.query.name as any,
       /** 教材Index */
       typeIndex: 0,
       /** 音乐Index */
       musicIndex: 0,
-      /** 显示简谱 */
-      isShowJianpu: true,
+      /** 显示哪种曲谱 */
+      showMusicImg: 'first' as 'staff' | 'first' | 'fixed',
+      popoverShow: false,
+      popoverMusicShow: false,
       /** 教材列表 */
       types: [] as any[],
       /** 音乐列表 */
@@ -80,6 +89,45 @@ export default defineComponent({
     });
     const downRef = ref();
     const showGuide = ref(false);
+
+    const _actions = computed(() => {
+      return [
+        {
+          value: 'staff',
+          text: '五线谱'
+        },
+        {
+          value: 'first',
+          text: '首调'
+        },
+        {
+          value: 'fixed',
+          text: '固定调'
+        }
+      ].map((item, index) => {
+        return {
+          ...item,
+          color:
+            data.showMusicImg === item.value ? 'var(--van-primary-color)' : '',
+          className: data.showMusicImg === item.value ? 'fontBlod' : ''
+        };
+      });
+    });
+
+    const _types = computed(() => {
+      return data.types.map((item: any) => {
+        return {
+          ...item,
+          color:
+            musicForms.musicSheetCategoriesId == item.value
+              ? 'var(--van-primary-color)'
+              : '',
+          className:
+            musicForms.musicSheetCategoriesId == item.value ? 'fontBlod' : ''
+        };
+      });
+    });
+
     // 返回
     const goback = () => {
       postMessage({ api: 'goBack' });
@@ -147,10 +195,14 @@ export default defineComponent({
           ...categorForms
         });
         if (res.code === 200 && Array.isArray(res?.data?.rows)) {
-          data.types = res.data.rows;
-          if (!musicForms.musicSheetCategoriesId && data.types.length > 0) {
-            musicForms.musicSheetCategoriesId = data.types[0].id;
-          }
+          const temp: any = [];
+          res.data.rows.forEach((item: any) => {
+            temp.push({
+              value: item.id,
+              text: item.name
+            });
+          });
+          data.types = temp;
         }
       } catch (error) {
         console.log('🚀 ~ error:', error);
@@ -235,11 +287,37 @@ export default defineComponent({
         <div class={styles.back} onClick={goback}>
           <img src={icon_back} />
         </div>
+
+        <div class={styles.musicCFixed}>
+          <Popover
+            v-model:show={data.popoverMusicShow}
+            class={styles.popoverMusic}
+            actions={_types.value}
+            placement="bottom"
+            showArrow={false}
+            onSelect={(item: any) => {
+              // data.showMusicImg = item.value;
+              data.musics = [];
+              musicForms.musicSheetCategoriesId = item.value;
+              data.musicSheetCategoriesName = item.text;
+              data.popoverMusicShow = false;
+              getMusicList();
+            }}>
+            {{
+              reference: () => (
+                <span class={styles.musicName}>
+                  {data.musicSheetCategoriesName}
+                  <Icon name="arrow-down"></Icon>
+                </span>
+              )
+            }}
+          </Popover>
+        </div>
         <div class={styles.content}>
           <div class={[styles.leftContent]}>
             <div class={styles.leftBg2}></div>
-            <div class={styles.leftBg}></div>
-            <div class={styles.types}>
+            {/* <div class={styles.leftBg}></div> */}
+            {/* <div class={styles.types}>
               {data.types.map((item, index) => {
                 return (
                   <div
@@ -265,7 +343,7 @@ export default defineComponent({
                   </div>
                 );
               })}
-            </div>
+            </div> */}
             <div class={styles.center}>
               <div class={styles.centerSearch}>
                 <div id="coai-0">
@@ -284,7 +362,7 @@ export default defineComponent({
                       musicForms.keyword = val?.trim() || '';
                       requestAnimationFrame(() => {
                         requestAnimationFrame(() => {
-                          if (musicForms.keyword){
+                          if (musicForms.keyword) {
                             data.searchNoticeShow = true;
                           }
                         });
@@ -354,7 +432,7 @@ export default defineComponent({
                 <div class={styles['right-musicName']}>
                   {data.musics[data.musicIndex]?.musicSheetName}
                 </div>
-                {data.isShowJianpu ? (
+                {data.showMusicImg === 'first' ? (
                   <>
                     {data.musics[data.musicIndex]?.musicSvg
                       ?.split(',')
@@ -369,6 +447,23 @@ export default defineComponent({
                         );
                       })}
                   </>
+                ) : data.showMusicImg === 'fixed' ? (
+                  <>
+                    <TransitionGroup name="van-fade">
+                      {data.musics[data.musicIndex]?.musicJianSvg
+                        ?.split(',')
+                        .map((item: any, index: number) => {
+                          return (
+                            <img
+                              class={styles.staff}
+                              src={item + '?v=' + Date.now()}
+                              key={item}
+                              crossorigin="anonymous"
+                            />
+                          );
+                        })}
+                    </TransitionGroup>
+                  </>
                 ) : (
                   <>
                     {data.musics[data.musicIndex]?.musicImg
@@ -389,11 +484,22 @@ export default defineComponent({
             </div>
 
             <div class={styles.rightBtns}>
-              <img
-                id="coai-1"
-                src={data.isShowJianpu ? icon_jianpuActive : icon_jianpu}
-                onClick={() => (data.isShowJianpu = !data.isShowJianpu)}
-              />
+              <Popover
+                v-model:show={data.popoverShow}
+                class={styles.popover}
+                actions={_actions.value}
+                placement="top-start"
+                onSelect={(item: any) => {
+                  data.showMusicImg = item.value;
+                  data.popoverShow = false;
+                }}
+                // onSelect={onSelect}
+              >
+                {{
+                  reference: () => <img id="coai-1" src={icon_jianpuActive} />
+                }}
+              </Popover>
+
               <img id="coai-2" src={icon_down} onClick={handleSave} />
               <div class={styles.rightBtnsRight} id="coai-3">
                 <img src={icons.icon_start} onClick={() => handleGoto()} />

+ 7 - 0
src/views/courseware-list/api.ts

@@ -45,3 +45,10 @@ export const api_classLessonCoursewareDetail = (
 ): Promise<any> => {
   return request.get(`/edu-app/classLessonCourseware/detail/${params}`);
 };
+
+/** 版本教材 */
+export const api_tenantInfoDetail = (params: any): Promise<any> => {
+  return request.get(`/edu-app/tenantInfo/detail`, {
+    params: params
+  });
+};

+ 20 - 15
src/views/courseware-list/component/book/index.tsx

@@ -40,7 +40,7 @@ export default defineComponent({
   emits: ['close'],
   setup(props, { emit }) {
     const router = useRouter();
-    console.log(state.user.data.phone)
+    console.log(state.user.data.phone);
     const lastTimeKey = 'lastTime' + (state?.user?.data?.phone ?? '');
     const data = reactive({
       show: false,
@@ -204,28 +204,33 @@ export default defineComponent({
         localStorage.setItem(lastTimeKey, item.id);
         const query = queryString.stringify({
           id: item.id,
+          lessonCoursewareId: item.lessonCoursewareId,
+          lessonCoursewareDetailId: item.lessonCoursewareDetailId,
           name: item.name,
           tab: props.tab // 当前切换的是哪个类型
         });
         const url =
           location.origin + location.pathname + '#/courseware-play?' + query;
         console.log('🚀 ~ url:', url);
-        // postMessage({
-        //   api: 'openWebView',
-        //   content: {
-        //     url,
-        //     orientation: 0,
-        //     isHideTitle: false
-        //   }
-        // });
-        router.push({
-          path: '/courseware-play',
-          query: {
-            id: item.id,
-            name: item.name,
-            tab: props.tab
+        postMessage({
+          api: 'openWebView',
+          content: {
+            url,
+            orientation: 0,
+            isHideTitle: false,
+            c_orientation: 0 // 0 横屏 1 竖屏
           }
         });
+        // router.push({
+        //   path: '/courseware-play',
+        //   query: {
+        //     id: item.id,
+        //     lessonCoursewareId: item.lessonCoursewareId,
+        //     lessonCoursewareDetailId: item.lessonCoursewareDetailId,
+        //     name: item.name,
+        //     tab: props.tab
+        //   }
+        // });
       }
     };
     return () => (

+ 106 - 10
src/views/courseware-list/index.module.less

@@ -3,8 +3,7 @@
   width: 100vw;
   height: 100vh;
   overflow: hidden;
-  background: url('../../common/images/icon_bg.png') no-repeat center center /
-    cover;
+  background: url('../../common/images/icon_bg.png') no-repeat center center / cover;
   display: flex;
   flex-direction: column;
 }
@@ -59,6 +58,19 @@
   justify-content: center;
   align-items: center;
 
+  &.activeBtn {
+    .icon {
+      color: var(--van-primary-color) !important;
+    }
+
+
+    :global {
+      .van-button__text {
+        color: var(--van-primary-color);
+      }
+    }
+  }
+
   :global {
     .van-button__content {
       width: 100%;
@@ -161,9 +173,11 @@
     height: 170px;
     background-color: #edeff2;
     box-shadow: 0 5px 14px rgba(0, 0, 0, 0.4);
+
     img {
       position: absolute;
     }
+
     &::before {
       content: '';
       position: absolute;
@@ -252,18 +266,14 @@
     height: 100%;
     z-index: 2;
     background-repeat: no-repeat;
-    background-image: linear-gradient(
-        to right,
+    background-image: linear-gradient(to right,
         rgba(0, 0, 0, 0.2) 0,
         rgba(255, 255, 255, 0.08) 0%,
-        transparent 0.5%
-      ),
-      linear-gradient(
-        to right,
+        transparent 0.5%),
+      linear-gradient(to right,
         rgba(0, 0, 0, 0.1) 0.3%,
         rgba(255, 255, 255, 0.09) 1.1%,
-        transparent 1.3%
-      );
+        transparent 1.3%);
     background-size: 50% 100%, 50% 100%;
     background-position: 0% top, 9% top;
   }
@@ -314,3 +324,89 @@
     transform: translate(-50%, -50%) scale(1);
   }
 }
+
+
+.popupContainer {
+  background: #FFFFFF;
+  border-radius: 12px;
+  width: 298px;
+  padding-top: 12px;
+
+  .searchList {
+    padding: 0 0px 0 11px;
+    max-height: 58vh;
+    overflow-x: hidden;
+    overflow-y: auto;
+  }
+
+  .popoverTitle {
+    display: flex;
+    align-items: center;
+    font-size: 14px;
+    font-weight: 600;
+    color: #333333;
+
+    &::before {
+      content: ' ';
+      display: inline-block;
+      width: 4px;
+      height: 11px;
+      background: #2AA4FE;
+      border-radius: 2px;
+      margin-right: 6px;
+    }
+  }
+
+  .popupList {
+    display: flex;
+    flex-flow: wrap;
+    justify-content: flex-start;
+    // gap: 8px 9px;
+    padding-top: 11px;
+    padding-bottom: 14px;
+
+    :global {
+      .van-tag {
+        padding: 7px 24px;
+        height: 30px;
+        font-size: 12px;
+        background: #F6F6F6;
+        border: none;
+        color: #333333;
+        margin-right: 8px;
+        margin-bottom: 9px;
+      }
+
+      .van-tag--plain {
+        background: rgba(42, 164, 254, 0.08);
+        border: none;
+        color: #2AA4FE;
+      }
+    }
+  }
+
+  .btnGroup {
+    border: 1px solid #F2F2F2;
+    padding: 16px 12px;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+
+    :global {
+      .van-button {
+        font-size: 15px;
+        color: #333333;
+        line-height: 22px;
+        padding: 0 48px;
+        height: 37px;
+        font-weight: 400;
+      }
+    }
+
+    .btnSure {
+      background: linear-gradient(135deg, #19F1E1 0%, #0094FF 100%), linear-gradient(73deg, #5BECFF 0%, #259CFE 100%);
+      border: none;
+      color: #fff;
+    }
+  }
+}

+ 156 - 58
src/views/courseware-list/index.tsx

@@ -14,8 +14,11 @@ import {
   Button,
   Image,
   Popover,
+  Popup,
   Tab,
   Tabs,
+  Tag,
+  popoverProps,
   showConfirmDialog,
   showDialog,
   showToast
@@ -27,7 +30,8 @@ import {
   api_lessonCoursewarePage,
   api_lessonCoursewareDetail,
   api_classLessonCoursewarePage,
-  api_classLessonCoursewareDetail
+  api_classLessonCoursewareDetail,
+  api_tenantInfoDetail
 } from './api';
 import { NImage } from 'naive-ui';
 import { state } from '@/state';
@@ -39,10 +43,12 @@ import CoursewareList from '@/custom-plugins/guide-page/courseware-list';
 import './jquery.min.1.7.js';
 import './turn.js';
 import MEmpty from '@/components/m-empty';
+import deepClone from '@/helpers/deep-clone';
+import book from './component/book';
 
 export const BOOK_DATA = {
   grades: [
-    { text: '全部年级', value: '' },
+    // { text: '全部年级', value: '' },
     { text: '一年级', value: 1 },
     { text: '二年级', value: 2 },
     { text: '三年级', value: 3 },
@@ -64,12 +70,15 @@ export default defineComponent({
   setup() {
     const router = useRouter();
     const popoverShow = ref(false);
+    const baseBookVerionList = ref([] as any);
+    const bookVersionList = ref([] as any);
     // 返回
     const goback = () => {
       postMessage({ api: 'goBack' });
     };
     const forms = reactive({
-      currentGradeNum: 0,
+      currentGradeNum: null,
+      bookVersionId: null as any,
       page: 1,
       rows: 999,
       type: 'COURSEWARE'
@@ -84,10 +93,10 @@ export default defineComponent({
         };
       });
     });
-    const onSelect = (action: any, index: number) => {
-      forms.currentGradeNum = index;
-      handleChange();
-    };
+    // const onSelect = (action: any, index: number) => {
+    //   forms.currentGradeNum = index;
+    //   handleChange();
+    // };
     const isShowGuide = ref(false);
     const data = reactive({
       list: [] as any[],
@@ -100,11 +109,32 @@ export default defineComponent({
       book: {} as DOMRect
     });
 
+    const getTanentList = async () => {
+      try {
+        const schoolInfos = state.user.data.schoolInfos;
+        const tenantId =
+          schoolInfos.length > 0 ? schoolInfos[0].tenantId : null;
+        if (tenantId) {
+          const { data } = await api_tenantInfoDetail({
+            id: tenantId
+          });
+
+          baseBookVerionList.value = data.bookVersionList || [];
+
+          bookVersionList.value = deepClone(baseBookVerionList.value);
+        }
+      } catch {
+        //
+      }
+    };
+
     const getList = async () => {
       data.loading = true;
+      const { bookVersionId, currentGradeNum, ...more } = forms;
       const res = await api_lessonCoursewarePage({
-        ...forms,
-        currentGradeNum: forms.currentGradeNum ? forms.currentGradeNum : ''
+        ...more,
+        bookVersionId: bookVersionId == -1 ? null : bookVersionId,
+        currentGradeNum: currentGradeNum ? currentGradeNum : ''
       });
       if (res?.code === 200 && Array.isArray(res?.data?.rows)) {
         data.list = res.data.rows.map((item: any) => {
@@ -123,6 +153,7 @@ export default defineComponent({
         userId: state.user?.data?.id,
         page: forms.page,
         rows: forms.rows,
+        bookVersionId: forms.bookVersionId != -1 ? forms.bookVersionId : '',
         currentGradeNum: forms.currentGradeNum ? forms.currentGradeNum : ''
       });
       if (res?.code === 200 && Array.isArray(res?.data?.rows)) {
@@ -141,9 +172,10 @@ export default defineComponent({
       const res = await api_classLessonCoursewarePage({
         // clientType: 'STUDENT',
         // userId: state.user?.data?.id,
+        bookVersionId: forms.bookVersionId ? forms.bookVersionId : '',
         page: forms.page,
-        rows: forms.rows
-        // currentGradeNum: forms.currentGradeNum ? forms.currentGradeNum : ''
+        rows: forms.rows,
+        currentGradeNum: forms.currentGradeNum ? forms.currentGradeNum : ''
       });
       if (res?.code === 200 && Array.isArray(res?.data?.rows)) {
         data.list = res.data.rows.map((item: any) => {
@@ -167,9 +199,21 @@ export default defineComponent({
       }
     };
     const handleChange = () => {
+      if (data.tab === 'course') {
+        bookVersionList.value = [
+          ...deepClone(baseBookVerionList.value),
+          {
+            bookVersionId: -1,
+            bookVersionName: '自定义'
+          }
+        ];
+      } else {
+        bookVersionList.value = deepClone(baseBookVerionList.value);
+      }
       getData();
     };
     onMounted(() => {
+      getTanentList();
       getData();
     });
 
@@ -200,7 +244,6 @@ export default defineComponent({
     const getDetail = async (item: any) => {
       if (data.tab === 'course') {
         const res = await api_classLessonCoursewareDetail(item.id);
-        console.log(res, item, 'course');
         if (res?.code == 200 && Array.isArray(res?.data?.lessonList)) {
           data.details = res.data.lessonList || [];
           data.bookData = res.data;
@@ -271,60 +314,65 @@ export default defineComponent({
                 title: () => <div id="courseware-2">我的收藏</div>
               }}></Tab>
           </Tabs>
-          <Popover
+          <Button
+            class={[
+              styles.downBtn,
+              (data.tab != 'course' && forms.bookVersionId > 0) ||
+              (data.tab == 'course' && forms.bookVersionId) ||
+              forms.currentGradeNum
+                ? styles.activeBtn
+                : ''
+            ]}
+            round
+            size="small"
+            onClick={() => (popoverShow.value = true)}
+            {...{ id: 'courseware-3' }}>
+            {/* {BOOK_DATA.grades[forms.currentGradeNum].text} */}
+            筛选
+            {/* <img class={styles.icon} src={icon_arrow} /> */}
+            <svg
+              class={[styles.icon, popoverShow.value ? styles.iconUp : '']}
+              width="9px"
+              height="5px"
+              viewBox="0 0 9 5"
+              version="1.1"
+              xmlns="http://www.w3.org/2000/svg">
+              <title>三角形</title>
+              <g
+                id="演示用"
+                stroke="none"
+                stroke-width="1"
+                fill="currentColor"
+                fill-rule="evenodd">
+                <g
+                  id="全部教材-筛选"
+                  transform="translate(-769.000000, -35.000000)"
+                  fill="currentColor">
+                  <g id="编组-3" transform="translate(696.000000, 20.000000)">
+                    <g
+                      id="筛选目录备份-2"
+                      transform="translate(13.000000, 7.000000)">
+                      <path
+                        d="M64.8716471,8.41294119 L68.2489659,12.1655176 C68.4336954,12.3707726 68.4170562,12.6869176 68.2118012,12.8716471 C68.1199888,12.9542782 68.0008397,13 67.8773188,13 L61.1226812,13 C60.8465388,13 60.6226812,12.7761424 60.6226812,12.5 C60.6226812,12.3764791 60.668403,12.25733 60.7510341,12.1655176 L64.1283529,8.41294119 C64.3130824,8.20768618 64.6292274,8.19104698 64.8344824,8.37577649 C64.8475136,8.38750459 64.859919,8.39990996 64.8716471,8.41294119 Z"
+                        id="三角形"
+                        transform="translate(64.500000, 10.500000) rotate(-180.000000) translate(-64.500000, -10.500000) "></path>
+                    </g>
+                  </g>
+                </g>
+              </g>
+            </svg>
+          </Button>
+          {/* <Popover
             v-model:show={popoverShow.value}
             class={styles.popover}
             actions={_actions.value}
             onSelect={onSelect}>
             {{
               reference: () => (
-                <Button
-                  class={styles.downBtn}
-                  round
-                  size="small"
-                  {...{ id: 'courseware-3' }}>
-                  {BOOK_DATA.grades[forms.currentGradeNum].text}{' '}
-                  {/* <img class={styles.icon} src={icon_arrow} /> */}
-                  <svg
-                    class={[
-                      styles.icon,
-                      popoverShow.value ? styles.iconUp : ''
-                    ]}
-                    width="9px"
-                    height="5px"
-                    viewBox="0 0 9 5"
-                    version="1.1"
-                    xmlns="http://www.w3.org/2000/svg">
-                    <title>三角形</title>
-                    <g
-                      id="演示用"
-                      stroke="none"
-                      stroke-width="1"
-                      fill="currentColor"
-                      fill-rule="evenodd">
-                      <g
-                        id="全部教材-筛选"
-                        transform="translate(-769.000000, -35.000000)"
-                        fill="currentColor">
-                        <g
-                          id="编组-3"
-                          transform="translate(696.000000, 20.000000)">
-                          <g
-                            id="筛选目录备份-2"
-                            transform="translate(13.000000, 7.000000)">
-                            <path
-                              d="M64.8716471,8.41294119 L68.2489659,12.1655176 C68.4336954,12.3707726 68.4170562,12.6869176 68.2118012,12.8716471 C68.1199888,12.9542782 68.0008397,13 67.8773188,13 L61.1226812,13 C60.8465388,13 60.6226812,12.7761424 60.6226812,12.5 C60.6226812,12.3764791 60.668403,12.25733 60.7510341,12.1655176 L64.1283529,8.41294119 C64.3130824,8.20768618 64.6292274,8.19104698 64.8344824,8.37577649 C64.8475136,8.38750459 64.859919,8.39990996 64.8716471,8.41294119 Z"
-                              id="三角形"
-                              transform="translate(64.500000, 10.500000) rotate(-180.000000) translate(-64.500000, -10.500000) "></path>
-                          </g>
-                        </g>
-                      </g>
-                    </g>
-                  </svg>
-                </Button>
+
               )
             }}
-          </Popover>
+          </Popover> */}
         </div>
 
         <div class={styles.content}>
@@ -396,6 +444,56 @@ export default defineComponent({
           }}
         />
         {isShowGuide.value ? <CoursewareList></CoursewareList> : null}
+
+        <Popup v-model:show={popoverShow.value} class={styles.popupContainer}>
+          <div class={styles.popoverContainer}>
+            <div class={styles.searchList}>
+              <div class={styles.popoverTitle}>教材版本</div>
+              <div class={[styles.popupList, styles.versionList]}>
+                {bookVersionList.value.map((item: any) => (
+                  <Tag
+                    plain={forms.bookVersionId == item.bookVersionId}
+                    round
+                    onClick={() => (forms.bookVersionId = item.bookVersionId)}>
+                    {item.bookVersionName}
+                  </Tag>
+                ))}
+              </div>
+
+              <div class={styles.popoverTitle}>选择年级</div>
+              <div class={[styles.popupList, styles.versionList]}>
+                {BOOK_DATA.grades.map((item: any) => (
+                  <Tag
+                    plain={forms.currentGradeNum === item.value}
+                    round
+                    onClick={() => (forms.currentGradeNum = item.value)}>
+                    {item.text}
+                  </Tag>
+                ))}
+              </div>
+            </div>
+
+            <div class={styles.btnGroup}>
+              <Button
+                round
+                onClick={() => {
+                  forms.bookVersionId = null;
+                  forms.currentGradeNum = null;
+                }}>
+                重置
+              </Button>
+              <Button
+                round
+                class={styles.btnSure}
+                onClick={() => {
+                  getData();
+                  popoverShow.value = false;
+                }}>
+                确认
+              </Button>
+            </div>
+          </div>
+        </Popup>
       </div>
     );
   }

+ 103 - 0
src/views/courseware-play/component/chapter.module.less

@@ -0,0 +1,103 @@
+.container {
+  display: flex;
+  flex-direction: column;
+  min-width: 266px;
+  max-width: 266px;
+  height: 100vh;
+  color: #333;
+  font-size: 12px;
+  box-sizing: border-box;
+  background: #fff;
+}
+
+.pointHead {
+  display: flex;
+  align-items: center;
+  padding: 16px 10px 12px 13px;
+  flex-shrink: 0;
+  font-size: 14px;
+
+  img {
+    width: 16px;
+    height: 16px;
+    margin-right: 7px;
+  }
+}
+
+.content {
+  flex: 1;
+  overflow-x: hidden;
+  overflow-y: auto;
+  padding: 0 7px;
+
+  &::-webkit-scrollbar {
+    width: 0;
+    display: none;
+  }
+}
+
+.collapse {
+
+  .collapseItem {
+    padding: 7px;
+  }
+
+  :global {
+    .van-cell {
+      background: transparent;
+      font-size: 13px;
+      color: #777;
+      padding: 0;
+      border: none;
+      line-height: 18px;
+    }
+
+    .van-collapse-item__content {
+      padding-top: 0;
+      background-color: transparent;
+    }
+  }
+
+  .item {
+    display: flex;
+    align-items: center;
+    margin-top: 15px;
+
+    span {
+      color: #131415;
+      font-size: 12px;
+    }
+  }
+
+  .arrow {
+    width: 12px;
+    height: 12px;
+    margin-right: 5px;
+  }
+
+  .itemImage {
+    width: 15px;
+    height: 15px;
+    margin-right: 6px;
+  }
+
+  .activeItem {
+    background: #ECF8FF;
+    border-radius: 9px;
+
+    :global {
+      .van-cell {
+        color: #1C9AF7;
+        font-weight: 600;
+      }
+    }
+  }
+
+  .itemActive {
+    font-weight: 500;
+
+    span {
+      color: #1C9AF7;
+    }
+  }
+}

+ 114 - 0
src/views/courseware-play/component/chapter.tsx

@@ -0,0 +1,114 @@
+import { defineComponent, reactive, toRefs } from 'vue';
+import styles from './chapter.module.less';
+import iconMenuChapter from '../image/icon-menu-chapter.svg';
+import { Collapse, CollapseItem, Icon, Image, showToast } from 'vant';
+import chapterDown from '../image/chapter-down-arrow.svg';
+import chapterDefault from '../image/chapter-default-arrow.svg';
+import chapterVideo from '../image/chapter-video.svg';
+import { state } from '@/state';
+
+export default defineComponent({
+  name: 'chapter',
+  props: {
+    detail: {
+      type: Object,
+      default: () => []
+    },
+    itemActive: {
+      type: String,
+      default: ''
+    },
+    active: {
+      type: String,
+      default: ''
+    }
+  },
+  emits: ['handleSelect'],
+  setup(props, { emit }) {
+    const { detail, itemActive, active } = toRefs(props);
+    const pointData = reactive({
+      active: active.value
+    });
+    return () => (
+      <div class={styles.container}>
+        <div class={styles.pointHead}>
+          <img src={iconMenuChapter} />
+          切换章节
+        </div>
+        <div class={styles.content}>
+          <Collapse
+            class={styles.collapse}
+            modelValue={pointData.active}
+            onUpdate:modelValue={(val: any) => {
+              pointData.active = val;
+            }}
+            border={false}
+            accordion>
+            {detail.value.map((item: any) => (
+              <CollapseItem
+                center
+                class={[
+                  styles.collapseItem,
+                  pointData.active === item.id ? styles.activeItem : ''
+                ]}
+                border={false}
+                isLink={false}
+                title={item.name}
+                titleClass={'van-ellipsis'}
+                titleStyle={{ width: '80%' }}
+                name={item.id}>
+                {{
+                  default: () => (
+                    <>
+                      {item.knowledgeList.map((know: any) => (
+                        <div
+                          class={[
+                            styles.item,
+                            itemActive.value == know.id ? styles.itemActive : ''
+                          ]}
+                          onClick={() => {
+                            // 判断是否选择的同一个课件
+                            if (itemActive.value == know.id) {
+                              return;
+                            }
+                            if (!know.containMaterial) {
+                              showToast('该章节暂无课件');
+                              return;
+                            }
+                            emit('handleSelect', {
+                              itemActive: know.id,
+                              itemName: know.name,
+                              tabActive: item.id,
+                              tabName: item.name
+                            });
+                            const lastTimeKey =
+                              'lastTime' + (state?.user?.data?.phone ?? '');
+                            localStorage.setItem(lastTimeKey, know.id);
+                          }}>
+                          <Image src={chapterVideo} class={styles.itemImage} />
+                          <span style={{ width: '80%' }} class="van-ellipsis">
+                            {know.name}
+                          </span>
+                        </div>
+                      ))}
+                    </>
+                  ),
+                  icon: () => (
+                    <img
+                      class={styles.arrow}
+                      src={
+                        pointData.active === item.id
+                          ? chapterDown
+                          : chapterDefault
+                      }
+                    />
+                  )
+                }}
+              </CollapseItem>
+            ))}
+          </Collapse>
+        </div>
+      </div>
+    );
+  }
+});

+ 15 - 0
src/views/courseware-play/image/chapter-default-arrow.svg

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <g id="修改页面8.18" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="5、切换单元备份-3" transform="translate(-558.000000, -246.000000)" fill="#AAAAAA">
+            <g id="编组-9" transform="translate(542.000000, 0.000000)">
+                <g id="编组-5" transform="translate(16.000000, 242.000000)">
+                    <g id="展开更多备份" transform="translate(6.000000, 10.000000) rotate(-90.000000) translate(-6.000000, -10.000000) translate(0.000000, 4.000000)">
+                        <path d="M6.38411064,3.96093277 L10.3165898,8.6799078 C10.4933719,8.89204625 10.4647098,9.2073286 10.2525714,9.38411064 C10.1627139,9.45899189 10.0494474,9.5 9.93247919,9.5 L2.06752081,9.5 C1.79137843,9.5 1.56752081,9.27614237 1.56752081,9 C1.56752081,8.88303175 1.60852892,8.7697653 1.68341017,8.6799078 L5.61588936,3.96093277 C5.7926714,3.74879432 6.10795375,3.72013229 6.3200922,3.89691433 C6.34332373,3.91627394 6.36475103,3.93770124 6.38411064,3.96093277 Z" id="展开更多" transform="translate(6.000000, 6.500000) rotate(-180.000000) translate(-6.000000, -6.500000) "></path>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 15 - 0
src/views/courseware-play/image/chapter-down-arrow.svg

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <g id="修改页面8.18" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="5、切换单元备份-3" transform="translate(-558.000000, -62.000000)" fill="#1C9AF7">
+            <g id="编组-9" transform="translate(542.000000, 0.000000)">
+                <g id="编组-11" transform="translate(16.000000, 58.000000)">
+                    <g id="展开更多" transform="translate(0.000000, 4.000000)">
+                        <path d="M6.38411064,3.96093277 L10.3165898,8.6799078 C10.4933719,8.89204625 10.4647098,9.2073286 10.2525714,9.38411064 C10.1627139,9.45899189 10.0494474,9.5 9.93247919,9.5 L2.06752081,9.5 C1.79137843,9.5 1.56752081,9.27614237 1.56752081,9 C1.56752081,8.88303175 1.60852892,8.7697653 1.68341017,8.6799078 L5.61588936,3.96093277 C5.7926714,3.74879432 6.10795375,3.72013229 6.3200922,3.89691433 C6.34332373,3.91627394 6.36475103,3.93770124 6.38411064,3.96093277 Z" transform="translate(6.000000, 6.500000) rotate(-180.000000) translate(-6.000000, -6.500000) "></path>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 34 - 0
src/views/courseware-play/image/chapter-video.svg

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <defs>
+        <linearGradient x1="19.0447587%" y1="-4.51028104e-15%" x2="79.7055169%" y2="97.9814009%" id="linearGradient-1">
+            <stop stop-color="#FFC9C4" offset="0%"></stop>
+            <stop stop-color="#FF928B" offset="100%"></stop>
+        </linearGradient>
+        <linearGradient x1="19.0447587%" y1="25.5626814%" x2="80.9552413%" y2="100%" id="linearGradient-2">
+            <stop stop-color="#FFB469" offset="0%"></stop>
+            <stop stop-color="#ACD7FF" offset="100%"></stop>
+        </linearGradient>
+        <path d="M11.2061398,15.0811719 L11.1701517,15.0811719 L0.622734167,14.4679427 C0.268458993,14.4402344 -0.00389343752,14.1403646 4.21151333e-05,13.7819792 L4.21151333e-05,1.29791667 C-0.00389343752,0.93953125 0.268458993,0.63984375 0.622734167,0.611953125 L11.1701517,2.22044605e-16 L11.2075866,2.22044605e-16 C11.5768719,0.007109375 11.8712874,0.313541667 11.8662881,0.685963542 L11.8662881,14.395026 C11.8703832,14.7676302 11.575606,15.0740625 11.2061398,15.0811719 Z M4.51614418,5.24507813 C4.14034846,5.24507813 3.83562479,5.55223958 3.83562479,5.93104167 L3.83562479,9.13901042 C3.83562479,9.5178125 4.14034846,9.82497396 4.51614418,9.82497396 C4.63256363,9.82437819 4.74688482,9.79368631 4.84817543,9.73583333 L7.67496218,8.13221354 C7.89026679,8.01173235 8.02309534,7.78233446 8.02129854,7.53411458 C8.02308862,7.28601563 7.89034846,7.05651042 7.67496218,6.93601563 L4.84817543,5.333125 C4.74709003,5.27558408 4.63295746,5.24537832 4.51686756,5.24544271 L4.51614418,5.24544271 L4.51614418,5.24507813 Z" id="path-3"></path>
+    </defs>
+    <g id="修改页面8.18" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="5、切换单元备份-3" transform="translate(-578.000000, -95.000000)" fill-rule="nonzero">
+            <g id="编组-9" transform="translate(542.000000, 0.000000)">
+                <g id="编组-11" transform="translate(16.000000, 58.000000)">
+                    <g id="编组-4" transform="translate(20.000000, 36.000000)">
+                        <g id="视频" transform="translate(0.000000, 1.000000)">
+                            <g id="课程填充" transform="translate(1.000000, 0.460000)">
+                                <path d="M10.8209402,12.9246615 L12.873349,13.0227344 C13.2368473,13.0653906 13.565985,12.8027083 13.6081219,12.4361198 C13.609207,12.4260938 13.6101112,12.4160677 13.6108346,12.4060417 L13.6108346,2.66583333 C13.5855163,2.29778646 13.2690377,2.01997396 12.9039118,2.0453125 C12.8928803,2.04604167 12.8820296,2.04713542 12.8711789,2.04841146 L10.81877,2.14648438" id="路径" fill="#A6CAEC"></path>
+                                <g id="形状">
+                                    <use fill="url(#linearGradient-1)" xlink:href="#path-3"></use>
+                                    <use fill="url(#linearGradient-2)" xlink:href="#path-3"></use>
+                                </g>
+                            </g>
+                        </g>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 21 - 0
src/views/courseware-play/image/icon-change.svg

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <g id="修改页面8.18" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="3、课件播放加章节" transform="translate(-698.000000, -77.000000)">
+            <g id="编组-5" transform="translate(670.000000, 74.000000)">
+                <g id="编组-4备份" transform="translate(19.000000, 0.000000)">
+                    <g id="编组-2" transform="translate(9.000000, 3.000000)">
+                        <g id="编组-7" transform="translate(4.500000, 4.000000)">
+                            <rect id="矩形" stroke="#FFFFFF" stroke-width="2" stroke-linecap="square" x="0" y="0" width="15" height="16" rx="4"></rect>
+                            <g id="编组-3" transform="translate(3.500000, 4.000000)" fill="#FFFFFF">
+                                <rect id="矩形" x="0" y="0" width="8" height="2" rx="1"></rect>
+                                <rect id="矩形备份-2" x="0" y="5" width="6" height="2" rx="1"></rect>
+                            </g>
+                        </g>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 15 - 0
src/views/courseware-play/image/icon-menu-chapter.svg

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="18px" height="20px" viewBox="0 0 18 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>编组 10</title>
+    <g id="修改页面8.18" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="5、切换单元备份-3" transform="translate(-556.000000, -17.000000)">
+            <g id="编组-9" transform="translate(542.000000, 0.000000)">
+                <g id="编组-10" transform="translate(14.000000, 17.000000)">
+                    <rect id="矩形" fill="#1C9AF7" x="0" y="0" width="18" height="20" rx="5.45454545"></rect>
+                    <rect id="矩形" fill="#FFFFFF" x="4.09090909" y="5" width="9.81818182" height="2.5" rx="1.25"></rect>
+                    <rect id="矩形备份" fill="#FFFFFF" x="4.09090909" y="10" width="7.36363636" height="2.5" rx="1.25"></rect>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 3 - 1
src/views/courseware-play/index.module.less

@@ -165,7 +165,7 @@
 .rightFixedBtns {
   position: absolute;
   top: 50%;
-  transform: translateY(-50%);
+  transform: translateY(-55%);
   right: 12px;
   z-index: 10;
 }
@@ -183,6 +183,7 @@
   background: rgba(51, 51, 51, 0.8);
   border-radius: 6px;
   overflow: hidden;
+
   &:not(:last-child):not(:first-child) {
     margin: 8px 0;
   }
@@ -190,6 +191,7 @@
   &:active {
     opacity: .8;
   }
+
   &.btnsDisabled {
     opacity: .3;
     pointer-events: none;

+ 231 - 168
src/views/courseware-play/index.tsx

@@ -21,6 +21,7 @@ import {
 } from '@/helpers/native-message';
 import MusicScore from './component/musicScore';
 import iconMenu from './image/icon-menu.svg';
+import iconChange from './image/icon-change.svg';
 import iconDian from './image/icon-dian.svg';
 import iconPoint from './image/icon-point.svg';
 import iconUp from './image/icon-up.svg';
@@ -36,6 +37,8 @@ import {
   api_lessonCoursewareKnowledgeDetailDetail
 } from './api';
 import VideoItem from './component/video-item';
+import Chapter from './component/chapter';
+import { api_lessonCoursewareDetail } from '../courseware-list/api';
 
 export default defineComponent({
   name: 'CoursewarePlay',
@@ -100,15 +103,33 @@ export default defineComponent({
       window.removeEventListener('message', iframeHandle);
     });
 
+    const getCourseDetail = async () => {
+      try {
+        const res = await api_lessonCoursewareDetail(
+          route.query.lessonCoursewareId as any
+        );
+        if (res?.code == 200 && Array.isArray(res?.data?.lessonList)) {
+          data.courseDetails = res.data.lessonList || [];
+        }
+      } catch {
+        //
+      }
+    };
+
     const route = useRoute();
     const headeRef = ref();
+    const loadingClass = ref(false); // 重新加载课件
     const data = reactive({
       knowledgePointList: [] as any,
+      courseDetails: [] as any,
       itemList: [] as any,
       videoRefs: {} as any[]
     });
     const activeData = reactive({
       isAutoPlay: true, // 是否自动播放
+      lessonCoursewareId: route.query.lessonCoursewareId,
+      lessonCoursewareDetailId: route.query.lessonCoursewareDetailId,
+      coursewareDetailKnowledgeId: route.query.id,
       nowTime: 0,
       model: true, // 遮罩
       isAnimation: true, // 是否动画
@@ -122,7 +143,7 @@ export default defineComponent({
       let courseList: any[] = [];
       if (route.query.tab == 'course') {
         const res = await api_classLessonCoursewareQuery({
-          coursewareDetailKnowledgeId: route.query.id,
+          coursewareDetailKnowledgeId: activeData.coursewareDetailKnowledgeId,
           page: 1,
           rows: -1
         });
@@ -143,7 +164,8 @@ export default defineComponent({
         }
       } else {
         const res = await api_lessonCoursewareKnowledgeDetailDetail({
-          lessonCoursewareKnowledgeDetailId: route.query.id
+          lessonCoursewareKnowledgeDetailId:
+            activeData.coursewareDetailKnowledgeId
         });
         if (res?.code === 200 && Array.isArray(res.data)) {
           courseList = res.data || [];
@@ -184,9 +206,9 @@ export default defineComponent({
         activeData.model =
           ev.data.show || (ev.data.playState == 'play' ? false : true);
       }
-      if (ev.data?.api === 'api_fingerPreView'){
-        clearInterval(activeData.timer)
-        activeData.model = !ev.data.state
+      if (ev.data?.api === 'api_fingerPreView') {
+        clearInterval(activeData.timer);
+        activeData.model = !ev.data.state;
       }
     };
 
@@ -199,6 +221,7 @@ export default defineComponent({
         }
       });
       getDetail();
+      getCourseDetail();
       window.addEventListener('message', iframeHandle);
     });
 
@@ -214,9 +237,11 @@ export default defineComponent({
 
     const popupData = reactive({
       open: false,
+
       activeIndex: 0,
       itemActive: '',
-      itemName: ''
+      itemName: '',
+      chapterOpen: false
     });
 
     // 切换素材
@@ -337,176 +362,185 @@ export default defineComponent({
     };
     return () => (
       <div id="playContent" class={styles.playContent}>
-        <div>
-          <div
-            class={styles.coursewarePlay}
-            style={{ width: parentContainer.width }}
-            onClick={() => setModelOpen()}>
-            <div class={styles.wraps}>
-              {data.itemList.map((m: any, mIndex: number) => {
-                const isRender =
-                  m.isRender || Math.abs(popupData.activeIndex - mIndex) < 2;
-                const isEmtry = Math.abs(popupData.activeIndex - mIndex) > 4;
-                if (isRender) {
-                  m.isRender = true;
-                }
-                return isRender ? (
+        {!loadingClass.value && (
+          <div>
+            <div
+              class={styles.coursewarePlay}
+              style={{ width: parentContainer.width }}
+              onClick={() => setModelOpen()}>
+              <div class={styles.wraps}>
+                {data.itemList.map((m: any, mIndex: number) => {
+                  const isRender =
+                    m.isRender || Math.abs(popupData.activeIndex - mIndex) < 2;
+                  const isEmtry = Math.abs(popupData.activeIndex - mIndex) > 4;
+                  if (isRender) {
+                    m.isRender = true;
+                  }
+                  return isRender ? (
+                    <div
+                      key={'index' + mIndex}
+                      class={[
+                        styles.itemDiv,
+                        popupData.activeIndex === mIndex && styles.itemActive,
+                        activeData.isAnimation && styles.acitveAnimation,
+                        Math.abs(popupData.activeIndex - mIndex) < 2
+                          ? styles.show
+                          : styles.hide
+                      ]}
+                      style={
+                        mIndex < popupData.activeIndex
+                          ? effects[effectIndex.value].prev
+                          : mIndex > popupData.activeIndex
+                          ? effects[effectIndex.value].next
+                          : {}
+                      }
+                      onClick={(e: Event) => {
+                        if (Date.now() - activeData.nowTime < 300) {
+                          handleDbClick(m);
+                          return;
+                        }
+                        activeData.nowTime = Date.now();
+                      }}>
+                      {m.type === 'IMG' && <img src={m.content} />}
+                      {m.type === 'VIDEO' && (
+                        <VideoItem
+                          ref={(v: any) => (data.videoRefs[mIndex] = v)}
+                          item={m}
+                          show={popupData.activeIndex === mIndex}
+                          pageVisibility={pageVisibility.value}
+                          showModel={activeData.model}
+                          isEmtry={isEmtry}
+                          onLoadedmetadata={() => {
+                            m.isprepare = true;
+                            m.error = false;
+                          }}
+                          onEnded={() => {
+                            const _index = popupData.activeIndex + 1;
+                            if (_index < data.itemList.length) {
+                              handleSwipeChange(_index);
+                            }
+                          }}
+                          onReset={() => {
+                            m.error = false;
+                          }}
+                          onError={() => {
+                            m.isprepare = true;
+                            m.error = true;
+                          }}
+                        />
+                      )}
+                      {m.type === 'SONG' && (
+                        <AudioItem
+                          item={m}
+                          show={popupData.activeIndex === mIndex}
+                          pageVisibility={pageVisibility.value}
+                          showModel={activeData.model}
+                          isEmtry={isEmtry}
+                          onEnded={() => {
+                            const _index = popupData.activeIndex + 1;
+                            if (_index < data.itemList.length) {
+                              handleSwipeChange(_index);
+                            }
+                          }}
+                          onClose={() => {
+                            clearTimeout(activeData.timer);
+                            activeData.timer = setTimeout(() => {
+                              activeData.model = false;
+                            }, 4000);
+                          }}
+                        />
+                      )}
+                      {m.type === 'MUSIC' && (
+                        <MusicScore
+                          pageVisibility={pageVisibility.value}
+                          show={popupData.activeIndex === mIndex}
+                          activeModel={activeData.model}
+                          data-vid={m.id}
+                          music={m}
+                        />
+                      )}
+
+                      {m.type === 'VIDEO' && (
+                        <Transition name="van-fade">
+                          {!m.isprepare && (
+                            <div class={styles.loadWrap}>
+                              <Vue3Lottie
+                                style={{ width: '100%', height: '100%' }}
+                                animationData={playLoadData}></Vue3Lottie>
+                            </div>
+                          )}
+                        </Transition>
+                      )}
+                    </div>
+                  ) : (
+                    <div
+                      key={'index' + mIndex}
+                      class={[
+                        styles.itemDiv,
+                        popupData.activeIndex === mIndex && styles.itemActive,
+                        activeData.isAnimation && styles.acitveAnimation,
+                        Math.abs(popupData.activeIndex - mIndex) < 2
+                          ? styles.show
+                          : styles.hide
+                      ]}
+                      style={
+                        mIndex < popupData.activeIndex
+                          ? effects[effectIndex.value].prev
+                          : mIndex > popupData.activeIndex
+                          ? effects[effectIndex.value].next
+                          : {}
+                      }></div>
+                  );
+                })}
+              </div>
+              <Transition name="right">
+                {activeData.model && (
                   <div
-                    key={'index' + mIndex}
-                    class={[
-                      styles.itemDiv,
-                      popupData.activeIndex === mIndex && styles.itemActive,
-                      activeData.isAnimation && styles.acitveAnimation,
-                      Math.abs(popupData.activeIndex - mIndex) < 2
-                        ? styles.show
-                        : styles.hide
-                    ]}
-                    style={
-                      mIndex < popupData.activeIndex
-                        ? effects[effectIndex.value].prev
-                        : mIndex > popupData.activeIndex
-                        ? effects[effectIndex.value].next
-                        : {}
-                    }
+                    class={styles.rightFixedBtns}
                     onClick={(e: Event) => {
-                      if (Date.now() - activeData.nowTime < 300) {
-                        handleDbClick(m);
-                        return;
-                      }
-                      activeData.nowTime = Date.now();
+                      e.stopPropagation();
+                      clearTimeout(activeData.timer);
                     }}>
-                    {m.type === 'IMG' && <img src={m.content} />}
-                    {m.type === 'VIDEO' && (
-                      <VideoItem
-                        ref={(v: any) => (data.videoRefs[mIndex] = v)}
-                        item={m}
-                        show={popupData.activeIndex === mIndex}
-                        pageVisibility={pageVisibility.value}
-                        showModel={activeData.model}
-                        isEmtry={isEmtry}
-                        onLoadedmetadata={() => {
-                          m.isprepare = true;
-                          m.error = false;
-                        }}
-                        onEnded={() => {
-                          const _index = popupData.activeIndex + 1;
-                          if (_index < data.itemList.length) {
-                            handleSwipeChange(_index);
-                          }
-                        }}
-                        onReset={() => {
-                          m.error = false;
-                        }}
-                        onError={() => {
-                          m.isprepare = true;
-                          m.error = true;
-                        }}
-                      />
-                    )}
-                    {m.type === 'SONG' && (
-                      <AudioItem
-                        item={m}
-                        show={popupData.activeIndex === mIndex}
-                        pageVisibility={pageVisibility.value}
-                        showModel={activeData.model}
-                        isEmtry={isEmtry}
-                        onEnded={() => {
-                          const _index = popupData.activeIndex + 1;
-                          if (_index < data.itemList.length) {
-                            handleSwipeChange(_index);
-                          }
-                        }}
-                        onClose={() => {
-                          clearTimeout(activeData.timer);
-                          activeData.timer = setTimeout(() => {
-                            activeData.model = false;
-                          }, 4000);
-                        }}
-                      />
-                    )}
-                    {m.type === 'MUSIC' && (
-                      <MusicScore
-                        pageVisibility={pageVisibility.value}
-                        show={popupData.activeIndex === mIndex}
-                        activeModel={activeData.model}
-                        data-vid={m.id}
-                        music={m}
-                      />
-                    )}
+                    <div
+                      class={[styles.fullBtn, styles.point]}
+                      onClick={() => (popupData.chapterOpen = true)}>
+                      <img src={iconChange} />
+                      <span>切换</span>
+                    </div>
 
-                    {m.type === 'VIDEO' && (
-                      <Transition name="van-fade">
-                        {!m.isprepare && (
-                          <div class={styles.loadWrap}>
-                            <Vue3Lottie
-                              style={{ width: '100%', height: '100%' }}
-                              animationData={playLoadData}></Vue3Lottie>
-                          </div>
-                        )}
-                      </Transition>
-                    )}
-                  </div>
-                ) : (
-                  <div
-                    key={'index' + mIndex}
-                    class={[
-                      styles.itemDiv,
-                      popupData.activeIndex === mIndex && styles.itemActive,
-                      activeData.isAnimation && styles.acitveAnimation,
-                      Math.abs(popupData.activeIndex - mIndex) < 2
-                        ? styles.show
-                        : styles.hide
-                    ]}
-                    style={
-                      mIndex < popupData.activeIndex
-                        ? effects[effectIndex.value].prev
-                        : mIndex > popupData.activeIndex
-                        ? effects[effectIndex.value].next
-                        : {}
-                    }></div>
-                );
-              })}
-            </div>
-            <Transition name="right">
-              {activeData.model && (
-                <div
-                  class={styles.rightFixedBtns}
-                  onClick={(e: Event) => {
-                    e.stopPropagation();
-                    clearTimeout(activeData.timer);
-                  }}>
-                  <div
-                    class={[styles.fullBtn, styles.point]}
-                    onClick={() => (popupData.open = true)}>
-                    <img src={iconMenu} />
-                    <span>课件</span>
-                  </div>
+                    <div
+                      class={[styles.fullBtn, styles.point]}
+                      onClick={() => (popupData.open = true)}>
+                      <img src={iconMenu} />
+                      <span>课件</span>
+                    </div>
 
-                  <div
-                    class={[
-                      styles.fullBtn,
-                      popupData.activeIndex == 0 && styles.btnsDisabled
-                    ]}
-                    onClick={() => handlePreAndNext('up')}>
-                    <img src={iconUp} />
-                    <span style={{ textAlign: 'center' }}>上一个</span>
-                  </div>
+                    <div
+                      class={[
+                        styles.fullBtn,
+                        popupData.activeIndex == 0 && styles.btnsDisabled
+                      ]}
+                      onClick={() => handlePreAndNext('up')}>
+                      <img src={iconUp} />
+                      <span style={{ textAlign: 'center' }}>上一个</span>
+                    </div>
 
-                  <div
-                    class={[
-                      styles.fullBtn,
-                      popupData.activeIndex == data.itemList.length - 1 &&
-                        styles.btnsDisabled
-                    ]}
-                    onClick={() => handlePreAndNext('down')}>
-                    <span style={{ textAlign: 'center' }}>下一个</span>
-                    <img src={iconDown} />
+                    <div
+                      class={[
+                        styles.fullBtn,
+                        popupData.activeIndex == data.itemList.length - 1 &&
+                          styles.btnsDisabled
+                      ]}
+                      onClick={() => handlePreAndNext('down')}>
+                      <span style={{ textAlign: 'center' }}>下一个</span>
+                      <img src={iconDown} />
+                    </div>
                   </div>
-                </div>
-              )}
-            </Transition>
+                )}
+              </Transition>
+            </div>
           </div>
-        </div>
+        )}
 
         <div
           style={{ transform: activeData.model ? '' : 'translateY(-100%)' }}
@@ -519,6 +553,7 @@ export default defineComponent({
           <div class={styles.menu}>{popupData.itemName}</div>
         </div>
 
+        {/* 课件列表 */}
         <Popup
           class={styles.popup}
           style={{ background: 'rgba(0,0,0, 0.75)' }}
@@ -536,6 +571,34 @@ export default defineComponent({
             }}
           />
         </Popup>
+
+        {/* 知识点列表 */}
+        <Popup
+          class={styles.popup}
+          style={{ background: 'rgba(0,0,0, 0.75)' }}
+          overlayClass={styles.overlayClass}
+          position="right"
+          round
+          v-model:show={popupData.chapterOpen}
+          onClose={handleClosePopup}>
+          <Chapter
+            detail={data.courseDetails}
+            itemActive={activeData.coursewareDetailKnowledgeId as any}
+            active={activeData.lessonCoursewareDetailId as any}
+            onHandleSelect={async (item: any) => {
+              console.log(item, 'item');
+              loadingClass.value = true;
+              activeData.coursewareDetailKnowledgeId = item.itemActive;
+              activeData.lessonCoursewareDetailId = item.tabActive;
+              await getDetail();
+              popupData.activeIndex = 0;
+              popupData.itemActive = item.itemActive;
+              popupData.itemName = item.itemName;
+              popupData.chapterOpen = false;
+              loadingClass.value = false;
+            }}
+          />
+        </Popup>
       </div>
     );
   }