mo пре 2 година
родитељ
комит
b0192214fe

+ 9 - 10
src/constant/index.ts

@@ -78,14 +78,14 @@ export const attType = {
 
 // 课程类型
 export const courseEmnu = {
-  PERCUSSION_SINGLE: '打击乐-单技',
-  FLUTE_SINGLE: '长笛-单技',
-  SAX_SINGLE: '萨克斯-单技',
-  CLARINET_SINGLE: '单簧管-单技',
-  TRUMPET_SINGLE: '小号-单技',
-  TROMBONE_SINGLE: '长号-单技',
-  HORN_SINGLE: '圆号-单技',
-  BARITONE_TUBA_SINGLE: '上低音号-大号-单技',
+  PERCUSSION_SINGLE: '打击乐',
+  FLUTE_SINGLE: '长笛',
+  SAX_SINGLE: '萨克斯',
+  CLARINET_SINGLE: '单簧管',
+  TRUMPET_SINGLE: '小号',
+  TROMBONE_SINGLE: '长号',
+  HORN_SINGLE: '圆号',
+  BARITONE_TUBA_SINGLE: '上低音号-大号',
   MUSIC_THEORY: '乐理',
   INSTRUMENTAL_ENSEMBLE: '合奏'
 }
@@ -109,7 +109,6 @@ export const schoolSystem = {
   sixYearSystem: '六年制'
 }
 
-
 // 订单状态
 export const orderType = {
   WAIT_PAY: '待支付',
@@ -120,4 +119,4 @@ export const orderType = {
   CLOSED: '订单关闭',
   REFUNDING: '退款中',
   REFUNDED: '已退款'
-}
+}

+ 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'),
@@ -56,42 +57,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

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

@@ -45,6 +45,14 @@ export default [
         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>

+ 1 - 1
src/state.ts

@@ -9,7 +9,7 @@ export const state = reactive({
     status: 'init' as status,
     data: {} as any
   },
-  platformType: 'SCHOOL' as 'STUDENT' | 'TEACHER' | 'SCHOOL',
+  platformType: 'STUDENT' as 'STUDENT' | 'TEACHER' | 'SCHOOL',
   clientId: {
     STUDENT: 'jmedu-student',
     TEACHER: 'jmedu-teacher',

+ 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>
+      </>
+    )
+  }
+})

+ 4 - 2
src/views/exercise-record/exercis-detail.tsx

@@ -20,12 +20,14 @@ import styles from './exercis-detail.module.less'
 import request from '@/helpers/request'
 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 - 1
src/views/exercise-record/index.tsx

@@ -25,7 +25,6 @@ import request from '@/helpers/request'
 export default defineComponent({
   name: 'exercise-record',
   setup() {
-    console.log(globalState.platformApi, 'globalState')
     const platformApi = ref(globalState.platformApi)
     const router = useRouter()
     const state = reactive({