Browse Source

Merge branch 'jenkins' of http://git.dayaedu.com/lex/orchestra-app into jenkins

lex 2 years ago
parent
commit
58cdf8e424
43 changed files with 1739 additions and 86 deletions
  1. 25 22
      package-lock.json
  2. 1 1
      package.json
  3. 24 1
      src/router/routes-common.ts
  4. 4 3
      src/router/routes-school.ts
  5. 51 36
      src/router/routes-student.ts
  6. 25 3
      src/router/routes-teacher.ts
  7. 2 2
      src/school/ranking-list/components/day-bang.tsx
  8. 2 2
      src/school/ranking-list/components/timer-bang.tsx
  9. 4 4
      src/school/ranking-list/index.tsx
  10. 136 0
      src/student/ranking-list/components/day-bang.tsx
  11. 10 0
      src/student/ranking-list/components/timer-bang.module.less
  12. 156 0
      src/student/ranking-list/components/timer-bang.tsx
  13. BIN
      src/student/ranking-list/images/first.png
  14. BIN
      src/student/ranking-list/images/ranking-bg.png
  15. BIN
      src/student/ranking-list/images/second.png
  16. BIN
      src/student/ranking-list/images/third.png
  17. 30 0
      src/student/ranking-list/index.module.less
  18. 83 0
      src/student/ranking-list/index.tsx
  19. 33 0
      src/student/ranking-list/modals/my-ranking-item.tsx
  20. 137 0
      src/student/ranking-list/modals/rank-item.module.less
  21. 46 0
      src/student/ranking-list/modals/rank-item.tsx
  22. 24 0
      src/teacher/attendance/index.module.less
  23. 253 0
      src/teacher/attendance/index.tsx
  24. 142 0
      src/teacher/attendance/modals/teacherAtt-item.module.less
  25. 87 0
      src/teacher/attendance/modals/teacherAtt-item.tsx
  26. 13 0
      src/views/courseList/image/look.svg
  27. 65 0
      src/views/courseList/index.module.less
  28. 75 0
      src/views/courseList/index.tsx
  29. 11 0
      src/views/coursewarePlay/image/back.svg
  30. 58 0
      src/views/coursewarePlay/index.module.less
  31. 87 0
      src/views/coursewarePlay/index.tsx
  32. 1 1
      src/views/exercise-record/exercis-detail.module.less
  33. 5 3
      src/views/exercise-record/exercis-detail.tsx
  34. 0 0
      src/views/exercise-record/index.module.less
  35. 4 3
      src/views/exercise-record/index.tsx
  36. 0 0
      src/views/exercise-record/modals/detail-item.module.less
  37. 5 5
      src/views/exercise-record/modals/detail-item.tsx
  38. 0 0
      src/views/exercise-record/modals/student-item.module.less
  39. 0 0
      src/views/exercise-record/modals/student-item.tsx
  40. 1 0
      src/views/layout/login.tsx
  41. 13 0
      src/views/lessonCourseware/image/look.svg
  42. 65 0
      src/views/lessonCourseware/index.module.less
  43. 61 0
      src/views/lessonCourseware/index.tsx

+ 25 - 22
package-lock.json

@@ -24,7 +24,7 @@
         "mitt": "^3.0.0",
         "normalize.css": "^8.0.1",
         "numeral": "^2.0.6",
-        "plyr": "^3.6.12",
+        "plyr": "^3.7.3",
         "qrcode": "^1.5.1",
         "qrcode.vue": "^3.3.3",
         "query-string": "^7.1.1",
