lex-xin 4 months ago
parent
commit
61d852256e

+ 8 - 0
src/router/routes-tenant.ts

@@ -131,6 +131,14 @@ export default [
         }
       },
       {
+        path: '/courseListSearch',
+        name: 'courseListSearch',
+        component: () => import('@/tenant/music/courseListSearch/index'),
+        meta: {
+          title: '搜索'
+        }
+      },
+      {
         path: '/courseList',
         component: () => import('@/tenant/music/courseList'),
         meta: {

BIN
src/tenant/music/courseList/image/icon-search.png


+ 124 - 110
src/tenant/music/courseList/index.tsx

@@ -23,6 +23,7 @@ import {
 import iconCourse from './image/icon-course.png'
 import iconCachePoint from './image/icon-cache-point.png'
 import play from './image/paly.png'
+import iconSearch from './image/icon-search.png'
 import { browser } from '@/helpers/utils'
 import ColResult from '@/components/col-result'
 import { useEventListener } from '@vant/use'
@@ -387,123 +388,136 @@ export default defineComponent({
             color="#131415"
           />
         </TheSticky>
-        <div class={styles.periodContent}>
-          <div class={styles.cover}>
-            <img
-              src={data.detail.coverImg}
-              onLoad={(e: Event) => {
-                if (e.target) {
-                  ;(e.target as any).style.opacity = 1
-                }
-              }}
-            />
-          </div>
-          <div>
-            <div class={styles.contentTitle}>{data.detail.name}</div>
-            <div class={styles.contentLabel}>
-              教学目标:{data.detail.lessonTargetDesc}
+        <div style="height: calc(100vh - var(--header-height, 0px) - var(--bottom-height, 0px)); overflow-x: hidden; overflow-y: auto;display:flex; flex-direction: column;">
+          <div class={styles.periodContent}>
+            <div class={styles.cover}>
+              <img
+                src={data.detail.coverImg}
+                onLoad={(e: Event) => {
+                  if (e.target) {
+                    ;(e.target as any).style.opacity = 1
+                  }
+                }}
+              />
             </div>
-          </div>
-        </div>
-        <TransitionGroup name="van-fade">
-          {!data.loading && (
-            <>
-              <div key="periodTitle" class={styles.periodTitle}>
-                <img class={styles.pIcon} src={iconList} />
-                <div class={styles.pTitle}>课程列表</div>
-                <div class={styles.pNum}>共{data.list.length}课</div>
+            <div>
+              <div class={styles.contentTitle}>{data.detail.name}</div>
+              <div class={styles.contentLabel}>
+                教学目标:{data.detail.lessonTargetDesc}
               </div>
+            </div>
+          </div>
+          <TransitionGroup name="van-fade">
+            {!data.loading && (
+              <>
+                <div key="periodTitle" class={styles.periodTitle}>
+                    <img class={styles.pIcon} src={iconList} />
+                    <div class={styles.pTitle}>课程列表</div>
+                    <div class={styles.pNum}>共{data.list.length}课</div>
+                  </div>
+                  <div class={styles.searchGroup} onClick={() => {
+                    router.push({
+                      path: '/courseListSearch',
+                      query: {
+                        id: route.query.id
+                      }
+                    })
+                  }}>
+                    <img src={iconSearch} class={styles.iconSearch} />
+                    <span class={styles.searchContent}>搜索素材</span>
+                  </div>
 
-              <div key="list" class={styles.periodList}>
-                <CellGroup inset>
-                  {data.list.map((item: any) => {
-                    // const isLock =
-                    //   item.lockFlag ||
-                    //   ((route.query.code == 'select' ||
-                    //     state.platformType == 'STUDENT') &&
-                    //     !item.unlock);
-                    // const isSelect = route.query.code === 'select';
-                    return (
-                      <Cell
-                        border
-                        center
-                        title={item.coursewareDetailName}
-                        // label={
-                        //   !browserInfo.isStudent
-                        //     ? `已使用${item.useNum || 0}次`
-                        //     : ''
-                        // }
-                        onClick={() => handleClick(item)}
-                      >
-                        {{
-                          icon: () => (
-                            <div class={styles.periodItem}>
-                              <div class={styles.periodItemModel}>
-                                <img src={iconCourse} />
-                                {item.hasCache ? (
-                                  <img
-                                    class={styles.iconCachePoint}
-                                    src={iconCachePoint}
-                                  />
-                                ) : (
-                                  ''
-                                )}
-                                {item.downloadStatus == 1 && (
-                                  <div class={styles.downloading}>{`${
-                                    item.progress || 0
-                                  }%`}</div>
-                                )}
-                              </div>
-                            </div>
-                          ),
-                          value: () => (
-                            <>
-                              {item.knowledgePointList ? (
-                                <div class={styles.circleProgress}>
-                                  {item.hasCache ||
-                                  item.downloadStatus !== 1 ? (
-                                    <img class={styles.basePlay} src={play} />
+                <div key="list" class={styles.periodList}>
+                  <CellGroup inset>
+                    {data.list.map((item: any) => {
+                      // const isLock =
+                      //   item.lockFlag ||
+                      //   ((route.query.code == 'select' ||
+                      //     state.platformType == 'STUDENT') &&
+                      //     !item.unlock);
+                      // const isSelect = route.query.code === 'select';
+                      return (
+                        <Cell
+                          border
+                          center
+                          title={item.coursewareDetailName}
+                          // label={
+                          //   !browserInfo.isStudent
+                          //     ? `已使用${item.useNum || 0}次`
+                          //     : ''
+                          // }
+                          onClick={() => handleClick(item)}
+                        >
+                          {{
+                            icon: () => (
+                              <div class={styles.periodItem}>
+                                <div class={styles.periodItemModel}>
+                                  <img src={iconCourse} />
+                                  {item.hasCache ? (
+                                    <img
+                                      class={styles.iconCachePoint}
+                                      src={iconCachePoint}
+                                    />
                                   ) : (
-                                    <>
-                                      <div class={styles.tips}></div>
-                                      <Circle
-                                        v-model:current-rate={item.progress}
-                                        rate={item.progress}
-                                        speed={10}
-                                        stroke-width={80}
-                                        layer-color={'#B3B3B3'}
-                                        color={'#FE2451'}
-                                      />
-                                    </>
+                                    ''
+                                  )}
+                                  {item.downloadStatus == 1 && (
+                                    <div class={styles.downloading}>{`${
+                                      item.progress || 0
+                                    }%`}</div>
                                   )}
                                 </div>
-                              ) : (
-                                ''
-                              )}
-                            </>
-                          )
-                        }}
-                      </Cell>
-                    )
-                  })}
-                </CellGroup>
-              </div>
-            </>
+                              </div>
+                            ),
+                            value: () => (
+                              <>
+                                {item.knowledgePointList ? (
+                                  <div class={styles.circleProgress}>
+                                    {item.hasCache ||
+                                    item.downloadStatus !== 1 ? (
+                                      <img class={styles.basePlay} src={play} />
+                                    ) : (
+                                      <>
+                                        <div class={styles.tips}></div>
+                                        <Circle
+                                          v-model:current-rate={item.progress}
+                                          rate={item.progress}
+                                          speed={10}
+                                          stroke-width={80}
+                                          layer-color={'#B3B3B3'}
+                                          color={'#FE2451'}
+                                        />
+                                      </>
+                                    )}
+                                  </div>
+                                ) : (
+                                  ''
+                                )}
+                              </>
+                            )
+                          }}
+                        </Cell>
+                      )
+                    })}
+                  </CellGroup>
+                </div>
+              </>
+            )}
+          </TransitionGroup>
+          {data.loading && (
+            <div>
+              <Vue3Lottie
+                animationData={AstronautJSON}
+                class={styles.finch}
+              ></Vue3Lottie>
+              {/* <p class={styles.finchLoad}>加载中...</p> */}
+            </div>
           )}
-        </TransitionGroup>
-        {data.loading && (
-          <div>
-            <Vue3Lottie
-              animationData={AstronautJSON}
-              class={styles.finch}
-            ></Vue3Lottie>
-            {/* <p class={styles.finchLoad}>加载中...</p> */}
-          </div>
-        )}
-        {!data.loading && !data.list.length && (
-          <ColResult tips="暂无内容" classImgSize="SMALL" btnStatus={false} />
-        )}
-        <TheSticky position="bottom">
+          {!data.loading && !data.list.length && (
+            <ColResult tips="暂无内容" classImgSize="SMALL" btnStatus={false} />
+          )}
+        </div>
+        <TheSticky position="bottom" varName="--bottom-height">
           {data.detail.id && !data.detail.play && (
             <div class={styles.footers}>
               <Button

+ 154 - 0
src/tenant/music/courseListSearch/child-node.tsx

@@ -0,0 +1,154 @@
+import { defineComponent } from "vue";
+import styles from './index.module.less'
+import { Cell, Collapse, CollapseItem } from "vant";
+import iconVideo from './image/icon-video.png'
+import iconSong from './image/icon-song.png'
+import iconImage from './image/icon-image.png'
+import { handleShowVip } from "@/state";
+import { browser } from "@/helpers/utils";
+import {  postMessage } from '@/helpers/native-message';
+import { useRouter } from "vue-router";
+
+// 获取对应图片
+export const getImage = (item: any) => {
+  if (item.typeCode === 'VIDEO') {
+    return iconVideo;
+  } else if (['IMAGE', 'IMG'].includes(item.typeCode)) {
+    return iconImage;
+  } else if (item.typeCode === 'SONG') {
+    return iconSong;
+  } else {
+    return iconVideo;
+  }
+};
+
+const ChildNode = defineComponent({
+  name: 'child-node',
+  props: {
+    id: {
+      type: String,
+      default: ''
+    },
+    search: {
+      type: String,
+      default: ''
+    },
+    isLock: {
+      type: Boolean,
+      default: false,
+    },
+    list: {
+      type: Array,
+      default: () => []
+    },
+    collapse: {
+      type: String,
+      default: ''
+    }
+  },
+  emits: ['update:collapse'],
+  setup(props, { emit }) {
+    const router = useRouter()
+    const toDetail = (item: any) => {
+      // 
+      if(props.isLock) {
+        handleShowVip(props.id, "LESSON")
+        return
+      }
+
+      if (browser().isApp) {
+        postMessage({
+          api: 'openWebView',
+          content: {
+            url: `${location.origin}${location.pathname}#/coursewarePlay?lessonId=${props.id}&source=search&kId=${item.id}&search=${encodeURIComponent(props.search)}`,
+            orientation: 0,
+            isHideTitle: true,
+            statusBarTextColor: false,
+            isOpenLight: true,
+            showLoadingAnim: true
+          }
+        });
+      } else {
+        router.push({
+          path: '/coursewarePlay',
+          query: {
+            lessonId: props.id,
+            kId: item.id,
+            search: props.search,
+            source: 'search'
+          }
+        });
+      }
+    }
+
+    const formatName = (name: string) => {
+      if(!name || !props.search) return name
+      const search: any = props.search
+      return name.replace(search, `<span style="color: #01C1B5;">${search}</span>`)
+    }
+
+    console.log(props.collapse, "collapse")
+    return () => (
+      <Collapse
+        modelValue={props.collapse}
+        onUpdate:modelValue={(val: any) => {
+          emit('update:collapse', val);
+        }}
+        border={false}
+        accordion>
+        {props.list?.map((point: any) => (
+          <CollapseItem
+            clickable={false}
+            center
+            border={false}
+            class={styles.collapseChild}
+            name={point.id}>
+            {{
+              title: () => (
+                <div
+                  class={[
+                    styles.itemTitle,
+                    Array.isArray(point?.materialList) && point?.materialList.length > 0 ? styles.materialTitle : ''
+                  ]}>
+                  {point.name}
+                </div>
+              ),
+              default: () => (
+                <>
+                  {Array.isArray(point?.materialList) &&
+                    point.materialList.map((n: any) => (
+                      <Cell center isLink border={false} clickable={false} onClick={() => toDetail(n)}>
+                        {{
+                          title: () => <div class={styles.materialSection}>
+                            <img src={getImage(n)} class={styles.iconMaterial} />
+                            <div v-html={formatName(n.name)}></div>
+                          </div>
+                        }}
+                      </Cell>
+                    ))}
+                  {Array.isArray(point?.children) && (
+                    <ChildNode
+                      isLock={props.isLock}
+                      search={props.search}
+                      id={props.id}
+                      list={point.children}
+                      collapse={point.collapse}
+                      onUpdate:collapse={val => {
+                        point.collapse = val;
+                      }}
+                    />
+                  )}
+                </>
+              ),
+              'right-icon': () => (
+                  <i class={[styles.arrow, props.collapse === point.id ? styles.arrowActive : '']}></i>
+              )
+            }}
+          </CollapseItem>
+        ))}
+      </Collapse>
+    );
+  }
+});
+
+export default ChildNode;

BIN
src/tenant/music/courseListSearch/image/icon-arrow-down.png


BIN
src/tenant/music/courseListSearch/image/icon-arrow-up.png


BIN
src/tenant/music/courseListSearch/image/icon-image.png


BIN
src/tenant/music/courseListSearch/image/icon-menu.png


BIN
src/tenant/music/courseListSearch/image/icon-song.png


BIN
src/tenant/music/courseListSearch/image/icon-video.png


+ 141 - 0
src/tenant/music/courseListSearch/index.module.less

@@ -0,0 +1,141 @@
+.courseListSearch {
+  min-height: 100vh;
+  background-color: #F8F8F8;
+  background: linear-gradient(180deg, #7defe6 0%, rgba(255, 255, 255, 0) 170px);
+  box-sizing: border-box;
+
+  :global {
+    .van-nav-bar__title {
+      width: 100%;
+      max-width: 100%;
+      margin-left: 46px;
+    }
+    .van-nav-bar__right {
+      display: none;
+    }
+    .van-search {
+      padding-left: 0;
+    }
+
+    .o-result-container {
+      height: 90vh;
+    }
+  }
+}
+
+.collapseParent {
+  padding-top: 12px;
+
+  
+  :global {
+    .parentCollapse__item {
+      margin: 0 14px 12px;
+      &::after {
+        display: none;
+      }
+  
+      & >  .van-collapse-item__wrapper {
+        margin: 0 14px 12px;
+      }
+    }
+    .van-cell {
+      padding: 14px;
+      font-weight: 600;
+    }
+
+    .van-collapse-item {
+      background-color: #fff;
+      border-radius: 12px;
+      overflow: hidden;
+      
+    }
+
+    .van-collapse-item--border:after {
+      left: 0;
+      right: 0;
+    }
+
+    .van-collapse-item__content {
+      padding-top: 0;
+      padding-bottom: 0;
+      padding-right: 0;
+      padding-left: 0;
+    }
+  }
+}
+
+.collapseChild {
+  // margin: 0 !important;
+  :global {
+    .van-cell {
+      padding: 12px 0;
+      font-weight: 400;
+      font-size: 15px;
+      color: #333333;
+      line-height: 21px;  
+    }
+    .van-collapse-item__content {
+      // padding-left: 22px;
+
+      .van-cell {
+        padding: 12px 0;
+      }
+
+      .van-cell__title {
+        flex: 1 auto;
+      }
+
+      .van-cell__value {
+        flex-shrink: 0;
+        flex: 1 auto;
+      }
+    }
+  }
+}
+.materialSection {
+  display: flex;
+  align-items: center;
+  color: #333333;
+  padding-left: 10px;
+  .iconMaterial {
+    width: 32px;
+    height: 32px;
+    margin-right: 10px;
+  }
+}
+
+
+.itemTitle {
+  display: flex;
+  align-items: center;
+  font-size: 15px;
+  color: #333333;
+  line-height: 21px;
+  .iconMenu {
+    width: 36px;
+    height: 40px;
+    margin-right: 8px;
+  }
+
+  &.materialTitle::before {
+    content: '';
+    width: 4px;
+    height: 4px;
+    background: #01C1B5;
+    border-radius: 50%;
+    display: inline-block;
+    margin-right: 6px;
+  }
+}
+.arrow {
+  width: 16px;
+  height: 17px;
+  display: inline-block;
+  background: url('./image/icon-arrow-down.png');
+  background-size: contain;
+
+  &.arrowActive {
+    background: url('./image/icon-arrow-up.png');
+    background-size: contain;
+  }
+}

+ 304 - 0
src/tenant/music/courseListSearch/index.tsx

@@ -0,0 +1,304 @@
+import {
+  computed,
+  defineComponent,
+  reactive,
+  TransitionGroup,
+  watch
+} from 'vue';
+import styles from './index.module.less';
+import OHeader from '@/components/o-header';
+import OSearch from '@/components/o-search';
+import request from '@/helpers/request';
+import { state } from '@/state';
+import { useRoute } from 'vue-router';
+import OLoading from '@/components/o-loading';
+import OEmpty from '@/components/o-empty';
+import { Cell, Collapse, CollapseItem } from 'vant';
+import ChildNode, { getImage } from './child-node';
+import { usePageVisibility } from '@vant/use';
+import iconMenu from './image/icon-menu.png';
+import OSticky from '@/components/o-sticky';
+
+export default defineComponent({
+  name: 'course-list-search',
+  setup() {
+    const route = useRoute();
+    const data = reactive({
+      titleOpacity: 0,
+      loading: true,
+      detail: {
+        // id: '',
+        // cover: '',
+        // name: '',
+        // des: '',
+        useStatus: ''
+      },
+      list: [] as any,
+      search: '' as string | undefined,
+      parentCollapse: '' as any
+      // childrenCollapse: '' as any
+    });
+    const pageVisibility = usePageVisibility();
+    /** 是否锁定 */
+    const isLock = computed(() => {
+      return data.detail.useStatus === 'LOCK' &&
+        state.platformType === 'STUDENT'
+        ? true
+        : false;
+    });
+    const formatDataList = (list: any = []) => {
+      const tempList: any = [];
+      list.forEach((item: any) => {
+        let tempChild: any = {};
+        item.collapse = '';
+        tempChild = item;
+        if (Array.isArray(item.children)) {
+          tempChild.children = formatDataList(item.children);
+        }
+        tempList.push({
+          ...tempChild
+        });
+      });
+      return tempList;
+    };
+
+    const formatSelectFirstChild = (list: any = []): any => {
+      for (let i = 0; i < list.length; i++) {
+        if (
+          Array.isArray(list[i].materialList) &&
+          list[i].materialList.length > 0
+        ) {
+          const materialItem = list[i].materialList[0] || {};
+          if (materialItem) {
+            return materialItem;
+          }
+        } else {
+          return formatSelectFirstChild(list[i].children || []);
+        }
+      }
+    };
+
+    //
+    function selectFirstChild(pointList: any[], ids: any[]): any[] {
+      return pointList.map(point => {
+        if (point.children) {
+          let id = '';
+          point.children.map((item: any) => {
+            if (!id) {
+              id = ids.includes(item.id) ? item.id : '';
+            }
+          });
+          point.collapse = id;
+          return Object.assign(point, {
+            children: selectFirstChild(point.children, ids)
+          });
+        } else {
+          return Object.assign(point, {
+            materialList: point.materialList
+          });
+        }
+      });
+    }
+
+    function filterPointList(
+      pointList: any[],
+      parentData?: { ids: string[]; name: string }
+    ): any[] {
+      // 设置父级及以上id数组和父级name
+      return pointList.map(point => {
+        if (point.children) {
+          return Object.assign(point, {
+            children: filterPointList(point.children, {
+              ids: [...(parentData?.ids || []), point.id],
+              name: point.name
+            })
+          });
+        } else {
+          return Object.assign(point, {
+            materialList: point.materialList.map((item: any) => {
+              item.parentData = {
+                ids: [...(parentData?.ids || []), point.id],
+                name: point.name
+              };
+              return item;
+            })
+          });
+        }
+      });
+    }
+
+    /** 获取课件详情 */
+    const getDetail = async () => {
+      const res: any = await request.get(
+        `${state.platformApi}/lessonCourseware/getLessonCoursewareDetail/${route.query.id}`
+      );
+      if (res?.data) {
+        // data.detail.id = res.data.id;
+        // data.detail.cover = res.data.coverImg;
+        // data.detail.name = res.data.name;
+        // data.detail.des = res.data.lessonTargetDesc;
+        data.detail.useStatus = res.data.useStatus;
+      }
+    };
+    const getList = async (search?: string) => {
+      data.loading = true;
+      try {
+        const res: any = await request.get(
+          state.platformApi +
+            '/lessonCourseware/getLessonCoursewareCourseList/' +
+            route.query.id,
+          {
+            requestType: 'form',
+            params: {
+              search
+            }
+          }
+        );
+        if (Array.isArray(res?.data)) {
+          data.search = search;
+          res.data.forEach((item: any) => {
+            item.children = item.knowledgePointList || [];
+            item.id = item.coursewareDetailId;
+            item.name = item.coursewareDetailName;
+            formatDataList(item.children);
+          });
+          const firstItem = formatSelectFirstChild(res.data);
+          let list = filterPointList(res.data);
+          list = selectFirstChild(list, firstItem?.parentData?.ids || []);
+          data.list = list;
+
+          list.forEach((item: any) => {
+            if (item.id === firstItem.parentData?.ids[0]) {
+              data.parentCollapse = item.id;
+            }
+          });
+        }
+      } catch (error) {
+        //
+        console.log(error, 'error');
+      }
+      data.loading = false;
+    };
+    getDetail();
+    getList();
+
+    /** 页面显示和隐藏 */
+    watch(
+      () => pageVisibility.value,
+      value => {
+        if (value === 'visible') {
+          getDetail();
+        }
+      }
+    );
+
+    // useEventListener('scroll', () => {
+    //   const height =
+    //     window.scrollY ||
+    //     window.pageYOffset ||
+    //     document.documentElement.scrollTop;
+    //   data.titleOpacity = height > 30 ? 1 : 0;
+    // });
+    return () => (
+      <div class={styles.courseListSearch}>
+        <OSticky position="top">
+          <OHeader
+            background={`rgba(255,255,255, ${data.titleOpacity})`}
+            border={false}>
+            {{
+              title: () => (
+                <div class={styles.title}>
+                  <OSearch
+                    background="transparent"
+                    placeholder="请输入素材关键词"
+                    onSearch={(val: string) => {
+                      getList(val);
+                    }}
+                  />
+                </div>
+              )
+            }}
+          </OHeader>
+        </OSticky>
+
+        <div style="height: calc(100vh - var(--header-height)); overflow-x: hidden; overflow-y: auto;">
+          <TransitionGroup name="van-fade">
+            {!data.loading && (
+              <Collapse
+                key="courseListSearch"
+                modelValue={data.parentCollapse}
+                onUpdate:modelValue={(val: any) => {
+                  data.parentCollapse = val;
+                  // data.childrenCollapse = ''; // 重置子项选择
+                }}
+                class={styles.collapseParent}
+                border={false}
+                accordion>
+                {data.list.map((item: any) => (
+                  <CollapseItem
+                    center
+                    border={true}
+                    name={item.coursewareDetailId}
+                    clickable={false}
+                    class={['parentCollapse__item']}>
+                    {{
+                      title: () => (
+                        <div class={[styles.itemTitle]}>
+                          <img src={iconMenu} class={styles.iconMenu} />
+                          {item.coursewareDetailName}
+                        </div>
+                      ),
+                      default: () => (
+                        <>
+                          {Array.isArray(item?.materialList) &&
+                            item.materialList.map((n: any) => (
+                              <Cell center isLink clickable={false}>
+                                {{
+                                  title: () => (
+                                    <div class={styles.materialSection}>
+                                      <img
+                                        src={getImage(n)}
+                                        class={styles.iconMaterial}
+                                      />
+                                      {n.name}
+                                    </div>
+                                  )
+                                }}
+                              </Cell>
+                            ))}
+                          {Array.isArray(item?.children) && (
+                            <ChildNode
+                              id={route.query.id as any}
+                              search={data.search}
+                              isLock={isLock.value}
+                              list={item.children}
+                              collapse={item.collapse}
+                              onUpdate:collapse={(val: any) => {
+                                item.collapse = val;
+                              }}
+                            />
+                          )}
+                        </>
+                      ),
+                      'right-icon': () => (
+                        <i
+                          class={[
+                            styles.arrow,
+                            data.parentCollapse === item.coursewareDetailId
+                              ? styles.arrowActive
+                              : ''
+                          ]}></i>
+                      )
+                    }}
+                  </CollapseItem>
+                ))}
+              </Collapse>
+            )}
+          </TransitionGroup>
+          {data.loading && <OLoading />}
+          {!data.loading && !data.list.length && <OEmpty tips="暂无搜索结果" />}
+        </div>
+      </div>
+    );
+  }
+});