소스 검색

Merge branch '2023-3-21_CoursewareList-UI' into online

liushengqiang 2 년 전
부모
커밋
7927e5b538

BIN
src/views/courseList/image/icon-course-lock.png


BIN
src/views/courseList/image/icon-course.png


BIN
src/views/courseList/image/icon-list.png


+ 157 - 58
src/views/courseList/index.module.less

@@ -1,70 +1,169 @@
-.grid {
+.courseList {
+  min-height: 100vh;
+  background-color: #fff;
+  background-image: linear-gradient(180deg, #FFE8CE 0%, rgba(251, 233, 213, 0) 198px);
+  padding:10px 0;
+  box-sizing: border-box;
+}
+
+.periodContent {
+  display: flex;
+  padding: 20px;
+
+  .cover {
+    width: 107px;
+    margin-right: 30px;
+    border-radius: 4px 8px 8px 4px;
+    box-shadow: 0px 2px 6px 0px rgba(221, 168, 133, 0.67);
+    overflow: hidden;
+    :global{
+      .van-image__loading{
+        position: relative;
+        min-height: 130px;
+        animation: van-skeleton-blink var(--van-skeleton-duration) ease-in-out infinite;
+      }
+    }
+    &::before {
+      content: '';
+      position: absolute;
+      left: 5px;
+      width: 5px;
+      height: 100%;
+      background: linear-gradient(270deg, rgba(0, 0, 0, 0.25) 0%, rgba(0, 0, 0, 0.03) 100%);
+      box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.2);
+  }
+  }
+
+  .contentTitle {
+    font-size: 16px;
+    font-weight: 500;
+    color: #333;
+    line-height: 22px;
+    padding-bottom: 8px;
+  }
+
+  .contentLabel {
+    font-size: 12px;
+    font-weight: 400;
+    color: rgb(96, 96, 96);
+    line-height: 20px;
+  }
+}
+
+
+.periodTitle {
+  display: flex;
+  align-items: center;
+  padding: 20px 20px 0;
+
+  .pIcon {
+    width: 20px;
+    height: 20px;
+    margin-right: 6px;
+  }
+
+  .pTitle {
+    font-size: 16px;
+    font-weight: 600;
+    color: rgba(124, 61, 18, 1);
+    margin-right: 8px;
+  }
+
+  .pNum {
+    font-size: 12px;
+    font-weight: 400;
+    color: rgba(131,131,131,1);
+  }
+}
+
+.periodList {
   :global {
-    .van-grid-item {
-      .van-grid-item__content {
-        padding: 0;
-        background: transparent;
-        &::after {
-          display: none;
+    .van-cell-group--inset {
+      margin: 0;
+    }
+
+    .van-cell-group,
+    .van-cell {
+      background: transparent;
+    }
+
+    .van-cell {
+      padding: 18px 20px;
+      &::after{
+        left: 20px;
+        right: 20px;
+        border-color: rgba(242, 242, 242, 1);
+        transform: none;
+      }
+      .van-cell__title {
+        padding-right: 8px;
+
+        span {
+          font-size: 15px;
+          font-weight: 600;
+          color: #333333;
+          line-height: 21px;
+          word-break: break-all;
+        }
+
+        .van-cell__label {
+          font-size: 12px;
+          font-weight: 400;
+          color: #AAAAAA;
+          line-height: 17px;
+          margin: 0;
         }
       }
+
+      .van-cell__value {
+        flex: inherit;
+        flex-shrink: 0;
+      }
     }
   }
-  .gridItem {
-    position: relative;
-    width: 100%;
-    height: 107px;
-    border-radius: 10px;
-    overflow: hidden;
-    .cover {
-      position: absolute;
-      left: 0;
-      right: 0;
-      top: 0;
-      bottom: 0;
-      z-index: -1;
-      display: block;
-      width: 100%;
-      height: 100%;
-      object-fit: cover;
+
+  .baseBtn {
+    width: 73px;
+    height: 26px;
+    line-height: 1;
+    color: #fff;
+    font-size: 13px;
+    font-weight: 500;
+    border: 0;
+    border-radius: 13px;
+    flex-shrink: 0;
+    :global{
+      .van-button__text{
+        white-space: nowrap;
+      }
     }
-    .title {
-      text-align: center;
-      padding: 10px;
-      color: #742c00;
-      font-size: 15px;
+    &.look {
+      background: linear-gradient(180deg, #FFAB71 0%, #FF6E45 100%);
     }
-    .subtitle{
-      font-size: 11px;
-      margin-top: 2px;
+
+    &.down {
+      background: linear-gradient(180deg, #80C6FF 0%, #4296FF 100%);
     }
-    .num {
-      position: absolute;
-      left: 50%;
-      bottom: 12px;
-      transform: translateX(-50%);
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      font-size: 12px;
-      width: 70%;
-      height: 20px;
-      border-radius: 20px;
-      background: linear-gradient(180deg, #ff9c7c 0%, #ff5757 100%);
-      color: #fff;
+
+    &.disable {
+      opacity: 1;
+      background: linear-gradient(180deg, #D3D3D3 0%, #8F8F8F 100%);
     }
-    .look {
-      position: absolute;
-      left: 0;
-      right: 0;
-      top: 0;
-      bottom: 0;
-      background-color: rgba(0, 0, 0, 0.6);
-      z-index: 10;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      color: #fff;
+    &.downing{
+      width: 100px;
     }
   }
 }
+
+.periodItem {
+  width: 36px;
+  height: 40px;
+  margin-right: 8px;
+  flex-shrink: 0;
+
+  img {
+    width: 100%;
+    height: 100%;
+    display: block;
+  }
+}

+ 150 - 55
src/views/courseList/index.tsx

@@ -1,6 +1,21 @@
 import request from '@/helpers/request'
 import { state } from '@/state'
-import { Button, Empty, Grid, GridItem, Icon, showConfirmDialog, showToast } from 'vant'
+import {
+  Button,
+  Cell,
+  CellGroup,
+  Empty,
+  Grid,
+  GridItem,
+  Icon,
+  Image,
+  Loading,
+  showConfirmDialog,
+  showToast,
+  Skeleton,
+  SkeletonImage,
+  Space
+} from 'vant'
 import { defineComponent, onMounted, reactive, onUnmounted } from 'vue'
 import styles from './index.module.less'
 import { useRoute, useRouter } from 'vue-router'
@@ -12,11 +27,15 @@ import {
 } from '@/helpers/native-message'
 import iconLook from './image/look.svg'
 import iconCourse from './image/icon-course.png'
+import iconCourseLock from './image/icon-course-lock.png'
 import { browser } from '@/helpers/utils'
 import OEmpty from '@/components/o-empty'
 import { handleCheckVip } from '../hook/useFee'
+import iconList from './image/icon-list.png'
+import OSticky from '@/components/o-sticky'
+import OHeader from '@/components/o-header'
 export default defineComponent({
-  name: 'lessonCourseware',
+  name: 'courseList',
   setup() {
     const route = useRoute()
     const router = useRouter()
@@ -24,8 +43,25 @@ export default defineComponent({
     // const catchList = store
     const data = reactive({
       loading: true,
+      detail: {
+        cover: '',
+        name: '',
+        des: ''
+      },
       list: [] as any
     })
+
+    /** 获取课件详情 */
+    const getDetail = async () => {
+      const res: any = await request.get(
+        `${state.platformApi}/lessonCourseware/detail/${route.query.id}`
+      )
+      if (res?.data) {
+        data.detail.cover = res.data.coverImg
+        data.detail.name = res.data.name
+        data.detail.des = res.data.lessonTargetDesc
+      }
+    }
     const getList = async () => {
       data.loading = true
       if (route.query.courseScheduleId) {
@@ -49,13 +85,24 @@ export default defineComponent({
             state.platformApi + '/courseSchedule/myCoursewareDetail/' + route.query.id
           )
           if (Array.isArray(res?.data)) {
-            data.list = browserInfo.isApp ? await checkCoursewareCache(res.data) : res.data
+            const _list =  await checkCoursewareCache(res.data)
+            data.list = browserInfo.isApp ? res.data.map((item: any) => {
+              const _item = _list.find((n: any) => n.lessonCoursewareDetailId == item.lessonCoursewareDetailId)
+              const n = {
+                ...item
+              }
+              if (_item){
+                n.hasCache = _item.hasCache
+              }
+              return n
+            }) : res.data
           }
         } catch (error) {}
       }
       data.loading = false
     }
     onMounted(() => {
+      getDetail()
       getList()
       listenerMessage('downloadCoursewareToCache', getProgress)
     })
@@ -116,7 +163,7 @@ export default defineComponent({
       })
     }
     // 检查数据的缓存状态
-    const checkCoursewareCache = (list: []) => {
+    const checkCoursewareCache = (list: []): Promise<any[]> => {
       return new Promise((resolve) => {
         postMessage(
           {
@@ -185,59 +232,107 @@ export default defineComponent({
       } catch (error) {}
     }
     return () => (
-      <div style={{ paddingTop: '14px' }}>
-        <Grid gutter={14} columnNum={3} class={styles.grid}>
-          {data.list.map((item: any) => {
-            return (
-              <GridItem>
-                <div class={styles.gridItem} onClick={() => handleClick(item)}>
-                  <img src={iconCourse} class={styles.cover} />
-                  <div class={styles.title}>
-                    <div class="van-multi-ellipsis--l2">{item.coursewareDetailName}</div>
-                    {!browserInfo.isStudent && (
-                      <div class={styles.subtitle}>已使用 {item.useNum} 次</div>
-                    )}
-                  </div>
-                  {route.query.code !== 'select' ? (
-                    <>
-                      {!!item.knowledgePointList && (
-                        <>
-                          {item.hasCache ? (
-                            <div class={styles.num}>
-                              查看
-                              <Icon name="play-circle-o" />
-                            </div>
-                          ) : (
-                            <>
-                              {item.downloadStatus === 1 ? (
-                                <div class={styles.num}>下载中 {item.progress || 0}%</div>
-                              ) : item.downloadStatus === 2 ? (
-                                <div class={styles.num}>下载成功</div>
-                              ) : item.downloadStatus === 3 ? (
-                                <div class={styles.num}>重新下载</div>
-                              ) : (
-                                <div class={styles.num}>下载</div>
-                              )}
-                            </>
-                          )}
-                        </>
-                      )}
-                    </>
-                  ) : (
-                    <div class={styles.num}>选择</div>
-                  )}
+      <div class={styles.courseList}>
+        <OSticky
+          onGetHeight={(height: number) => {
+            document.documentElement.style.setProperty('--header-height', height + 'px')
+          }}
+        >
+          <OHeader
+            border={false}
+            background="transparent"
+            color="rgba(124, 61, 18, 1)"
+            title="教材详情"
+          />
+        </OSticky>
+
+        <div class={styles.periodContent}>
+          <Image class={styles.cover} src={data.detail.cover}>
+            {{
+              loading: () => <Loading />
+            }}
+          </Image>
+          {/* <img class={styles.cover} src={data.detail.cover} /> */}
+          <div>
+            <div class={styles.contentTitle}>{data.detail.name}</div>
+            <div class={styles.contentLabel}>教学目标:{data.detail.des}</div>
+          </div>
+        </div>
+
+        <div class={styles.periodTitle}>
+          <img class={styles.pIcon} src={iconList} />
+          <div class={styles.pTitle}>课程列表</div>
+          <div class={styles.pNum}>共{data.list.length}课</div>
+        </div>
 
-                  {(route.query.code == 'select' || state.platformType == 'STUDENT') &&
-                    !item.unlock && (
-                      <div class={styles.look} onClick={(e: Event) => e.stopPropagation()}>
-                        <Icon name={iconLook} /> 未解锁
+        <div 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={() => !isLock && handleClick(item)}
+                >
+                  {{
+                    icon: () => (
+                      <div class={styles.periodItem}>
+                        <div class={styles.periodItemModel}>
+                          <img src={isLock ? iconCourseLock : iconCourse} />
+                        </div>
                       </div>
-                    )}
-                </div>
-              </GridItem>
-            )
-          })}
-        </Grid>
+                    ),
+                    value: () => (
+                      <>
+                        {isSelect ? (
+                          <Button
+                            disabled={isLock}
+                            class={[styles.baseBtn, isLock ? styles.disable : styles.look]}
+                          >
+                            选择
+                          </Button>
+                        ) : item.knowledgePointList ? (
+                          <>
+                            {item.hasCache ? (
+                              <Button disabled={isLock} class={[styles.baseBtn, isLock ? styles.disable : styles.look]}>查看</Button>
+                            ) : (
+                              <Button
+                                disabled={isLock}
+                                class={[
+                                  styles.baseBtn,
+                                  isLock ? styles.disable : styles.down,
+                                  item.downloadStatus ? styles.downing : ''
+                                ]}
+                              >
+                                {item.downloadStatus === 1
+                                  ? `下载中 ${item.progress || 0}%`
+                                  : item.downloadStatus === 2
+                                  ? '下载成功'
+                                  : item.downloadStatus === 3
+                                  ? '重新下载'
+                                  : '下载'}
+                              </Button>
+                            )}
+                          </>
+                        ) : (
+                          ''
+                        )}
+                      </>
+                    )
+                  }}
+                </Cell>
+              )
+            })}
+          </CellGroup>
+        </div>
+
         {!data.loading && !data.list.length && <OEmpty tips="暂无内容" />}
       </div>
     )

+ 16 - 2
src/views/coursewarePlay/index.tsx

@@ -254,8 +254,22 @@ export default defineComponent({
             hideLoading: true
           }
         )
-        if (Array.isArray(res?.data)) {
-          data.detail = res.data
+        data.detail = res.data
+        if (res?.data?.lockFlag) {
+          postMessage({
+            api: 'courseLoading',
+            content: {
+              show: false,
+              type: 'fullscreen'
+            }
+          })
+          showDialog({
+            title: '温馨提示',
+            message: '课件已锁定',
+          }).then((value) => {
+            goback()
+          })
+          return
         }
         if (Array.isArray(res?.data?.knowledgePointList)) {
           let index = 0

BIN
src/views/lessonCourseware/component/CourseItem/image/icon-lock.png


BIN
src/views/lessonCourseware/component/CourseItem/image/icon-top.png


+ 129 - 0
src/views/lessonCourseware/component/CourseItem/index.module.less

@@ -0,0 +1,129 @@
+.content {
+    padding: 12px;
+}
+
+.wrap {
+    position: relative;
+    background: rgba(253, 243, 231, 1);
+    border-radius: 10px;
+    border: 2px solid #FFFFFF;
+    min-height: 220px;
+    display: flex;
+    flex-wrap: wrap;
+    padding-top: 22px;
+    padding-bottom: 20px;
+    box-sizing: border-box;
+
+    div {
+        box-sizing: border-box;
+    }
+
+    .icon {
+        position: absolute;
+        left: 50%;
+        top: -8px;
+        width: 126px;
+        padding: 5px 10px;
+        transform: translate(-50%, 0);
+        z-index: 10;
+        background-image: url('./image/icon-top.png');
+        background-repeat: no-repeat;
+        background-size: contain;
+        text-align: center;
+        color: #fff;
+        font-size: 16px;
+        font-weight: 500;
+        line-height: 22px;
+
+        img {
+            display: block;
+            width: 100%;
+            height: 100%;
+        }
+    }
+}
+
+.item {
+    margin: 20px 0 0 0;
+    padding: 0 30px;
+    width: 50%;
+
+    .cover {
+        position: relative;
+        border-radius: 4px 8px 8px 4px;
+        overflow: hidden;
+        margin-bottom: 8px;
+        box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.2);
+    }
+
+    .model {
+        position: absolute;
+        left: 0;
+        top: 0;
+        right: 0;
+        bottom: 0;
+        background-color: rgba(0, 0, 0, 0.4);
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        color: #fff;
+        font-size: 12px;
+        line-height: 18px;
+
+        img {
+            width: 12px;
+            height: 12px;
+            margin-right: 4px;
+        }
+    }
+
+    .coverNum {
+        position: absolute;
+        bottom: 12px;
+        left: 50%;
+        transform: translateX(-50%);
+        border-radius: 20px;
+        color: rgba(116, 44, 0, 1);
+        background-color: #fff;
+        padding: 4px 6px;
+        line-height: 1;
+        font-size: 12px;
+        z-index: 1;
+        white-space: nowrap;
+        word-break: break-all;
+        min-width: 50px;
+        text-align: center;
+    }
+
+    .coverImg {
+        display: block;
+        width: 100%;
+        max-height: 140px;
+
+        :global {
+            .van-image__loading {
+                position: relative;
+                min-height: 130px;
+                animation: van-skeleton-blink var(--van-skeleton-duration) ease-in-out infinite;
+            }
+        }
+
+        &::before {
+            content: '';
+            position: absolute;
+            left: 5px;
+            width: 5px;
+            height: 100%;
+            background: linear-gradient(270deg, rgba(0, 0, 0, 0.25) 0%, rgba(0, 0, 0, 0.03) 100%);
+            box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.2);
+        }
+    }
+
+    .name {
+        width: 109%;
+        font-size: 14px;
+        font-weight: 500;
+        color: #333;
+        line-height: 20px;
+    }
+}

+ 59 - 0
src/views/lessonCourseware/component/CourseItem/index.tsx

@@ -0,0 +1,59 @@
+import { defineComponent, PropType } from 'vue'
+import styles from './index.module.less'
+import iconTop from './image/icon-top.png'
+import iconLock from './image/icon-lock.png'
+import { Image, Loading } from 'vant'
+
+export default defineComponent({
+  name: 'CourseItem',
+  props: {
+    list: {
+      type: Array as PropType<any[]>,
+      default: () => []
+    },
+    term: {
+      type: String as PropType<any>,
+      defaut: 0
+    }
+  },
+  emits: ['itemClick'],
+  setup(prop, { emit }) {
+    const schoolTerm = {
+      1: '一',
+      2: '二',
+      3: '三',
+      4: '四',
+      5: '五',
+      6: '六'
+    }
+    return () => (
+      <div class={styles.content}>
+        <div class={styles.wrap}>
+          <div class={styles.icon}>
+            <div>第{schoolTerm[prop.term as any]}学期</div>
+          </div>
+          {prop.list.map((item: any, index: number) => {
+            return (
+              <div class={styles.item} onClick={() => emit('itemClick', item)}>
+                <div class={styles.cover}>
+                  <Image class={styles.coverImg} src={item.coverImg}>
+                    {{
+                      loading: () => <Loading />, 
+                    }}
+                  </Image>
+                  {/* <img class={styles.coverImg} src={item.coverImg} /> */}
+                  {/* {item.delFlag && <div class={styles.model}>
+                    <img src={iconLock} />
+                    <div>未解锁</div>
+                </div>} */}
+                  <div class={styles.coverNum}>{item.courseNumName ? item.courseNumName : `共${item.courseNum}课`}</div>
+                </div>
+                <div class={[styles.name, 'van-ellipsis']}>{item.name}</div>
+              </div>
+            )
+          })}
+        </div>
+      </div>
+    )
+  }
+})

+ 13 - 65
src/views/lessonCourseware/index.module.less

@@ -1,67 +1,15 @@
-.grid {
-  :global {
-    .van-grid-item {
-      .van-grid-item__content {
-        padding: 0;
-        background: transparent;
-        &::after {
-          display: none;
-        }
-      }
-    }
-  }
-  .gridItem {
-    position: relative;
-    width: 100%;
-    height: 130px;
-    border-radius: 8px;
-    overflow: hidden;
-    background: rgba(247,203,143,1);
-    .cover {
-      position: absolute;
-      left: 0;
-      right: 0;
-      top: 0;
-      bottom: 0;
-      display: block;
-      width: 100%;
-      height: 100%;
-      object-fit: cover;
-    }
-    .title {
-      position: relative;
-      text-align: center;
-      padding: 14px;
-      color:#742C00;
-      font-size: 14px;
-    }
-    .num {
-      position: absolute;
-      left: 50%;
-      bottom: 12px;
-      transform: translateX(-50%);
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      font-size: 12px;
-      width: 50%;
-      height: 20px;
-      border-radius: 20px;
-      background-color: #fff;
-      color: #742c00;
-    }
-    .look {
-      position: absolute;
-      left: 0;
-      right: 0;
-      top: 0;
-      bottom: 0;
-      background-color: rgba(0, 0, 0, 0.6);
-      z-index: 10;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      color: #fff;
-    }
+.lessonCourseware{
+  min-height: 100vh;
+  background-color: rgba(255, 232, 206, 1);
+  padding: 10px 0;
+  box-sizing: border-box;
+}
+.filter{
+  font-size: 14px;
+  font-weight: 500;
+}
+:global{
+  .van-action-sheet{
+    max-height: 40%;
   }
 }

+ 110 - 33
src/views/lessonCourseware/index.tsx

@@ -1,11 +1,29 @@
 import request from '@/helpers/request'
 import { state } from '@/state'
-import { Button, Empty, Grid, GridItem, Icon, showToast, Toast } from 'vant'
-import { defineComponent, onMounted, reactive } from 'vue'
+import {
+  ActionSheet,
+  ActionSheetAction,
+  Button,
+  Empty,
+  Grid,
+  GridItem,
+  Icon,
+  Picker,
+  Popover,
+  Popup,
+  showToast,
+  Toast
+} from 'vant'
+import { computed, defineComponent, onMounted, reactive } from 'vue'
 import styles from './index.module.less'
 import iconLook from './image/look.svg'
 import { useRoute, useRouter } from 'vue-router'
 import OEmpty from '@/components/o-empty'
+import OSticky from '@/components/o-sticky'
+import OHeader from '@/components/o-header'
+import CourseItem from './component/CourseItem'
+import { courseEmnu } from '@/constant'
+import { browser } from '@/helpers/utils'
 export default defineComponent({
   name: 'lessonCourseware',
   setup() {
@@ -13,8 +31,23 @@ export default defineComponent({
     const router = useRouter()
     const data = reactive({
       loading: true,
-      list: [] as any
+      list: [] as any,
+      actionShow: false,
+      actionName: '课程类型' as string | undefined,
+      actionKey: '',
+      showRight: route.query.code != 'select' && browser().isTeacher
     })
+    const filterData = (list: any[]) => {
+      const schoolTerm = {}
+      for (let i = 0; i < list.length; i++) {
+        if (schoolTerm[list[i].sortNo]) {
+          schoolTerm[list[i].sortNo].push(list[i])
+        } else {
+          schoolTerm[list[i].sortNo] = [list[i]]
+        }
+      }
+      return schoolTerm
+    }
     const getList = async () => {
       data.loading = true
       if (route.query.code === 'select') {
@@ -23,21 +56,27 @@ export default defineComponent({
             state.platformApi + `/courseSchedule/getCourseware/${route.query.courseScheduleId}`
           )
           if (Array.isArray(res?.data)) {
-            data.list = res.data.map((n: any) => {
+            const _data = res.data.map((n: any) => {
               return {
+                ...n,
                 coverImg: n.coverImg,
                 name: n.coursewareName,
                 id: n.lessonCoursewareId,
                 courseNum: n.coursewareNum
               }
             })
+            data.list = filterData(_data)
+            console.log("🚀 ~ data.list:", data.list)
           }
         } catch (error) {}
       } else {
         try {
           const res: any = await request.post(state.platformApi + '/courseSchedule/myCourseware')
           if (Array.isArray(res?.data)) {
-            data.list = res.data
+            const _data = data.actionKey
+              ? res.data.filter((n: any) => n.courseTypeCode === data.actionKey)
+              : res.data
+            data.list = filterData(_data)
           }
         } catch (error) {}
       }
@@ -65,38 +104,76 @@ export default defineComponent({
       })
     }
 
+    const actions = computed(() => {
+      const _list = Object.entries(courseEmnu).map(([key, value]) => {
+        return {
+          id: key,
+          name: value,
+          text: value,
+          value: key,
+          color: key === data.actionKey ? 'var(--van-primary)' : ''
+        }
+      })
+      _list.unshift({
+        id: '',
+        name: '课程类型',
+        text: '全部',
+        value: '',
+        color: '' === data.actionKey ? 'var(--van-primary)' : ''
+      })
+      return _list
+    })
+
+    const handleSelect = (action: any) => {
+      data.actionKey = action.id
+      data.actionName = action.name
+      data.actionShow = false
+      getList()
+    }
     return () => (
       <div
-        style={{ paddingTop: '14px', boxSizing: 'border-box' }}
-        class={!data.list.length && 'emptyRootContainer'}
+        class={[styles.lessonCourseware, !Object.values(data.list).length && 'emptyRootContainer']}
       >
-        <Grid gutter={14} columnNum={3} class={styles.grid}>
-          {data.list.map((item: any) => {
-            return (
-              <GridItem>
-                <div
-                  class={styles.gridItem}
-                  style={{
-                    background: item.coverImg
-                      ? ''
-                      : `hsla(${Math.floor(Math.random() * 360)},50%,50%,.8)`
-                  }}
-                  onClick={() => handleClick(item)}
-                >
-                  <img src={item.coverImg} class={styles.cover} />
-                  {/* <div class={['van-multi-ellipsis--l3', styles.title]}>{item.name}</div> */}
-                  <div class={styles.num}>共{item.courseNum}课</div>
-                  {/* {item.delFlag && (
-                    <div class={styles.look}>
-                      <Icon name={iconLook} /> 未解锁
+        <OSticky
+          onGetHeight={(height: number) => {
+            document.documentElement.style.setProperty('--header-height', height + 'px')
+          }}
+        >
+          <OHeader
+            border={false}
+            background="rgba(255, 232, 206, 1)"
+            color="rgba(124, 61, 18, 1)"
+            title="云教材"
+          >
+            {{
+              right: () => (
+                <>
+                  {data.showRight && (
+                    <div class={styles.filter} onClick={() => (data.actionShow = true)}>
+                      {data.actionName} <Icon style={{ transform: 'rotate(90deg)' }} name="play" />{' '}
                     </div>
-                  )} */}
-                </div>
-              </GridItem>
-            )
-          })}
-        </Grid>
-        {!data.loading && !data.list.length && <OEmpty tips="没有课件" />}
+                  )}
+                </>
+              )
+            }}
+          </OHeader>
+        </OSticky>
+        {Object.keys(data.list).map((key: any) => {
+          return (
+            <CourseItem term={key} list={data.list[key]} onItemClick={(row) => handleClick(row)} />
+          )
+        })}
+        {!data.loading && !Object.values(data.list).length && <OEmpty tips="没有课件" />}
+        <Popup position="bottom" round v-model:show={data.actionShow}>
+          <Picker
+            class="popupBottomSearch"
+            columns={actions.value}
+            onCancel={() => (data.actionShow = false)}
+            onConfirm={({ selectedOptions }) => {
+              handleSelect(selectedOptions[0])
+            }}
+          />
+        </Popup>
       </div>
     )
   }

BIN
src/views/unit-test/unit-create/image/icon-course-lock.png


BIN
src/views/unit-test/unit-create/image/icon-course.png


+ 150 - 68
src/views/unit-test/unit-create/uni-test.module.less

@@ -1,79 +1,161 @@
-.grid {
-  :global {
-    .van-grid-item {
-      .van-grid-item__content {
-        padding: 0;
-        background: transparent;
-        &::after {
-          display: none;
-        }
+.uniTest {
+  min-height: 100vh;
+  background-color: #fff;
+  background-image: linear-gradient(180deg, #FFE8CE 0%, rgba(251, 233, 213, 0) 198px);
+  padding:10px 0;
+  box-sizing: border-box;
+}
+
+.periodContent {
+  display: flex;
+  padding: 20px;
+
+  .cover {
+    width: 107px;
+    margin-right: 30px;
+    border-radius: 4px 8px 8px 4px;
+    box-shadow: 0px 2px 6px 0px rgba(221, 168, 133, 0.67);
+    overflow: hidden;
+
+    :global {
+      .van-image__loading {
+        position: relative;
+        min-height: 130px;
+        animation: van-skeleton-blink var(--van-skeleton-duration) ease-in-out infinite;
       }
     }
-  }
-  .gridItem {
-    position: relative;
-    width: 100%;
-    height: 107px;
-    border-radius: 8px;
-    overflow: hidden;
-    .cover {
+    &::before {
+      content: '';
       position: absolute;
-      left: 0;
-      right: 0;
-      top: 0;
-      bottom: 0;
-      z-index: -1;
-      display: block;
-      width: 100%;
+      left: 5px;
+      width: 5px;
       height: 100%;
-      object-fit: cover;
+      background: linear-gradient(270deg, rgba(0, 0, 0, 0.25) 0%, rgba(0, 0, 0, 0.03) 100%);
+      box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.2);
+  }
+  }
+
+  .contentTitle {
+    font-size: 16px;
+    font-weight: 500;
+    color: #333;
+    line-height: 22px;
+    padding-bottom: 8px;
+  }
+
+  .contentLabel {
+    font-size: 12px;
+    font-weight: 400;
+    color: rgb(96, 96, 96);
+    line-height: 20px;
+  }
+}
+
+
+.periodTitle {
+  display: flex;
+  align-items: center;
+  padding: 20px 20px 0;
+
+  .pIcon {
+    width: 20px;
+    height: 20px;
+    margin-right: 6px;
+  }
+
+  .pTitle {
+    font-size: 16px;
+    font-weight: 600;
+    color: rgba(124, 61, 18, 1);
+    margin-right: 8px;
+  }
+
+  .pNum {
+    font-size: 12px;
+    font-weight: 400;
+    color: rgba(131,131,131,1);
+  }
+}
+
+.periodList {
+  :global {
+    .van-cell-group--inset {
+      margin: 0;
     }
-    .title {
-      text-align: center;
-      padding: 10px;
-      color: #742c00;
-
-      .coreTitle {
-        font-size: 16px;
-        font-weight: 600;
-        line-height: 22px;
-        margin-bottom: 4px;
-      }
+
+    .van-cell-group,
+    .van-cell {
+      background: transparent;
     }
-    .num {
-      position: absolute;
-      left: 50%;
-      bottom: 12px;
-      transform: translateX(-50%);
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      font-size: 12px;
-      width: 70%;
-      line-height: 20px;
-      border-radius: 20px;
-      background: linear-gradient(180deg, #ff9c7c 0%, #ff5757 100%);
-      display: flex;
-      flex-direction: row;
-      align-items: center;
-      color: #fff;
-      .playIcon {
-        font-size: 12px;
-        margin-left: 2px;
+
+    .van-cell {
+      padding: 18px 20px;
+
+      &::after {
+        left: 20px;
+        right: 20px;
+        border-color: rgba(242, 242, 242, 1);
+        transform: none;
+      }
+
+      .van-cell__title {
+        padding-right: 8px;
+
+        span {
+          font-size: 15px;
+          font-weight: 600;
+          color: #333333;
+          line-height: 21px;
+          word-break: break-all;
+        }
+
+        .van-cell__label {
+          font-size: 12px;
+          font-weight: 400;
+          color: #AAAAAA;
+          line-height: 17px;
+          margin: 0;
+        }
+      }
+
+      .van-cell__value {
+        flex: inherit;
+        flex-shrink: 0;
       }
     }
-    .look {
-      position: absolute;
-      left: 0;
-      right: 0;
-      top: 0;
-      bottom: 0;
-      background-color: rgba(0, 0, 0, 0.6);
-      z-index: 10;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      color: #fff;
+  }
+
+  .baseBtn {
+    width: 73px;
+    height: 26px;
+    line-height: 1;
+    color: #fff;
+    font-size: 13px;
+    font-weight: 500;
+    border: 0;
+    border-radius: 13px;
+    flex-shrink: 0;
+
+    &.look {
+      background: linear-gradient(180deg, #FF8A7C 0%, #FF5757 100%);
+    }
+
+    &.disable {
+      opacity: 1;
+      background: linear-gradient(180deg, #D3D3D3 0%, #8F8F8F 100%);
     }
   }
 }
+
+.periodItem {
+  width: 36px;
+  height: 40px;
+  margin-right: 8px;
+  flex-shrink: 0;
+
+  img {
+    width: 100%;
+    height: 100%;
+    display: block;
+  }
+}

+ 83 - 28
src/views/unit-test/unit-create/uni-test.tsx

@@ -1,6 +1,6 @@
 import request from '@/helpers/request'
 import { state } from '@/state'
-import { Button, Empty, Grid, GridItem, Icon, showConfirmDialog, showToast } from 'vant'
+import { Button, Cell, CellGroup, Empty, Grid, GridItem, Icon, Image, Loading, showConfirmDialog, showToast } from 'vant'
 import { defineComponent, onMounted, reactive, onUnmounted, ref } from 'vue'
 import styles from './uni-test.module.less'
 import { useRoute, useRouter } from 'vue-router'
@@ -12,8 +12,12 @@ import {
 } from '@/helpers/native-message'
 import OEmpty from '@/components/o-empty'
 import iconLook from './image/look.svg'
-import iconCourse from '@/views/courseList/image/icon-course.png'
+import iconCourse from './image/icon-course.png'
+import iconCourseLock from './image/icon-course-lock.png'
 import { browser } from '@/helpers/utils'
+import OSticky from '@/components/o-sticky'
+import OHeader from '@/components/o-header'
+import iconList from '@/views/courseList/image/icon-list.png'
 export default defineComponent({
   name: 'uni-test',
   setup() {
@@ -24,8 +28,23 @@ export default defineComponent({
     const forms = ref({} as any)
     const data = reactive({
       loading: true,
+      detail: {
+        cover: '',
+        name: '',
+        des: ''
+      },
       list: [] as any
     })
+
+    /** 获取课件详情 */
+    const getDetail = async () => {
+      const res: any = await request.get(`${state.platformApi}/lessonCourseware/detail/${route.query.lessonCoursewareId}`)
+      if (res?.data){
+        data.detail.cover = res.data.coverImg
+        data.detail.name = res.data.name
+        data.detail.des = res.data.lessonTargetDesc
+      }
+    }
     const getList = async () => {
       data.loading = true
       try {
@@ -47,6 +66,7 @@ export default defineComponent({
       data.loading = false
     }
     onMounted(() => {
+      getDetail()
       forms.value = { ...JSON.parse(sessionStorage.getItem('unit-create') || '{}') } as any
       getList()
     })
@@ -65,37 +85,72 @@ export default defineComponent({
     }
 
     return () => (
-      <div style={{ paddingTop: '14px' }}>
-        {data.list.length > 0 ? (
-          <Grid gutter={14} columnNum={3} class={styles.grid}>
-            {data.list.map((item: any) => {
-              return (
-                <GridItem>
-                  <div class={styles.gridItem} onClick={() => handleClick(item)}>
-                    <img src={iconCourse} class={styles.cover} />
-                    <div class={styles.title}>
-                      <div class={styles.coreTitle}>{item.name}</div>
-                      {<div>已使用 {item.useNum} 次</div>}
-                    </div>
+      <div class={styles.uniTest}>
+        <OSticky
+          onGetHeight={(height: number) => {
+            document.documentElement.style.setProperty('--header-height', height + 'px')
+          }}
+        >
+          <OHeader
+            border={false}
+            background="transparent"
+            color="rgba(124, 61, 18, 1)"
+            title="教材详情"
+          />
+        </OSticky>
+        <div class={styles.periodContent}>
+          <Image class={styles.cover} src={data.detail.cover}>
+            {{
+              loading: () => <Loading />, 
+            }}
+          </Image>
+          {/* <img class={styles.cover} src={data.detail.cover} /> */}
+          <div>
+            <div class={styles.contentTitle}>{data.detail.name}</div>
+            <div class={styles.contentLabel}>
+              教学目标:{data.detail.des}
+            </div>
+          </div>
+        </div>
 
-                    {/* <div class={styles.num}>
-                      查看
-                      <Icon name="play-circle-o" class={styles.playIcon} />
-                    </div> */}
+        <div class={styles.periodTitle}>
+          <img class={styles.pIcon} src={iconList} />
+          <div class={styles.pTitle}>课程列表</div>
+          <div class={styles.pNum}>共{data.list.length}课</div>
+        </div>
 
-                    {!item.unLockFlag && (
-                      <div class={styles.look} onClick={(e: Event) => e.stopPropagation()}>
-                        <Icon name={iconLook} /> 未解锁
+        <div class={styles.periodList}>
+          <CellGroup inset>
+            {data.list.map((item: any) => {
+              const isLock = !item.unLockFlag
+              return (
+                <Cell
+                  border
+                  center
+                  title={item.name}
+                  label={`已使用${item.useNum}次`}
+                  onClick={() => !isLock && handleClick(item)}
+                >
+                  {{
+                    icon: () => (
+                      <div class={styles.periodItem}>
+                        <div class={styles.periodItemModel}>
+                          <img src={isLock ? iconCourseLock : iconCourse} />
+                        </div>
                       </div>
-                    )}
-                  </div>
-                </GridItem>
+                    ),
+                    value: () => (
+                      <>
+                        <Button disabled={isLock} class={[styles.baseBtn, isLock ? styles.disable : styles.look]}>使用</Button>
+                      </>
+                    )
+                  }}
+                </Cell>
               )
             })}
-          </Grid>
-        ) : (
-          <OEmpty></OEmpty>
-        )}
+          </CellGroup>
+        </div>
+        {!data.loading && !data.list.length && <OEmpty tips="暂无内容" />}
       </div>
     )
   }

+ 41 - 37
src/views/unit-test/unit-create/unit-Lesson.tsx

@@ -6,6 +6,9 @@ import styles from './unit-lesson.module.less'
 import iconLook from './image/look.svg'
 import { useRoute, useRouter } from 'vue-router'
 import OEmpty from '@/components/o-empty'
+import CourseItem from '@/views/lessonCourseware/component/CourseItem'
+import OSticky from '@/components/o-sticky'
+import OHeader from '@/components/o-header'
 export default defineComponent({
   name: 'unit-Lesson',
   setup() {
@@ -16,6 +19,18 @@ export default defineComponent({
       loading: true,
       list: [] as any
     })
+    
+    const filterData = (list: any[]) => {
+      const schoolTerm = {}
+      for(let i = 0; i < list.length; i++){
+        if (schoolTerm[list[i].sortNo]){
+          schoolTerm[list[i].sortNo].push(list[i])
+        } else {
+          schoolTerm[list[i].sortNo] = [list[i]]
+        }
+      }
+      return schoolTerm
+    }
 
     const getList = async () => {
       data.loading = true
@@ -30,12 +45,17 @@ export default defineComponent({
           }
         )
         if (Array.isArray(res?.data)) {
-          data.list = []
-          res.data.map((item: any) => {
-            if (item.unitTestNum) {
-              data.list.push(item)
+          const list = res.data.filter((item: any) => item.unitTestNum).map((n: any) => {
+            return {
+              ...n,
+              coverImg: n.coverImg,
+              name: n.coursewareName,
+              id: n.lessonCoursewareId,
+              courseNum: n.coursewareNum,
+              courseNumName: `共${n.coursewareNum}次测验`
             }
           })
+          data.list = filterData(list)
         }
       } catch (error) {
         console.log(error)
@@ -61,40 +81,24 @@ export default defineComponent({
 
     return () => (
       <div
-        style={{ paddingTop: '14px' }}
-        class={[data.list.length > 0 ? '' : 'emptyRootContainer']}
+        class={[styles.unitLesson, Object.values(data.list).length ? '' : 'emptyRootContainer']}
       >
-        {data.list.length > 0 ? (
-          <Grid gutter={14} columnNum={3} class={styles.grid}>
-            {data.list.map((item: any) => {
-              return (
-                <GridItem>
-                  <div
-                    class={styles.gridItem}
-                    style={{
-                      background: item.coverImg
-                        ? ''
-                        : `hsla(${Math.floor(Math.random() * 360)},50%,50%,.8)`
-                    }}
-                    onClick={() => handleClick(item)}
-                  >
-                    <img src={item.coverImg} class={styles.cover} />
-                    <div class={styles.title}>{item.name}</div>
-                    <div class={styles.num}>共{item.unitTestNum || 0}次测验</div>
-
-                    {/* {!item.enableFlag && (
-                    <div class={styles.look}>
-                      <Icon name={iconLook} /> 未解锁
-                    </div>
-                  )} */}
-                  </div>
-                </GridItem>
-              )
-            })}
-          </Grid>
-        ) : (
-          <OEmpty btnStatus={false} tips="暂无教材"></OEmpty>
-        )}
+        <OSticky
+          onGetHeight={(height: number) => {
+            document.documentElement.style.setProperty('--header-height', height + 'px')
+          }}
+        >
+          <OHeader
+            border={false}
+            background="rgba(255, 232, 206, 1)"
+            color="rgba(124, 61, 18, 1)"
+            title="选择教材"
+          />
+        </OSticky>
+        {Object.keys(data.list).map((key: any) => {
+          return <CourseItem term={key} list={data.list[key]} onItemClick={(row) => handleClick(row)} />
+        })}
+        {!data.loading && !Object.values(data.list).length && <OEmpty btnStatus={false} tips="暂无教材"></OEmpty>}
 
         {/* <Button onClick={() => {
           location.href = 'http://192.168.3.114:1000/teacher.html#/courseList?id=1610595624868495362'

+ 5 - 65
src/views/unit-test/unit-create/unit-lesson.module.less

@@ -1,66 +1,6 @@
-.grid {
-  :global {
-    .van-grid-item {
-      .van-grid-item__content {
-        padding: 0;
-        background: transparent;
-        &::after {
-          display: none;
-        }
-      }
-    }
-  }
-  .gridItem {
-    position: relative;
-    width: 100%;
-    height: 130px;
-    border-radius: 8px;
-    overflow: hidden;
-    background: rgba(247, 203, 143, 1);
-    .cover {
-      position: absolute;
-      left: 0;
-      right: 0;
-      top: 0;
-      bottom: 0;
-      display: block;
-      width: 100%;
-      height: 100%;
-      object-fit: cover;
-    }
-    .title {
-      position: relative;
-      text-align: center;
-      padding: 10px;
-      color: #742c00;
-    }
-    .num {
-      position: absolute;
-      left: 50%;
-      bottom: 12px;
-      transform: translateX(-50%);
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      font-size: 12px;
-      width: 75%;
-      height: 20px;
-      border-radius: 20px;
-      background-color: #fff;
-      color: #742c00;
-    }
-    .look {
-      position: absolute;
-      left: 0;
-      right: 0;
-      top: 0;
-      bottom: 0;
-      background-color: rgba(0, 0, 0, 0.6);
-      z-index: 10;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      color: #fff;
-    }
-  }
+.unitLesson{
+  min-height: 100vh;
+  background-color: rgba(255, 232, 206, 1);
+  padding: 10px 0;
+  box-sizing: border-box;
 }

+ 2 - 2
vite.config.ts

@@ -11,8 +11,8 @@ function resolve(dir: string) {
 }
 // https://vitejs.dev/config/
 // https://github.com/vitejs/vite/issues/1930 .env
-// const proxyUrl = 'https://mstutest.dayaedu.com/';
-const proxyUrl = 'https://test.lexiaoya.cn'
+const proxyUrl = 'https://test.lexiaoya.cn/';
+// const proxyUrl = 'http://47.98.131.38:8989/'
 // const proxyUrl = 'http://192.168.3.20:8989/' // 邹旋
 // const proxyUrl = 'http://192.168.3.143:8989/' // 尚科
 // const proxyUrl = 'http://192.168.3.26:8989/' // 刘俊驰