@@ -3511,11 +3511,10 @@
       }
     },
     "node_modules/core-js": {
-      "version": "3.23.3",
-      "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.23.3.tgz",
-      "integrity": "sha512-oAKwkj9xcWNBAvGbT//WiCdOMpb9XQG92/Fe3ABFM/R16BsHgePG00mFOgKf7IsCtfj8tA1kHtf/VwErhriz5Q==",
+      "version": "3.27.0",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.0.tgz",
+      "integrity": "sha512-wY6cKosevs430KRkHUIsvepDXHGjlXOZO3hYXNyqpD6JvB0X28aXyv0t1Y1vZMwE7SoKmtfa6IASHCPN52FwBQ==",
       "hasInstallScript": true,
-      "license": "MIT",
       "funding": {
         "type": "opencollective",
         "url": "https://opencollective.com/core-js"
@@ -7686,12 +7685,11 @@
       }
     },
     "node_modules/plyr": {
-      "version": "3.7.2",
-      "resolved": "https://registry.npmmirror.com/plyr/-/plyr-3.7.2.tgz",
-      "integrity": "sha512-I0ZC/OI4oJ0iWG9s2rrnO0YFO6aLyrPiQBq9kum0FqITYljwTPBbYL3TZZu8UJQJUq7tUWN18Q7ACwNCkGKABQ==",
-      "license": "MIT",
+      "version": "3.7.3",
+      "resolved": "https://registry.npmjs.org/plyr/-/plyr-3.7.3.tgz",
+      "integrity": "sha512-ORULENBvEvvzMYXRQBALDmEi8P+wZt1Hr/NvHqchu/t7E2xJKNkRYWx0qCA1HETIGZ6zobrOVgqeAUqWimS7fQ==",
       "dependencies": {
-        "core-js": "^3.22.0",
+        "core-js": "^3.26.1",
         "custom-event-polyfill": "^1.0.7",
         "loadjs": "^4.2.0",
         "rangetouch": "^2.0.1",
@@ -11495,7 +11493,8 @@
       "version": "2.0.1",
       "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-2.0.1.tgz",
       "integrity": "sha512-wtdMnGVvys9K8tg+DxowU1ytTrdVveXr3LzdhaKakysgGXyrsfaeds2cDywtvujEASjWOwWL/OgWM+qoeM8Plg==",
-      "dev": true
+      "dev": true,
+      "requires": {}
     },
     "@vitejs/plugin-vue-jsx": {
       "version": "1.3.8",
@@ -11779,7 +11778,8 @@
       "version": "5.3.1",
       "resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz",
       "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==",
-      "dev": true
+      "dev": true,
+      "requires": {}
     },
     "aggregate-error": {
       "version": "3.1.0",
@@ -12301,9 +12301,9 @@
       "integrity": "sha512-rvVsHrpFcL4F2P8ihsoLdFHmd404+CMg71S756oRSeQgqk51U3kicGdnvfkrxva0xXH92SjGS62B0XIJsbh+9Q=="
     },
     "core-js": {
-      "version": "3.23.3",
-      "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.23.3.tgz",
-      "integrity": "sha512-oAKwkj9xcWNBAvGbT//WiCdOMpb9XQG92/Fe3ABFM/R16BsHgePG00mFOgKf7IsCtfj8tA1kHtf/VwErhriz5Q=="
+      "version": "3.27.0",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.0.tgz",
+      "integrity": "sha512-wY6cKosevs430KRkHUIsvepDXHGjlXOZO3hYXNyqpD6JvB0X28aXyv0t1Y1vZMwE7SoKmtfa6IASHCPN52FwBQ=="
     },
     "core-js-compat": {
       "version": "3.19.1",
@@ -15063,11 +15063,11 @@
       }
     },
     "plyr": {
-      "version": "3.7.2",
-      "resolved": "https://registry.npmmirror.com/plyr/-/plyr-3.7.2.tgz",
-      "integrity": "sha512-I0ZC/OI4oJ0iWG9s2rrnO0YFO6aLyrPiQBq9kum0FqITYljwTPBbYL3TZZu8UJQJUq7tUWN18Q7ACwNCkGKABQ==",
+      "version": "3.7.3",
+      "resolved": "https://registry.npmjs.org/plyr/-/plyr-3.7.3.tgz",
+      "integrity": "sha512-ORULENBvEvvzMYXRQBALDmEi8P+wZt1Hr/NvHqchu/t7E2xJKNkRYWx0qCA1HETIGZ6zobrOVgqeAUqWimS7fQ==",
       "requires": {
-        "core-js": "^3.22.0",
+        "core-js": "^3.26.1",
         "custom-event-polyfill": "^1.0.7",
         "loadjs": "^4.2.0",
         "rangetouch": "^2.0.1",
@@ -15093,7 +15093,8 @@
       "version": "6.0.0",
       "resolved": "https://registry.npmmirror.com/postcss-pxtorem/-/postcss-pxtorem-6.0.0.tgz",
       "integrity": "sha512-ZRXrD7MLLjLk2RNGV6UA4f5Y7gy+a/j1EqjAfp9NdcNYVjUMvg5HTYduTjSkKBkRkfqbg/iKrjMO70V4g1LZeg==",
-      "dev": true
+      "dev": true,
+      "requires": {}
     },
     "postcss-value-parser": {
       "version": "4.2.0",
@@ -15294,7 +15295,8 @@
     "qrcode.vue": {
       "version": "3.3.3",
       "resolved": "https://registry.npmmirror.com/qrcode.vue/-/qrcode.vue-3.3.3.tgz",
-      "integrity": "sha512-OsD4tQjIbxg/K6D5ZkWjBdYI9eg9K2i8qeYILdEAX5mdAydSAxV7xKmmZSP/hA12olLqEMZ9ryqDQrwa9jEMgw=="
+      "integrity": "sha512-OsD4tQjIbxg/K6D5ZkWjBdYI9eg9K2i8qeYILdEAX5mdAydSAxV7xKmmZSP/hA12olLqEMZ9ryqDQrwa9jEMgw==",
+      "requires": {}
     },
     "qs": {
       "version": "6.10.3",
@@ -16264,7 +16266,8 @@
     "vue-demi": {
       "version": "0.13.2",
       "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.13.2.tgz",
-      "integrity": "sha512-41ukrclEbMddAyP7PvxMSYqnOSzPV6r7GNnyTSKSCNTaz19GehxmTiXyP9kwHSUv2+Dr6hHqiUiF7L1VAw2KdQ=="
+      "integrity": "sha512-41ukrclEbMddAyP7PvxMSYqnOSzPV6r7GNnyTSKSCNTaz19GehxmTiXyP9kwHSUv2+Dr6hHqiUiF7L1VAw2KdQ==",
+      "requires": {}
     },
     "vue-echarts": {
       "version": "6.2.3",

+ 1 - 1
package.json

@@ -36,7 +36,7 @@
     "mitt": "^3.0.0",
     "normalize.css": "^8.0.1",
     "numeral": "^2.0.6",
-    "plyr": "^3.6.12",
+    "plyr": "^3.7.3",
     "qrcode": "^1.5.1",
     "qrcode.vue": "^3.3.3",
     "query-string": "^7.1.1",

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

@@ -2,7 +2,30 @@ const paymentType = (window as any).paymentType
 
 // 需要登录的路由
 export const router = [
-
+  {
+    path: '/lessonCourseware',
+    name: 'lessonCourseware',
+    component: () => import('@/views/lessonCourseware/index'),
+    meta: {
+      title: '选择课件'
+    }
+  },
+  {
+    path: '/courseList',
+    name: 'courseList',
+    component: () => import('@/views/courseList/index'),
+    meta: {
+      title: '课程列表'
+    }
+  },
+  {
+    path: '/coursewarePlay',
+    name: 'coursewarePlay',
+    component: () => import('@/views/coursewarePlay/index'),
+    meta: {
+      title: '课程播放'
+    }
+  },
 ]
 
 // 不需要登录的路由

+ 4 - 3
src/router/routes-school.ts

@@ -160,7 +160,7 @@ export default [
       {
         path: '/exercise-record',
         name: 'exercise-record',
-        component: () => import('@/school/exercise-record'),
+        component: () => import('@/views/exercise-record'),
         meta: {
           title: '练习记录'
         }
@@ -168,7 +168,7 @@ export default [
       {
         path: '/exercis-detail',
         name: 'exercis-detail',
-        component: () => import('@/school/exercise-record/exercis-detail'),
+        component: () => import('@/views/exercise-record/exercis-detail'),
         meta: {
           title: '测评详情'
         }
@@ -244,7 +244,8 @@ export default [
         meta: {
           title: '课程预览'
         }
-      }, {
+      },
+      {
         path: '/course-adjust',
         name: 'course-adjust',
         component: () => import('@/school/approval-manage/course-adjust'),

+ 51 - 36
src/router/routes-student.ts

@@ -43,7 +43,8 @@ export default [
         meta: {
           title: '优惠券'
         }
-      }, {
+      },
+      {
         path: '/memberCenter',
         name: 'memberCenter',
         component: () => import('@/student/member-center/index'),
@@ -63,42 +64,56 @@ export default [
   {
     path: '/msuicGroup',
     component: MusicAuth,
-    children: [{
-      path: '/loginMusic',
-      name: 'loginMusic',
-      component: () => import('@/student/music-group/layout/login'),
-      meta: {
-        isRegister: false
-      } as metaType
-    }, {
-      path: '/preApply',
-      name: 'preApply',
-      component: () => import('@/student/music-group/pre-apply/index'),
-      meta: {
-        title: '乐团报名'
-      }
-    }, {
-      path: '/orderDetail',
-      name: 'orderDetail',
-      component: () => import('@/student/music-group/pre-apply/order-detail'),
-      meta: {
-        title: '订单详情'
-      }
-    }, {
-      path: '/shopAddress',
-      name: 'shopAddress',
-      component: () => import('@/student/music-group/shop-address/index'),
-      meta: {
-        title: '收货地址'
-      }
-    }, {
-      path: '/addressOperation',
-      name: 'addressOperation',
-      component: () => import('@/student/music-group/shop-address/address-operation'),
-      meta: {
-        title: '收货地址'
+    children: [
+      {
+        path: '/loginMusic',
+        name: 'loginMusic',
+        component: () => import('@/student/music-group/layout/login'),
+        meta: {
+          isRegister: false
+        } as metaType
+      },
+      {
+        path: '/preApply',
+        name: 'preApply',
+        component: () => import('@/student/music-group/pre-apply/index'),
+        meta: {
+          title: '乐团报名'
+        }
+      },
+      {
+        path: '/orderDetail',
+        name: 'orderDetail',
+        component: () => import('@/student/music-group/pre-apply/order-detail'),
+        meta: {
+          title: '订单详情'
+        }
+      },
+      {
+        path: '/shopAddress',
+        name: 'shopAddress',
+        component: () => import('@/student/music-group/shop-address/index'),
+        meta: {
+          title: '收货地址'
+        }
+      },
+      {
+        path: '/addressOperation',
+        name: 'addressOperation',
+        component: () => import('@/student/music-group/shop-address/address-operation'),
+        meta: {
+          title: '收货地址'
+        }
       }
-    }]
+    ]
+  },
+  {
+    path: '/ranking-list',
+    name: 'ranking-list',
+    component: () => import('@/student/ranking-list/index'),
+    meta: {
+      title: '排行榜'
+    }
   },
   ...noLoginRouter,
   ...rootRouter

+ 25 - 3
src/router/routes-teacher.ts

@@ -9,9 +9,7 @@ type metaType = {
 }
 
 // 不需要登录的路由
-const noLoginRouter = [
-
-]
+const noLoginRouter = []
 
 export default [
   {
@@ -31,6 +29,30 @@ export default [
         path: '/home',
         name: 'home',
         component: () => import('@/teacher/home/index')
+      },
+      {
+        path: '/exercise-record',
+        name: 'exercise-record',
+        component: () => import('@/views/exercise-record'),
+        meta: {
+          title: '练习记录'
+        }
+      },
+      {
+        path: '/exercis-detail',
+        name: 'exercis-detail',
+        component: () => import('@/views/exercise-record/exercis-detail'),
+        meta: {
+          title: '测评详情'
+        }
+      },
+      {
+        path: '/attendance',
+        name: 'attendance',
+        component: () => import('@/teacher/attendance/index'),
+        meta: {
+          title: '我的考勤'
+        }
       }
     ]
   },

+ 2 - 2
src/school/ranking-list/components/day-bang.tsx

@@ -42,7 +42,7 @@ export default defineComponent({
       subjectName: '全部声部',
       page: 1,
       rows: 50,
-      sortType: 'PRACTICE_DAY'
+      sortType: 'PRACTICE_TIMES'
     })
     const minDate = ref(new Date(dayjs().subtract(10, 'year').format('YYYY-MM-DD')))
     const maxDate = ref(new Date(dayjs().add(10, 'year').format('YYYY-MM-DD')))
@@ -223,7 +223,7 @@ export default defineComponent({
               onLoad={getList}
             >
               {list.value.map((item: any, index: number) => (
-                <RankItem item={item} type="time" index={index + 1}></RankItem>
+                <RankItem item={item} type="day" index={index + 1}></RankItem>
               ))}
             </List>
           </PullRefresh>

+ 2 - 2
src/school/ranking-list/components/timer-bang.tsx

@@ -42,7 +42,7 @@ export default defineComponent({
       subjectName: '全部声部',
       page: 1,
       rows: 50,
-      sortType: 'PRACTICE_DAY'
+      sortType: 'PRACTICE_TIMERS'
     })
     const minDate = ref(new Date(dayjs().subtract(10, 'year').format('YYYY-MM-DD')))
     const maxDate = ref(new Date(dayjs().add(10, 'year').format('YYYY-MM-DD')))
@@ -223,7 +223,7 @@ export default defineComponent({
               onLoad={getList}
             >
               {list.value.map((item: any, index: number) => (
-                <RankItem item={item} type="day" index={index + 1}></RankItem>
+                <RankItem item={item} type="time" index={index + 1}></RankItem>
               ))}
             </List>
           </PullRefresh>

+ 4 - 4
src/school/ranking-list/index.tsx

@@ -7,7 +7,7 @@ import { useRouter } from 'vue-router'
 import TimerBang from './components/timer-bang'
 import DayBang from './components/day-bang'
 import styles from './index.module.less'
-const activeName = ref('student')
+const activeName = ref('day')
 const timers = ref('')
 export default defineComponent({
   name: 'ranking-list',
@@ -42,11 +42,11 @@ export default defineComponent({
             title-inactive-color={'#fff'}
             color={'#fff'}
           >
-            <Tab name="student" title="天数榜"></Tab>
-            <Tab name="teacher" title="时长榜"></Tab>
+            <Tab name="day" title="天数榜"></Tab>
+            <Tab name="timer" title="时长榜"></Tab>
           </Tabs>
         </OSticky>
-        {activeName.value == 'student' ? (
+        {activeName.value == 'timer' ? (
           <TimerBang onSetTime={(val: string) => setTime(val)} toHeight={state.heightV}></TimerBang>
         ) : (
           <DayBang onSetTime={(val: string) => setTime(val)} toHeight={state.heightV}></DayBang>

+ 136 - 0
src/student/ranking-list/components/day-bang.tsx

@@ -0,0 +1,136 @@
+import OSearch from '@/components/o-search'
+import OEmpty from '@/components/o-empty'
+import dayjs from 'dayjs'
+import {
+  Icon,
+  Popover,
+  DatePicker,
+  DatePickerColumnType,
+  Popup,
+  List,
+  PullRefresh,
+  ActionSheet,
+  showToast,
+  Sticky
+} from 'vant'
+import { defineComponent, reactive, ref, onMounted, watch, inject } from 'vue'
+import { useRouter } from 'vue-router'
+import styles from './timer-bang.module.less'
+import request from '@/helpers/request'
+import { state as globalState } from '@/state'
+import RankItem from '../modals/rank-item'
+import MyRankingItem from '../modals/my-ranking-item'
+export default defineComponent({
+  props: ['toHeight'],
+  emits: ['setTime'],
+  name: 'day-bang',
+  setup(props, { slots, attrs, emit }) {
+    const router = useRouter()
+    const state = reactive({
+      showPopoverTime: false,
+      showPopoverOrchestra: false,
+      showPopoverSubject: false,
+      actions: [] as any,
+      subjects: [] as any,
+      currentDate: [dayjs().format('YYYY'), dayjs().format('MM')]
+    })
+    const parentData = inject('parentData', { practiceMonth: '', timeName: '' } as any)
+    const forms = reactive({
+      practiceMonth: parentData.practiceMonth,
+      page: 1,
+      rows: 50,
+      sortType: 'PRACTICE_DAY'
+    })
+    const minDate = ref(new Date(dayjs().subtract(10, 'year').format('YYYY-MM-DD')))
+    const maxDate = ref(new Date(dayjs().add(10, 'year').format('YYYY-MM-DD')))
+    const columnsType = ref<DatePickerColumnType[]>(['year', 'month'])
+    const refreshing = ref(false)
+    const loading = ref(false)
+    const finished = ref(false)
+    const showContact = ref(false)
+    const list = ref([])
+    const toTop = ref(props.toHeight)
+    console.log(props.toHeight)
+    watch(
+      () => props.toHeight,
+      (val: number) => {
+        toTop.value = val
+      }
+    )
+    watch(
+      () => parentData.practiceMonth,
+      (val) => {
+        forms.practiceMonth = val
+        refreshing.value = true
+        getList()
+      }
+    )
+    const getList = async () => {
+      loading.value = true
+      try {
+        if (refreshing.value) {
+          forms.page = 1
+          list.value = []
+          refreshing.value = false
+        }
+
+        const res = await request.post('/api-school/student/page', {
+          data: { ...forms }
+        })
+
+        if (list.value.length > 0 && res.data.pages === 1) {
+          return
+        }
+
+        forms.page = res.data.current + 1
+        // list.value = list.value.concat(res.data.rows || [])
+        list.value = res.data.rows
+        showContact.value = list.value.length > 0
+        console.log(showContact.value, ' showContact.value ')
+        loading.value = false
+        finished.value = true
+        // finished.value = res.data.current >= res.data.pages
+      } catch (e: any) {
+        // console.log(e, 'e')
+        const message = e.message
+        showToast(message)
+        showContact.value = false
+        finished.value = true
+      }
+    }
+
+    onMounted(() => {
+      getList()
+    })
+    const onRefresh = () => {
+      finished.value = false
+      // 重新加载数据
+      // 将 loading 设置为 true,表示处于加载状态
+      loading.value = true
+      getList()
+    }
+    return () => (
+      <>
+        {showContact.value ? (
+          <div>
+            <PullRefresh v-model={refreshing.value} onRefresh={onRefresh}>
+              <List
+                v-model:loading={loading.value}
+                finished={finished.value}
+                finished-text="没有更多了"
+                onLoad={getList}
+              >
+                {list.value.map((item: any, index: number) => (
+                  <RankItem item={item} type="day" index={index + 1}></RankItem>
+                ))}
+              </List>
+            </PullRefresh>
+            <MyRankingItem item={list.value[0]}></MyRankingItem>
+          </div>
+        ) : (
+          <OEmpty />
+        )}
+      </>
+    )
+  }
+})

+ 10 - 0
src/student/ranking-list/components/timer-bang.module.less

@@ -0,0 +1,10 @@
+.chioseWrap {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-around;
+  background-color: #fff;
+  color: #333;
+  font-weight: 500;
+  padding: 12px 0;
+}

+ 156 - 0
src/student/ranking-list/components/timer-bang.tsx

@@ -0,0 +1,156 @@
+import OSearch from '@/components/o-search'
+import OEmpty from '@/components/o-empty'
+import dayjs from 'dayjs'
+import {
+  Icon,
+  Popover,
+  DatePicker,
+  DatePickerColumnType,
+  Popup,
+  List,
+  PullRefresh,
+  ActionSheet,
+  showToast,
+  Sticky
+} from 'vant'
+import { defineComponent, reactive, ref, onMounted, watch, inject } from 'vue'
+import { useRouter } from 'vue-router'
+import styles from './timer-bang.module.less'
+import request from '@/helpers/request'
+import { state as globalState } from '@/state'
+import RankItem from '../modals/rank-item'
+import MyRankingItem from '../modals/my-ranking-item'
+export default defineComponent({
+  name: 'timer-bang',
+  props: ['toHeight'],
+  emits: ['setTime'],
+  setup(props, { slots, attrs, emit }) {
+    const router = useRouter()
+    const state = reactive({
+      showPopoverTime: false,
+      showPopoverOrchestra: false,
+      showPopoverSubject: false,
+      actions: [] as any,
+      subjects: [] as any,
+      currentDate: [dayjs().format('YYYY'), dayjs().format('MM')]
+    })
+    const parentData = inject('parentData', { practiceMonth: '', timeName: '' } as any)
+    const forms = reactive({
+      practiceMonth: parentData.practiceMonth,
+      page: 1,
+      rows: 50,
+      sortType: 'PRACTICE_TIMES'
+    })
+    const minDate = ref(new Date(dayjs().subtract(10, 'year').format('YYYY-MM-DD')))
+    const maxDate = ref(new Date(dayjs().add(10, 'year').format('YYYY-MM-DD')))
+
+    const refreshing = ref(false)
+    const loading = ref(false)
+    const finished = ref(false)
+    const showContact = ref(false)
+    const list = ref([])
+    const toTop = ref(props.toHeight)
+    watch(
+      () => props.toHeight,
+      (val: number) => {
+        toTop.value = val
+        console.log(toTop.value)
+      }
+    )
+    watch(
+      () => parentData.practiceMonth,
+      (val) => {
+        forms.practiceMonth = val
+        refreshing.value = true
+        getList()
+      }
+    )
+    const getList = async () => {
+      loading.value = true
+      try {
+        if (refreshing.value) {
+          forms.page = 1
+          list.value = []
+          refreshing.value = false
+        }
+
+        const res = await request.post('/api-school/student/page', {
+          data: { ...forms }
+        })
+
+        if (list.value.length > 0 && res.data.pages === 1) {
+          return
+        }
+
+        forms.page = res.data.current + 1
+        // list.value = list.value.concat(res.data.rows || [])
+        list.value = res.data.rows
+        showContact.value = list.value.length > 0
+        console.log(showContact.value, ' showContact.value ')
+        loading.value = false
+        finished.value = true
+        // finished.value = res.data.current >= res.data.pages
+      } catch (e: any) {
+        // console.log(e, 'e')
+        const message = e.message
+        showToast(message)
+        showContact.value = false
+        finished.value = true
+      }
+    }
+
+    const getSubjects = async () => {
+      try {
+        const res = await request.post('/api-school/subject/page', {
+          data: { page: 1, rows: 9999 }
+        })
+        state.subjects = res.data.rows.map((item) => {
+          return {
+            name: item.name,
+            value: item.id as string
+          }
+        })
+        state.subjects.unshift({ name: '全部声部', value: '' })
+      } catch (e: any) {
+        const message = e.message
+        showToast(message)
+      }
+    }
+    onMounted(() => {
+      getList()
+      emit('setTime', forms.timeName)
+    })
+    const onRefresh = () => {
+      finished.value = false
+      // 重新加载数据
+      // 将 loading 设置为 true,表示处于加载状态
+      loading.value = true
+      getList()
+    }
+    return () => (
+      <>
+        {/* <OSticky position="top" background="#FFF"> */}
+
+        {showContact.value ? (
+          <div>
+            <PullRefresh v-model={refreshing.value} onRefresh={onRefresh}>
+              <List
+                v-model:loading={loading.value}
+                finished={finished.value}
+                finished-text="没有更多了"
+                onLoad={getList}
+              >
+                {list.value.map((item: any, index: number) => (
+                  <RankItem item={item} type="time" index={index + 1}></RankItem>
+                ))}
+              </List>
+            </PullRefresh>
+            <MyRankingItem item={list.value[0]}></MyRankingItem>
+          </div>
+        ) : (
+          <OEmpty />
+        )}
+      </>
+    )
+  }
+})

BIN
src/student/ranking-list/images/first.png


BIN
src/student/ranking-list/images/ranking-bg.png


BIN
src/student/ranking-list/images/second.png


BIN
src/student/ranking-list/images/third.png


+ 30 - 0
src/student/ranking-list/index.module.less

@@ -0,0 +1,30 @@
+@img: './images';
+.topWrap {
+  height: 280px;
+  background: url('@{img}/ranking-bg.png') center center/ cover;
+  position: relative;
+  .topTime {
+    position: absolute;
+    left: 29px;
+    bottom: 73px;
+    font-size: 14px;
+    font-weight: 500;
+    color: #ffffff;
+    line-height: 20px;
+    letter-spacing: 1px;
+  }
+}
+
+.rankTabs {
+  position: absolute;
+  width: 100%;
+  bottom: -1px;
+  height: 45px;
+  line-height: 45px;
+  :global {
+    .van-tabs__line {
+      bottom: 20px;
+      width: 20px;
+    }
+  }
+}

+ 83 - 0
src/student/ranking-list/index.tsx

@@ -0,0 +1,83 @@
+import OHeader from '@/components/o-header'
+import OSticky from '@/components/o-sticky'
+import { Tabs, Tab, Icon, Popup, DatePicker, DatePickerColumnType } from 'vant'
+import { defineComponent, reactive, ref, provide } from 'vue'
+import { useRouter } from 'vue-router'
+// import linkBg from './images/ranking-bg.png'
+import TimerBang from './components/timer-bang'
+import DayBang from './components/day-bang'
+import styles from './index.module.less'
+import dayjs from 'dayjs'
+const activeName = ref('student')
+const timers = ref('')
+export default defineComponent({
+  name: 'ranking-list',
+  setup() {
+    const router = useRouter()
+    const state = reactive({
+      heightV: 0,
+      showPopoverTime: false,
+      currentDate: [dayjs().format('YYYY'), dayjs().format('MM')]
+    })
+
+    const forms = reactive({
+      practiceMonth: state.currentDate[0] + '' + state.currentDate[1],
+      timeName: state.currentDate[0] + '年' + state.currentDate[1] + '月'
+    })
+    provide('parentData', forms)
+    const getHeight = (dataHeight: number) => {
+      state.heightV = dataHeight
+      console.log(state.heightV, '获取高度')
+    }
+    const columnsType = ref<DatePickerColumnType[]>(['year', 'month'])
+    const checkTimer = (val: any) => {
+      forms.practiceMonth = val.selectedValues[0] + val.selectedValues[1]
+      forms.timeName = val.selectedValues[0] + '年' + val.selectedValues[1] + '月'
+      state.showPopoverTime = false
+    }
+    return () => (
+      <>
+        <OSticky position="top" background="#F8F8F8" onGetHeight={getHeight}>
+          <div class={styles.topWrap}>
+            <OHeader
+              isBack={true}
+              color={'#ffffff'}
+              background={'transparent'}
+              border={false}
+            ></OHeader>
+            <span class={styles.topTime} onClick={() => (state.showPopoverTime = true)}>
+              {forms.timeName} <Icon name={state.showPopoverTime ? 'arrow-up' : 'arrow-down'} />
+            </span>
+          </div>
+          <Tabs
+            v-model:active={activeName.value}
+            class={styles.rankTabs}
+            background={'rgba(0,0,0,.35)'}
+            title-active-color={'#fff'}
+            title-inactive-color={'#fff'}
+            color={'#fff'}
+          >
+            <Tab name="day" title="天数榜"></Tab>
+            <Tab name="timer" title="时长榜"></Tab>
+          </Tabs>
+        </OSticky>
+        {activeName.value == 'timer' ? (
+          <TimerBang toHeight={state.heightV}></TimerBang>
+        ) : (
+          <DayBang toHeight={state.heightV}></DayBang>
+        )}
+        <Popup v-model:show={state.showPopoverTime} position="bottom" style="{ height: '30%' }">
+          <DatePicker
+            onCancel={() => {
+              state.showPopoverTime = false
+            }}
+            onConfirm={checkTimer}
+            v-model={state.currentDate}
+            title="选择年月"
+            columnsType={columnsType.value}
+          />
+        </Popup>
+      </>
+    )
+  }
+})

+ 33 - 0
src/student/ranking-list/modals/my-ranking-item.tsx

@@ -0,0 +1,33 @@
+import { defineComponent, reactive, ref, watch } from 'vue'
+import styles from './rank-item.module.less'
+import defaultIcon from '@/school/images/default-icon.jpg'
+export default defineComponent({
+  props: ['item', 'type', 'index'],
+  name: 'rank-item',
+  setup(props) {
+    return () => (
+      <>
+        <div>
+          <div class={styles.itemRankWrap}>
+            <div class={styles.wrapLeft}>
+              <div class={styles.headerWrap}>
+                <img src={props.item.avatar ? props.item.avatar : defaultIcon} alt="" />
+              </div>
+              <div>
+                <p class={styles.studentName}>{props.item.nickname}</p>
+                <div class={styles.myTag}>{props.item.subjectNames}</div>
+              </div>
+            </div>
+            <div class={styles.wrapRight}>
+              <p>
+                <span>我的排名</span>
+                {props.item.ranking ? props.item.ranking : 0}
+              </p>
+            </div>
+          </div>
+          <div class={styles.wall}></div>
+        </div>
+      </>
+    )
+  }
+})

+ 137 - 0
src/student/ranking-list/modals/rank-item.module.less

@@ -0,0 +1,137 @@
+.itemWrap {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 12px 18px;
+  background-color: #fff;
+  justify-content: space-between;
+  .wrapLeft {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    .numWrap {
+      width: 32px;
+      height: 32px;
+      font-size: 22px;
+      font-family: DINAlternate-Bold, DINAlternate;
+      font-weight: bold;
+      color: #aaaaaa;
+      line-height: 32px;
+      margin-right: 10px;
+      text-align: center;
+      img {
+        width: 100%;
+      }
+    }
+    .headerWrap {
+      width: 48px;
+      height: 48px;
+      overflow: hidden;
+      border-radius: 50%;
+      margin-right: 10px;
+      img {
+        width: 100%;
+        height: 100%;
+      }
+    }
+    .studentName {
+      font-size: 16px;
+      font-weight: 500;
+      color: #333333;
+      line-height: 22px;
+      text-align: center;
+    }
+    .tag {
+      background: #ffe7da;
+      border-radius: 4px;
+      font-size: 12px;
+      text-align: center;
+      font-weight: 500;
+      color: #f67146;
+      line-height: 17px;
+      padding: 0 8px;
+    }
+  }
+  .wrapRight {
+    p {
+      font-size: 16px;
+      color: #777;
+      line-height: 19px;
+      span {
+        font-size: 16px;
+        font-family: DINAlternate-Bold, DINAlternate;
+        font-weight: bold;
+        color: #333333;
+        line-height: 19px;
+      }
+    }
+  }
+}
+
+.itemRankWrap {
+  width: 100%;
+  box-sizing: border-box;
+  height: 98px;
+  position: fixed;
+  left: 0;
+  bottom: 0;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 14px 18px 34px;
+  background-color: #fff;
+  justify-content: space-between;
+  .wrapLeft {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    .headerWrap {
+      width: 48px;
+      height: 48px;
+      overflow: hidden;
+      border-radius: 50%;
+      margin-right: 14px;
+      border: 2px solid #ffffff;
+      img {
+        width: 100%;
+        height: 100%;
+      }
+    }
+    .studentName {
+      font-size: 16px;
+      font-weight: 600;
+      color: #333333;
+      line-height: 22px;
+      margin-bottom: 1px;
+      text-align: center;
+    }
+    .myTag {
+      background: #ff8057;
+      border-radius: 12px;
+      font-size: 12px;
+      text-align: center;
+      font-weight: 500;
+      color: #fff;
+      line-height: 17px;
+      padding: 0 8px;
+    }
+  }
+  .wrapRight {
+    p {
+      font-size: 20px;
+      font-family: DINAlternate-Bold, DINAlternate;
+      font-weight: bold;
+      color: #333333;
+      line-height: 24px;
+      span {
+        font-size: 16px;
+        color: #777;
+        line-height: 24px;
+        margin-right: 3px;
+      }
+    }
+  }
+}
+.wall {
+  height: 98px;
+}

+ 46 - 0
src/student/ranking-list/modals/rank-item.tsx

@@ -0,0 +1,46 @@
+import { defineComponent, reactive, ref, watch } from 'vue'
+import styles from './rank-item.module.less'
+import defaultIcon from '@/school/images/default-icon.jpg'
+import firstIcon from '../images/first.png'
+import secondIcon from '../images/second.png'
+import thirdIcon from '../images/third.png'
+export default defineComponent({
+  props: ['item', 'type', 'index'],
+  name: 'rank-item',
+  setup(props) {
+    return () => (
+      <>
+        <div>
+          <div class={styles.itemWrap}>
+            <div class={styles.wrapLeft}>
+              <div class={styles.numWrap}>
+                {props.index == 1 ? <img src={firstIcon} alt="" /> : null}
+                {props.index == 2 ? <img src={secondIcon} alt="" /> : null}
+                {props.index == 3 ? <img src={thirdIcon} alt="" /> : null}
+                {props.index > 3 ? <p>{props.index}</p> : null}
+              </div>
+              <div class={styles.headerWrap}>
+                <img src={props.item.avatar ? props.item.avatar : defaultIcon} alt="" />
+              </div>
+              <div>
+                <p class={styles.studentName}>{props.item.nickname}</p>
+                <div class={styles.tag}>{props.item.subjectNames}</div>
+              </div>
+            </div>
+            <div class={styles.wrapRight}>
+              {props.type == 'day' ? (
+                <p>
+                  <span>{props.item.practiceDays ? props.item.practiceDays : 0}</span>天
+                </p>
+              ) : (
+                <p>
+                  <span>{props.item.practiceTimes ? props.item.practiceTimes : 0}</span>小时
+                </p>
+              )}
+            </div>
+          </div>
+        </div>
+      </>
+    )
+  }
+})

+ 24 - 0
src/teacher/attendance/index.module.less

@@ -0,0 +1,24 @@
+.NavTitle {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  .sanIcons {
+    width: 15px;
+    height: 15px;
+    margin-left: 4px;
+  }
+}
+.isReversal {
+  transition: all 0.1;
+  transform: rotate(180deg);
+}
+
+.chioseWrap {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: flex-start;
+  background-color: #f8f8f8;
+  color: #333;
+  font-weight: 500;
+}

+ 253 - 0
src/teacher/attendance/index.tsx

@@ -0,0 +1,253 @@
+import OHeader from '@/components/o-header'
+import OEmpty from '@/components/o-empty'
+import dayjs from 'dayjs'
+import {
+  Icon,
+  Popover,
+  DatePicker,
+  DatePickerColumnType,
+  Popup,
+  List,
+  PullRefresh,
+  ActionSheet,
+  showToast,
+  Sticky
+} from 'vant'
+import { defineComponent, reactive, ref, onMounted, watch } from 'vue'
+import { useRouter } from 'vue-router'
+import styles from './index.module.less'
+import request from '@/helpers/request'
+import { state as globalState } from '@/state'
+import { courseEmnu } from '@/constant'
+import TeacherAttItem from './modals/teacherAtt-item'
+export default defineComponent({
+  name: 'attend-student',
+  props: {
+    toHeight: {
+      type: Number,
+      default: 0
+    }
+  },
+  setup(props) {
+    const router = useRouter()
+    const state = reactive({
+      showPopoverTime: false,
+      showPopoverOrchestra: false,
+      showPopoverSubject: false,
+      actions: [] as any,
+      courseList: [] as any,
+      currentDate: [dayjs().format('YYYY'), dayjs().format('MM')]
+    })
+    const forms = reactive({
+      time: state.currentDate[0] + '-' + state.currentDate[1],
+      timeName: state.currentDate[0] + '年' + state.currentDate[1] + '月',
+      keyword: '',
+      orchestraId: '',
+      orchestraName: '全部乐团',
+      courseType: '',
+      courseTypeName: '所有课程',
+      page: 1,
+      rows: 20
+    })
+    const toTop = ref(props.toHeight)
+    const minDate = ref(new Date(dayjs().subtract(10, 'year').format('YYYY-MM-DD')))
+    const maxDate = ref(new Date(dayjs().add(10, 'year').format('YYYY-MM-DD')))
+    const columnsType = ref<DatePickerColumnType[]>(['year', 'month'])
+    const refreshing = ref(false)
+    const loading = ref(false)
+    const finished = ref(false)
+    const showContact = ref(false)
+    const list = ref([])
+
+    const getList = async () => {
+      loading.value = true
+      try {
+        if (refreshing.value) {
+          forms.page = 1
+          list.value = []
+          refreshing.value = false
+        }
+
+        const res = await request.post('/api-teacher/courseSchedule/teacherAttendance', {
+          data: { ...forms }
+        })
+
+        if (list.value.length > 0 && res.data.pages === 1) {
+          return
+        }
+
+        forms.page = res.data.current + 1
+        for (let i = 0; i < 10; i++) {
+          list.value = list.value.concat(res.data.rows || [])
+        }
+
+        showContact.value = list.value.length > 0
+        loading.value = false
+
+        finished.value = res.data.current >= res.data.pages
+      } catch (e: any) {
+        // console.log(e, 'e')
+        const message = e.message
+        showToast(message)
+        showContact.value = false
+        finished.value = true
+      }
+    }
+    const getCourseList = () => {
+      state.courseList = []
+      for (const key in courseEmnu) {
+        state.courseList.push({ name: courseEmnu[key], value: key })
+      }
+      state.courseList.unshift({ name: '全部课程', value: '' })
+    }
+    const checkTimer = (val: any) => {
+      forms.time = val.selectedValues[0] + '-' + val.selectedValues[1]
+      forms.timeName = val.selectedValues[0] + '年' + val.selectedValues[1] + '月'
+      state.showPopoverTime = false
+      refreshing.value = true
+      getList()
+    }
+    const checkOrchestra = (val: any) => {
+      forms.orchestraId = val.value
+      forms.orchestraName = val.name
+      state.showPopoverOrchestra = false
+      refreshing.value = true
+      getList()
+    }
+
+    const checkSubject = (val: any) => {
+      forms.courseType = val.value
+      forms.courseTypeName = val.name
+      refreshing.value = true
+      getList()
+    }
+    const getOrchestraList = async () => {
+      try {
+        const res = await request.post('/api-teacher/orchestra/page', {
+          data: { page: 1, rows: 9999 }
+        })
+        state.actions = res.data.rows.map((item) => {
+          return {
+            name: item.name,
+            value: item.id as string
+          }
+        })
+        state.actions.unshift({ name: '全部乐团', value: '' })
+      } catch (e: any) {
+        const message = e.message
+        showToast(message)
+      }
+    }
+    watch(
+      () => props.toHeight,
+      (val: number) => {
+        toTop.value = val
+        console.log(toTop.value, '老师的')
+      }
+    )
+    onMounted(() => {
+      getOrchestraList()
+      getList()
+      getCourseList()
+    })
+    const onRefresh = () => {
+      finished.value = false
+      // 重新加载数据
+      // 将 loading 设置为 true,表示处于加载状态
+      loading.value = true
+      getList()
+    }
+    return () => (
+      <>
+        <Sticky offsetTop={toTop.value}>
+          <div>
+            <OHeader></OHeader>
+            <div class={styles.chioseWrap}>
+              <div style={{ padding: '12px 13px', background: '#F8F8F8' }}>
+                <div
+                  class={styles.searchBand}
+                  onClick={() => {
+                    state.showPopoverTime = true
+                  }}
+                >
+                  {forms.timeName}
+                  <Icon name={state.showPopoverTime ? 'arrow-up' : 'arrow-down'} />
+                </div>
+              </div>
+
+              <div style={{ padding: '12px 13px', background: '#F8F8F8' }}>
+                <div
+                  class={styles.searchBand}
+                  onClick={() => {
+                    state.showPopoverOrchestra = true
+                  }}
+                >
+                  {forms.orchestraName}
+                  <Icon name={state.showPopoverOrchestra ? 'arrow-up' : 'arrow-down'} />
+                </div>
+              </div>
+              <div style={{ padding: '12px 13px', background: '#F8F8F8' }}>
+                <div
+                  class={styles.searchBand}
+                  onClick={() => {
+                    state.showPopoverSubject = true
+                  }}
+                >
+                  {forms.courseTypeName}
+                  <Icon name={state.showPopoverSubject ? 'arrow-up' : 'arrow-down'} />
+                </div>
+              </div>
+            </div>
+          </div>
+        </Sticky>
+
+        {showContact.value ? (
+          <PullRefresh v-model={refreshing.value} onRefresh={onRefresh}>
+            <List
+              v-model:loading={loading.value}
+              finished={finished.value}
+              finished-text="没有更多了"
+              onLoad={getList}
+            >
+              {list.value.map((item: any) => (
+                <TeacherAttItem item={item}></TeacherAttItem>
+              ))}
+            </List>
+          </PullRefresh>
+        ) : (
+          <OEmpty />
+        )}
+
+        <Popup v-model:show={state.showPopoverTime} position="bottom" style="{ height: '30%' }">
+          <DatePicker
+            onCancel={() => {
+              state.showPopoverTime = false
+            }}
+            onConfirm={checkTimer}
+            v-model={state.currentDate}
+            title="选择年月"
+            minDate={minDate.value}
+            maxDate={maxDate.value}
+            columnsType={columnsType.value}
+          />
+        </Popup>
+
+        <ActionSheet
+          v-model:show={state.showPopoverOrchestra}
+          title="选择乐团"
+          actions={state.actions}
+          onSelect={checkOrchestra}
+        ></ActionSheet>
+
+        <ActionSheet
+          style={{ height: '40%' }}
+          close-on-click-action
+          v-model:show={state.showPopoverSubject}
+          title="选择课程"
+          actions={state.courseList}
+          onSelect={checkSubject}
+        ></ActionSheet>
+      </>
+    )
+  }
+})

+ 142 - 0
src/teacher/attendance/modals/teacherAtt-item.module.less

@@ -0,0 +1,142 @@
+.itemWrap {
+  padding: 12px 15px 15px;
+  border-radius: 10px;
+  background-color: #fff;
+  margin: 0 13px 12px;
+  .itemWrapTop {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    justify-content: space-between;
+    padding-bottom: 12px;
+    border-bottom: 1px solid #f2f2f2;
+    .itemWrapTopLeft {
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+      .clockWrap {
+        width: 18px;
+        height: 18px;
+        margin-right: 6px;
+        img {
+          width: 100%;
+          height: 100%;
+        }
+      }
+      .leftTimer {
+        font-size: 14px;
+        font-weight: 500;
+        color: #333333;
+        line-height: 20px;
+      }
+    }
+    .itemWrapTopRight {
+      font-size: 12px;
+      color: #777;
+    }
+  }
+  .itemWrapBottom {
+    padding-top: 15px;
+
+    .courseInfo {
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+      padding-bottom: 15px;
+      justify-content: space-between;
+      .headImgs {
+        width: 42px;
+        height: 42px;
+        border-radius: 50%;
+        overflow: hidden;
+        margin-right: 12px;
+      }
+      .infoMsg {
+        .infoMsgMain {
+          font-size: 16px;
+          font-weight: 600;
+          color: #333333;
+          line-height: 22px;
+        }
+        .infoMsgSub {
+          font-size: 12px;
+          font-weight: 400;
+          color: #777777;
+          line-height: 17px;
+        }
+      }
+    }
+    .typeTag {
+      padding: 0 6px;
+      color: #fff;
+      background: #ff8057;
+      border-radius: 4px;
+      line-height: 17px;
+      font-weight: 500;
+    }
+    .typeTagNo {
+      padding: 0 6px;
+      color: #fff;
+      background: #d2d2d2;
+      border-radius: 4px;
+      line-height: 17px;
+      font-weight: 500;
+    }
+    .attInfo {
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+      justify-content: space-between;
+      .attInfoDot {
+        text-align: left;
+        padding: 12px;
+        .attInfoDotTitle {
+          width: 100%;
+          display: flex;
+          flex-direction: row;
+          margin-bottom: 7px;
+          align-items: center;
+          img {
+            width: 18px;
+            height: 18px;
+            margin-left: 60px;
+          }
+        }
+        .signTime {
+          font-size: 20px;
+          font-weight: 600;
+          color: #333333;
+          line-height: 28px;
+        }
+      }
+    }
+    .passWrap,
+    .goWrap {
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+      border-radius: 10px;
+      justify-content: space-between;
+      text-align: center;
+      .itemBottomMain {
+        font-size: 30px;
+        font-weight: bold;
+        color: #333333;
+        line-height: 35px;
+        margin-bottom: 2px;
+      }
+      .itemBottomSub {
+        font-size: 14px;
+        font-weight: 400;
+        color: #333333;
+        line-height: 20px;
+      }
+    }
+    .passWrap {
+      background-color: #ddecff;
+    }
+    .goWrap {
+      background-color: #ffe1e1;
+    }
+  }
+}

+ 87 - 0
src/teacher/attendance/modals/teacherAtt-item.tsx

@@ -0,0 +1,87 @@
+import { defineComponent, reactive, ref } from 'vue'
+import styles from './teacherAtt-item.module.less'
+import clockIcon from '@/school/attendance/images/clock-icon.png'
+import errorIcon from '@/school/attendance/images/error-icon.png'
+import successIcon from '@/school/attendance/images/success-icon.png'
+import defaultIcon from '@/school/images/default-icon.jpg'
+import { Icon, ActionSheet } from 'vant'
+import dayjs from 'dayjs'
+import { useRouter } from 'vue-router'
+export default defineComponent({
+  props: ['item'],
+  name: 'teacherAtt-item',
+  setup(props) {
+    const router = useRouter()
+    const gotoStudentDetail = () => {
+      // router.push({ path: '/student-att-day', query: { time: props.item.time } })
+    }
+    return () => (
+      <>
+        <div class={styles.itemWrap} onClick={gotoStudentDetail}>
+          <div class={styles.itemWrapTop}>
+            <div class={styles.itemWrapTopLeft}>
+              <div class={styles.clockWrap}>
+                <img src={clockIcon} alt="" />
+              </div>
+              <p class={styles.leftTimer}>
+                {dayjs(props.item.startTime).format('YYYY-MM-DD hh:mm')}
+                {'-'}
+                {dayjs(props.item.endTime).format('hh:mm')}
+              </p>
+            </div>
+            <div class={styles.itemWrapTopRight}>
+              <Icon name="arrow"></Icon>
+            </div>
+          </div>
+          <div class={styles.itemWrapBottom}>
+            <div class={styles.courseInfo}>
+              <div class={styles.infoMsg}>
+                <p class={styles.infoMsgMain}>{props.item.classGroupName}</p>
+                <p class={styles.infoMsgSub}>{props.item.orchestraName}</p>
+              </div>
+              {props.item.subsidy ? (
+                <div class={styles.typeTag}>补助课</div>
+              ) : (
+                <div class={styles.typeTagNo}>非补助课</div>
+              )}
+            </div>
+            <div class={styles.attInfo}>
+              <div class={props.item.signInStatus === 'NORMAL' ? styles.passWrap : styles.goWrap}>
+                <div class={styles.attInfoDot}>
+                  <div class={styles.attInfoDotTitle}>
+                    <span>签到时间</span>
+                    <img
+                      src={props.item.signInStatus === 'NORMAL' ? successIcon : errorIcon}
+                      alt=""
+                    />
+                  </div>
+                  <p class={styles.signTime}>
+                    {props.item.signInTime
+                      ? dayjs(props.item.signInTime).format('hh:mm:ss')
+                      : '未签到'}
+                  </p>
+                </div>
+              </div>
+              <div class={props.item.signOutStatus === 'NORMAL' ? styles.passWrap : styles.goWrap}>
+                <div class={styles.attInfoDot}>
+                  <div class={styles.attInfoDotTitle}>
+                    <span>签退时间</span>
+                    <img
+                      src={props.item.signOutStatus === 'NORMAL' ? successIcon : errorIcon}
+                      alt=""
+                    />
+                  </div>
+                  <p class={styles.signTime}>
+                    {props.item.signOutTime
+                      ? dayjs(props.item.signOutTime).format('hh:mm:ss')
+                      : '未签到'}
+                  </p>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </>
+    )
+  }
+})

+ 13 - 0
src/views/courseList/image/look.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="13px" height="13px" viewBox="0 0 13 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="选择课件" transform="translate(-282.000000, -159.000000)">
+            <g id="锁备份" transform="translate(282.000000, 159.000000)">
+                <circle id="椭圆形" fill="#FFFFFF" cx="6.5" cy="8" r="1"></circle>
+                <rect id="矩形" stroke="#FFFFFF" stroke-width="1.2" x="1.6" y="4.1" width="9.8" height="7.8" rx="2"></rect>
+                <path d="M4.5,4 L4.5,3 C4.5,1.8954305 5.3954305,1 6.5,1 C7.6045695,1 8.5,1.8954305 8.5,3 L8.5,4 L8.5,4" id="路径" stroke="#FFFFFF" stroke-width="1.2"></path>
+            </g>
+        </g>
+    </g>
+</svg>

+ 65 - 0
src/views/courseList/index.module.less

@@ -0,0 +1,65 @@
+.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;
+    .cover {
+      position: absolute;
+      left: 0;
+      right: 0;
+      top: 0;
+      bottom: 0;
+      z-index: -1;
+      display: block;
+      width: 100%;
+      height: 100%;
+      object-fit: cover;
+    }
+    .title {
+      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: 50%;
+      height: 20px;
+      border-radius: 20px;
+      background: linear-gradient(180deg, #ff9c7c 0%, #ff5757 100%);
+      color: #fff;
+    }
+    .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;
+    }
+  }
+}

+ 75 - 0
src/views/courseList/index.tsx

@@ -0,0 +1,75 @@
+import request from '@/helpers/request'
+import { state } from '@/state'
+import { Grid, GridItem, Icon } from 'vant'
+import { defineComponent, onMounted, reactive } from 'vue'
+import styles from './index.module.less'
+import iconLook from './image/look.svg'
+import { useRoute } from 'vue-router'
+export default defineComponent({
+  name: 'lessonCourseware',
+  setup() {
+    const route = useRoute()
+    const data = reactive({
+      list: [] as any
+    })
+    const getList = async () => {
+      try {
+        const res: any = await request.post(state.platformApi + '/lessonCoursewareDetail/page', {
+          data: {
+            lessonCoursewareId: route.query.id ? '1607562917531938817' : '1607562917531938817',
+            page: 1,
+            rows: 1000
+          }
+        })
+        if (Array.isArray(res?.data?.rows)) {
+          data.list = res.data.rows
+        }
+      } catch (error) {}
+    }
+    onMounted(() => {
+      getList()
+    })
+
+    const handleClick = (item: any) => {
+      if (route.query.code === 'select') {
+        console.log('选择课时')
+      }
+    }
+    return () => (
+      <div style={{ paddingTop: '14px' }}>
+        <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}>
+                    <div>{item.name}</div>
+                    <div>已使用 1 次</div>
+                  </div>
+                  <div class={styles.num}>
+                    查看
+                    <Icon name="play-circle-o" />
+                  </div>
+                  {item.delFlag && (
+                    <div class={styles.look}>
+                      <Icon name={iconLook} /> 未解锁
+                    </div>
+                  )}
+                </div>
+              </GridItem>
+            )
+          })}
+        </Grid>
+      </div>
+    )
+  }
+})

+ 11 - 0
src/views/coursewarePlay/image/back.svg

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="19px" height="19px" viewBox="0 0 19 19" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
+        <g id="课件播放(老师)" transform="translate(-40.000000, -13.000000)" stroke="#FFFFFF" stroke-width="2">
+            <g id="图标/通用/返回" transform="translate(40.000000, 13.000000)">
+                <polyline id="Stroke-1" points="13.5 18 5 9.5 13.5 1"></polyline>
+            </g>
+        </g>
+    </g>
+</svg>

+ 58 - 0
src/views/coursewarePlay/index.module.less

@@ -0,0 +1,58 @@
+.coursewarePlay {
+  position: relative;
+  min-height: 100vh;
+  background-color: rgba(89, 98, 126, 0.2);
+}
+.backBtn {
+  position: absolute;
+  left: 40px;
+  top: 10Px;
+  color: #fff;
+  width: 40Px;
+  height: 26Px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  z-index: 10;
+}
+.menu {
+  position: absolute;
+  top: 10Px;
+  left: 140Px;
+  right: 84Px;
+  z-index: 10;
+  :global {
+    .van-tabs__content,
+    .van-tabs__line {
+      display: none;
+    }
+    .van-tabs__wrap {
+      height: 26Px;
+    }
+    .van-tabs__nav {
+      padding: 0;
+      background: rgba(255, 128, 87, 0.5);
+      .van-tab {
+        color: #fff;
+        font-size: 14Px;
+      }
+      .van-tab--active.van-tab {
+        background: #ff8057;
+      }
+    }
+  }
+}
+.videoItem{
+    width: 100vw;
+    height: 100vh;
+    --plyr-color-main: var(--van-primary);
+    video{
+        width: 100%;
+        height: 100%;
+    }
+    :global{
+        .plyr{
+            height: 100%;
+        }
+    }
+}

+ 87 - 0
src/views/coursewarePlay/index.tsx

@@ -0,0 +1,87 @@
+import { Icon, Swipe, SwipeItem, Tab, Tabs } from 'vant'
+import { defineComponent, onMounted, reactive } from 'vue'
+import iconBack from './image/back.svg'
+import styles from './index.module.less'
+import Plyr from 'plyr'
+import 'plyr/dist/plyr.css'
+
+export default defineComponent({
+  name: 'CoursewarePlay',
+  setup() {
+    onMounted(() => {
+      const nodeList = document.querySelectorAll('.player')
+      console.log('🚀 ~ nodeList', nodeList)
+    //   const player = Plyr.setup('.player', {
+    //     debug: false,
+    //     ratio: '16:9'
+    //   })
+      const player = new Plyr('#player1', {
+        autoplay: true
+      })
+      setTimeout(() => {
+        // player.togglePlay()
+      }, 1000)
+      console.log('🚀 ~ player', player)
+    })
+    return () => (
+      <div class={styles.coursewarePlay}>
+        <div class={styles.backBtn}>
+          <Icon name={iconBack} />
+          返回
+        </div>
+        <div class={styles.menu}>
+          <Tabs>
+            <Tab title="前言"></Tab>
+            <Tab title="嘴形+长振"></Tab>
+            <Tab title="吐音"></Tab>
+            <Tab title="号角发音"></Tab>
+            <Tab title="发音练习"></Tab>
+            <Tab title="结束语"></Tab>
+          </Tabs>
+        </div>
+        <Swipe vertical>
+          <SwipeItem>
+            <div class={styles.videoItem}>
+              <video class="player" id='player1' autoplay>
+                <source
+                  src="https://daya.ks3-cn-beijing.ksyuncs.com/12/1672210184164.mp4"
+                  type="video/mp4"
+                />
+              </video>
+            </div>
+          </SwipeItem>
+          <SwipeItem>
+            <div class={styles.videoItem}>
+              <video class="player" controls>
+                <source
+                  src="https://daya.ks3-cn-beijing.ksyuncs.com/12/1672210184164.mp4"
+                  type="video/mp4"
+                />
+              </video>
+            </div>
+          </SwipeItem>
+          <SwipeItem>
+            <div class={styles.videoItem}>
+              <video class="player" controls>
+                <source
+                  src="https://daya.ks3-cn-beijing.ksyuncs.com/12/1672210184164.mp4"
+                  type="video/mp4"
+                />
+              </video>
+            </div>
+          </SwipeItem>
+          <SwipeItem>
+            <div class={styles.videoItem}>
+              <video class="player" controls>
+                <source
+                  src="https://daya.ks3-cn-beijing.ksyuncs.com/12/1672210184164.mp4"
+                  type="video/mp4"
+                />
+              </video>
+            </div>
+          </SwipeItem>
+        </Swipe>
+      </div>
+    )
+  }
+})

+ 1 - 1
src/school/exercise-record/exercis-detail.module.less → src/views/exercise-record/exercis-detail.module.less

@@ -1,4 +1,4 @@
-@img: '../images';
+@img: '@/school/images';
 
 .topWrap {
   height: 316px;

+ 5 - 3
src/school/exercise-record/exercis-detail.tsx → src/views/exercise-record/exercis-detail.tsx

@@ -18,14 +18,16 @@ import { defineComponent, onMounted, reactive, ref } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 import styles from './exercis-detail.module.less'
 import request from '@/helpers/request'
-import questIcon from '../images/quest-icon.png'
+import questIcon from '@/school/images/quest-icon.png'
 import defaultIcon from '@/school/images/default-icon.jpg'
+import { state as globalState } from '@/state'
 
 export default defineComponent({
   name: 'exercis-detail',
   setup() {
     const router = useRouter()
     const route = useRoute()
+    const platformApi = ref(globalState.platformApi)
     const state = reactive({
       showPopoverTime: false,
       showPopoverOrchestra: false,
@@ -37,7 +39,7 @@ export default defineComponent({
       ],
       id: route.query.id
     })
-    console.log(route.query)
+
     const forms = reactive({
       practiceMonth: route.query.practiceMonth
         ? route.query.practiceMonth
@@ -70,7 +72,7 @@ export default defineComponent({
         refreshing.value = false
       }
       try {
-        const res = await request.post('/api-school/musicPracticeRecord/page', {
+        const res = await request.post(`${platformApi.value}/musicPracticeRecord/page`, {
           data: { ...forms }
         })
 

+ 0 - 0
src/school/exercise-record/index.module.less → src/views/exercise-record/index.module.less


+ 4 - 3
src/school/exercise-record/index.tsx → src/views/exercise-record/index.tsx

@@ -25,6 +25,7 @@ import request from '@/helpers/request'
 export default defineComponent({
   name: 'exercise-record',
   setup() {
+    const platformApi = ref(globalState.platformApi)
     const router = useRouter()
     const state = reactive({
       showPopoverTime: false,
@@ -77,7 +78,7 @@ export default defineComponent({
           list.value = []
           refreshing.value = false
         }
-        const res = await request.post('/api-school/student/page', {
+        const res = await request.post(`${platformApi.value}/student/page`, {
           data: { ...forms }
         })
 
@@ -152,7 +153,7 @@ export default defineComponent({
       //   })
       //   .join(',')
       try {
-        const res = await request.post('/api-school/orchestra/page', {
+        const res = await request.post(`${platformApi.value}/orchestra/page`, {
           data: { page: 1, rows: 9999 }
         })
         state.actions = res.data.rows.map((item) => {
@@ -170,7 +171,7 @@ export default defineComponent({
 
     const getSubjects = async () => {
       try {
-        const res = await request.post('/api-school/subject/page', {
+        const res = await request.post(`${platformApi.value}/subject/page`, {
           data: { page: 1, rows: 9999 }
         })
         state.subjects = res.data.rows.map((item) => {

+ 0 - 0
src/school/exercise-record/modals/detail-item.module.less → src/views/exercise-record/modals/detail-item.module.less


+ 5 - 5
src/school/exercise-record/modals/detail-item.tsx → src/views/exercise-record/modals/detail-item.tsx

@@ -5,11 +5,11 @@ import msgIcon from '@/school/images/msg-icon.png'
 import sendmsgIcon from '@/school/images/sendmsg-icon.png'
 import phoneIcon from '@/school/images/phone-icon.png'
 import { Icon, ActionSheet } from 'vant'
-import Image1 from '../../images/Image1.png'
-import Image2 from '../../images/Image2.png'
-import Image3 from '../../images/Image3.png'
-import Image4 from '../../images/Image4.png'
-import Image5 from '../../images/Image5.png'
+import Image1 from '@/school/images/Image1.png'
+import Image2 from '@/school/images/Image2.png'
+import Image3 from '@/school/images/Image3.png'
+import Image4 from '@/school/images/Image4.png'
+import Image5 from '@/school/images/Image5.png'
 
 const scoreInfos: any = {
   1: {

+ 0 - 0
src/school/exercise-record/modals/student-item.module.less → src/views/exercise-record/modals/student-item.module.less


+ 0 - 0
src/school/exercise-record/modals/student-item.tsx → src/views/exercise-record/modals/student-item.tsx


+ 1 - 0
src/views/layout/login.tsx

@@ -65,6 +65,7 @@ export default defineComponent({
           client_id: state.clientId[state.platformType],
           client_secret: state.clientId[state.platformType]
         }
+          console.log("🚀 ~ state.clientId", state.clientId,state.platformType)
         if (this.loginType === 'PWD') {
           forms.password = this.password
           forms.loginType = 'PASSWORD'

+ 13 - 0
src/views/lessonCourseware/image/look.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="13px" height="13px" viewBox="0 0 13 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <title>切片</title>
+    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="选择课件" transform="translate(-282.000000, -159.000000)">
+            <g id="锁备份" transform="translate(282.000000, 159.000000)">
+                <circle id="椭圆形" fill="#FFFFFF" cx="6.5" cy="8" r="1"></circle>
+                <rect id="矩形" stroke="#FFFFFF" stroke-width="1.2" x="1.6" y="4.1" width="9.8" height="7.8" rx="2"></rect>
+                <path d="M4.5,4 L4.5,3 C4.5,1.8954305 5.3954305,1 6.5,1 C7.6045695,1 8.5,1.8954305 8.5,3 L8.5,4 L8.5,4" id="路径" stroke="#FFFFFF" stroke-width="1.2"></path>
+            </g>
+        </g>
+    </g>
+</svg>

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

@@ -0,0 +1,65 @@
+.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;
+    .cover {
+      position: absolute;
+      left: 0;
+      right: 0;
+      top: 0;
+      bottom: 0;
+      z-index: -1;
+      display: block;
+      width: 100%;
+      height: 100%;
+      object-fit: cover;
+    }
+    .title {
+      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: 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;
+    }
+  }
+}

+ 61 - 0
src/views/lessonCourseware/index.tsx

@@ -0,0 +1,61 @@
+import request from '@/helpers/request'
+import { state } from '@/state'
+import { Empty, Grid, GridItem, Icon } from 'vant'
+import { defineComponent, onMounted, reactive } from 'vue'
+import styles from './index.module.less'
+import iconLook from './image/look.svg'
+import { useRoute } from 'vue-router'
+export default defineComponent({
+  name: 'lessonCourseware',
+  setup() {
+    const route = useRoute()
+    const data = reactive({
+      list: [] as any
+    })
+    const getList = async () => {
+      try {
+        const res: any = await request.post(state.platformApi + '/courseSchedule/myCourseware')
+        if (Array.isArray(res?.data?.rows)) {
+          data.list = res.data.rows
+        }
+      } catch (error) {}
+    }
+    onMounted(() => {
+      getList()
+    })
+    const handleClick = (item: any) => {
+        if (route.query.code === 'select'){
+            console.log('这里是选择课件')
+        }
+    }
+    return () => (
+      <div style={{ paddingTop: '14px' }}>
+        <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.courseNum}课</div>
+                  {item.delFlag && (
+                    <div class={styles.look}>
+                      <Icon name={iconLook} /> 未解锁
+                    </div>
+                  )}
+                </div>
+              </GridItem>
+            )
+          })}
+          {!data.list.length && (
+            <Empty description='空空如也' />
+          )}
+        </Grid>
+      </div>
+    )
+  }
+})