Browse Source

到伴学指导考勤

1
mo 2 years ago
parent
commit
b848333217
31 changed files with 1793 additions and 368 deletions
  1. 176 175
      src/components/o-header/index.tsx
  2. 93 71
      src/constant/index.ts
  3. 2 1
      src/helpers/request.ts
  4. 16 0
      src/router/routes-school.ts
  5. 250 0
      src/school/attendance/components/attend-student.tsx
  6. 248 0
      src/school/attendance/components/attend-teacher.tsx
  7. 9 0
      src/school/attendance/components/attent-student.module.less
  8. BIN
      src/school/attendance/images/clock-icon.png
  9. BIN
      src/school/attendance/images/error-icon.png
  10. BIN
      src/school/attendance/images/go-icon.png
  11. BIN
      src/school/attendance/images/pass-icon.png
  12. BIN
      src/school/attendance/images/san-icon.png
  13. BIN
      src/school/attendance/images/success-icon.png
  14. 14 0
      src/school/attendance/index.module.less
  15. 31 0
      src/school/attendance/index.tsx
  16. 73 0
      src/school/attendance/modals/student-cell.module.less
  17. 51 0
      src/school/attendance/modals/student-cell.tsx
  18. 79 0
      src/school/attendance/modals/studentAtt-item.module.less
  19. 50 0
      src/school/attendance/modals/studentAtt-item.tsx
  20. 125 0
      src/school/attendance/modals/teacherAtt-item.module.less
  21. 85 0
      src/school/attendance/modals/teacherAtt-item.tsx
  22. 159 0
      src/school/attendance/student-att-day.tsx
  23. 1 1
      src/school/exercise-record/exercis-detail.module.less
  24. 78 32
      src/school/exercise-record/exercis-detail.tsx
  25. 135 68
      src/school/exercise-record/index.tsx
  26. 8 0
      src/school/exercise-record/modals/detail-item.module.less
  27. 8 6
      src/school/exercise-record/modals/detail-item.tsx
  28. 97 13
      src/school/exercise-record/modals/student-item.tsx
  29. BIN
      src/school/images/beto-icon.png
  30. BIN
      src/school/images/good-icon.png
  31. 5 1
      vite.config.ts

+ 176 - 175
src/components/o-header/index.tsx

@@ -1,175 +1,176 @@
-import { postMessage } from '@/helpers/native-message'
-import { browser } from '@/helpers/utils'
-import { NavBar } from 'vant'
-import { defineComponent, PropType, Teleport } from 'vue'
-import styles from './index.module.less'
-
-type backIconColor = 'black' | 'white'
-
-export default defineComponent({
-  name: 'o-header',
-  props: {
-    title: String,
-    isBack: {
-      type: Boolean,
-      default: false
-    },
-    backIconColor: {
-      // 返回按钮颜色
-      type: String as PropType<backIconColor>,
-      default: 'black'
-    },
-    isFixed: {
-      type: Boolean,
-      default: true
-    },
-    styleName: {
-      type: Object,
-      default: () => ({})
-    },
-    titleClass: String,
-    background: {
-      type: String,
-      default: 'white'
-    },
-    color: {
-      type: String,
-      default: '#323233'
-    },
-    rightText: String,
-    onClickRight: {
-      type: Function,
-      default: () => {}
-    },
-    border: {
-      type: Boolean,
-      default: true
-    },
-    onHeaderBack: {
-      // 头部高度设置后返回
-      type: Function,
-      default: () => {}
-    }
-  },
-  watch: {
-    backIconColor() {
-      // 设置返回按钮颜色
-      postMessage({
-        api: 'backIconChange',
-        content: { iconStyle: this.backIconColor }
-      })
-    }
-  },
-  data() {
-    return {
-      headerTitle: null as any,
-      navBarHeight: 0, // 顶部导航栏高度
-      titleHeight: 44 // 顶部导航高度(默认44px)
-    }
-  },
-  mounted() {
-    this.headerTitle = this.title || this.$route.meta.title
-    this.navBarInit(() => {
-      this.onHeaderBack && this.onHeaderBack()
-    })
-  },
-  unmounted() {
-    // 设置是否显示导航栏 0 显示 1 不显示
-    postMessage({ api: 'setBarStatus', content: { status: 1 } })
-    // 设置返回按钮颜色
-    postMessage({
-      api: 'backIconChange',
-      content: { iconStyle: 'black' as backIconColor }
-    })
-  },
-  methods: {
-    navBarInit(callBack?: Function) {
-      // 设置是否显示导航栏 0 显示 1 不显示
-      postMessage({ api: 'setBarStatus', content: { status: 0 } })
-      // 设置返回按钮颜色
-      postMessage({
-        api: 'backIconChange',
-        content: { iconStyle: this.backIconColor || 'black' }
-      })
-
-      const sNavHeight = sessionStorage.getItem('navHeight')
-      const sTitleHeight = sessionStorage.getItem('titleHeight')
-      if (sNavHeight && sTitleHeight) {
-        this.navBarHeight = Number(sNavHeight)
-        callBack && callBack()
-      } else {
-        postMessage({ api: 'getNavHeight' }, (res) => {
-          const { content } = res as any
-          const dpi = content.dpi || 2
-          if (content.navHeight) {
-            const navHeight = content.navHeight / dpi
-            sessionStorage.setItem('navHeight', String(navHeight))
-            this.navBarHeight = navHeight
-          }
-          if (content.titleHeight) {
-            // 导航栏的高度
-            const titleHeight = content.titleHeight / dpi
-            sessionStorage.setItem('titleHeight', String(titleHeight))
-            this.titleHeight = titleHeight
-          }
-          callBack && callBack()
-        })
-      }
-      !browser().isApp && callBack && callBack()
-    },
-    onClickLeft() {
-      this.$router.back()
-    },
-    clickRight() {
-      this.onClickRight && this.onClickRight()
-    }
-  },
-  render() {
-    return (
-      <div>
-        {this.$slots.content ? (
-          <div
-            style={{
-              paddingTop: `${this.navBarHeight}px`,
-              background: this.background
-            }}
-            class={styles.headerSection}
-          >
-            {this.$slots.content(this.navBarHeight)}
-          </div>
-        ) : (
-          <>
-            <div
-              // style={{ paddingTop: `${this.navBarHeight}px` }}
-              style={{
-                minHeight: `calc(var(--van-nav-bar-height) + ${this.navBarHeight}px)`
-              }}
-              class={styles.headerSection}
-            >
-              <NavBar
-                title={this.headerTitle}
-                class={[styles.colHeader]}
-                style={{
-                  background: this.background,
-                  color: this.color,
-                  paddingTop: `${this.navBarHeight}px`,
-                  zIndex: 99
-                }}
-                left-arrow={this.isBack}
-                rightText={this.rightText}
-                fixed={this.isFixed}
-                border={this.border}
-                onClick-right={this.clickRight}
-                onClick-left={this.onClickLeft}
-                v-slots={{
-                  right: () => (this.$slots.right && this.$slots.right()) || this.rightText
-                }}
-              ></NavBar>
-            </div>
-            {this.$slots.default ? this.$slots.default() : null}
-          </>
-        )}
-      </div>
-    )
-  }
-})
+import { postMessage } from '@/helpers/native-message'
+import { browser } from '@/helpers/utils'
+import { NavBar } from 'vant'
+import { defineComponent, PropType, Teleport } from 'vue'
+import styles from './index.module.less'
+
+type backIconColor = 'black' | 'white'
+
+export default defineComponent({
+  name: 'o-header',
+  props: {
+    title: String,
+    isBack: {
+      type: Boolean,
+      default: false
+    },
+    backIconColor: {
+      // 返回按钮颜色
+      type: String as PropType<backIconColor>,
+      default: 'black'
+    },
+    isFixed: {
+      type: Boolean,
+      default: true
+    },
+    styleName: {
+      type: Object,
+      default: () => ({})
+    },
+    titleClass: String,
+    background: {
+      type: String,
+      default: 'white'
+    },
+    color: {
+      type: String,
+      default: '#323233'
+    },
+    rightText: String,
+    onClickRight: {
+      type: Function,
+      default: () => {}
+    },
+    border: {
+      type: Boolean,
+      default: true
+    },
+    onHeaderBack: {
+      // 头部高度设置后返回
+      type: Function,
+      default: () => {}
+    }
+  },
+  watch: {
+    backIconColor() {
+      // 设置返回按钮颜色
+      postMessage({
+        api: 'backIconChange',
+        content: { iconStyle: this.backIconColor }
+      })
+    }
+  },
+  data() {
+    return {
+      headerTitle: null as any,
+      navBarHeight: 0, // 顶部导航栏高度
+      titleHeight: 44 // 顶部导航高度(默认44px)
+    }
+  },
+  mounted() {
+    this.headerTitle = this.title || this.$route.meta.title
+    this.navBarInit(() => {
+      this.onHeaderBack && this.onHeaderBack()
+    })
+  },
+  unmounted() {
+    // 设置是否显示导航栏 0 显示 1 不显示
+    postMessage({ api: 'setBarStatus', content: { status: 1 } })
+    // 设置返回按钮颜色
+    postMessage({
+      api: 'backIconChange',
+      content: { iconStyle: 'black' as backIconColor }
+    })
+  },
+  methods: {
+    navBarInit(callBack?: Function) {
+      // 设置是否显示导航栏 0 显示 1 不显示
+      postMessage({ api: 'setBarStatus', content: { status: 0 } })
+      // 设置返回按钮颜色
+      postMessage({
+        api: 'backIconChange',
+        content: { iconStyle: this.backIconColor || 'black' }
+      })
+
+      const sNavHeight = sessionStorage.getItem('navHeight')
+      const sTitleHeight = sessionStorage.getItem('titleHeight')
+      if (sNavHeight && sTitleHeight) {
+        this.navBarHeight = Number(sNavHeight)
+        callBack && callBack()
+      } else {
+        postMessage({ api: 'getNavHeight' }, (res) => {
+          const { content } = res as any
+          const dpi = content.dpi || 2
+          if (content.navHeight) {
+            const navHeight = content.navHeight / dpi
+            sessionStorage.setItem('navHeight', String(navHeight))
+            this.navBarHeight = navHeight
+          }
+          if (content.titleHeight) {
+            // 导航栏的高度
+            const titleHeight = content.titleHeight / dpi
+            sessionStorage.setItem('titleHeight', String(titleHeight))
+            this.titleHeight = titleHeight
+          }
+          callBack && callBack()
+        })
+      }
+      !browser().isApp && callBack && callBack()
+    },
+    onClickLeft() {
+      this.$router.back()
+    },
+    clickRight() {
+      this.onClickRight && this.onClickRight()
+    }
+  },
+  render() {
+    return (
+      <div>
+        {this.$slots.content ? (
+          <div
+            style={{
+              paddingTop: `${this.navBarHeight}px`,
+              background: this.background
+            }}
+            class={styles.headerSection}
+          >
+            {this.$slots.content(this.navBarHeight)}
+          </div>
+        ) : (
+          <>
+            <div
+              // style={{ paddingTop: `${this.navBarHeight}px` }}
+              style={{
+                minHeight: `calc(var(--van-nav-bar-height) + ${this.navBarHeight}px)`
+              }}
+              class={styles.headerSection}
+            >
+              <NavBar
+                title={this.headerTitle}
+                class={[styles.colHeader]}
+                style={{
+                  background: this.background,
+                  color: this.color,
+                  paddingTop: `${this.navBarHeight}px`,
+                  zIndex: 99
+                }}
+                left-arrow={this.isBack}
+                rightText={this.rightText}
+                fixed={this.isFixed}
+                border={this.border}
+                onClick-right={this.clickRight}
+                onClick-left={this.onClickLeft}
+                v-slots={{
+                  right: () => (this.$slots.right && this.$slots.right()) || this.rightText,
+                  title: () => (this.$slots.title && this.$slots.title()) || this.headerTitle
+                }}
+              ></NavBar>
+            </div>
+            {this.$slots.default ? this.$slots.default() : null}
+          </>
+        )}
+      </div>
+    )
+  }
+})

+ 93 - 71
src/constant/index.ts

@@ -1,71 +1,93 @@
-export const goodsType = {
-  LIVE: '直播课',
-  PRACTICE: '陪练课',
-  VIDEO: '视频课',
-  VIP: '开通会员',
-  MUSIC: '单曲点播',
-  ALBUM: '专辑购买',
-  PIANO_ROOM: '琴房时长充值',
-  ACTI_REGIST: '活动报名'
-}
-
-export const orderType = {
-  WAIT_PAY: '待支付',
-  PAYING: '支付中',
-  PAID: '已付款',
-  CLOSE: '已关闭',
-  FAIL: '支付失败'
-}
-
-export const returnType = {
-  DOING: '审核中',
-  PASS: '通过',
-  UNPASS: '不通过'
-}
-
-export const levelMember = {
-  BEGINNER: '入门级',
-  ADVANCED: '进阶级',
-  PERFORMER: '大师级'
-}
-
-export const memberType = {
-  MONTH: '月度会员',
-  QUARTERLY: '季度会员',
-  YEAR_HALF: '半年会员',
-  YEAR: '年度会员'
-}
-
-export const courseType = {
-  NOT_START: '未开始',
-  ING: '进行中',
-  COMPLETE: '已完成',
-  CANCEL: '已取消'
-}
-
-export const bizStatus = {
-  PRACTICE: '陪练课',
-  LIVE: '直播课',
-  VIDEO: '视频课',
-  MUSIC: '乐谱',
-  WITHDRAWAL: '提现',
-  LIVE_SHARE: '直播课分润',
-  VIDEO_SHARE: '视频课分润',
-  MUSIC_SHARE: '乐谱分润',
-  VIP_SHARE: '会员分润',
-  MALL_SHARE: '商品分润'
-}
-
-export const postStatus = {
-  WAIT: '待入账',
-  FROZEN: '冻结入账 ',
-  RECORDED: '已入账 ',
-  CANCEL: '取消'
-}
-
-// 评测难度
-export const difficulty = {
-  BEGINNER: '入门级',
-  ADVANCED: '进阶级',
-  PERFORMER: '大师级'
-}
+export const goodsType = {
+  LIVE: '直播课',
+  PRACTICE: '陪练课',
+  VIDEO: '视频课',
+  VIP: '开通会员',
+  MUSIC: '单曲点播',
+  ALBUM: '专辑购买',
+  PIANO_ROOM: '琴房时长充值',
+  ACTI_REGIST: '活动报名'
+}
+
+export const orderType = {
+  WAIT_PAY: '待支付',
+  PAYING: '支付中',
+  PAID: '已付款',
+  CLOSE: '已关闭',
+  FAIL: '支付失败'
+}
+
+export const returnType = {
+  DOING: '审核中',
+  PASS: '通过',
+  UNPASS: '不通过'
+}
+
+export const levelMember = {
+  BEGINNER: '入门级',
+  ADVANCED: '进阶级',
+  PERFORMER: '大师级'
+}
+
+export const memberType = {
+  MONTH: '月度会员',
+  QUARTERLY: '季度会员',
+  YEAR_HALF: '半年会员',
+  YEAR: '年度会员'
+}
+
+export const courseType = {
+  NOT_START: '未开始',
+  ING: '进行中',
+  COMPLETE: '已完成',
+  CANCEL: '已取消'
+}
+
+export const bizStatus = {
+  PRACTICE: '陪练课',
+  LIVE: '直播课',
+  VIDEO: '视频课',
+  MUSIC: '乐谱',
+  WITHDRAWAL: '提现',
+  LIVE_SHARE: '直播课分润',
+  VIDEO_SHARE: '视频课分润',
+  MUSIC_SHARE: '乐谱分润',
+  VIP_SHARE: '会员分润',
+  MALL_SHARE: '商品分润'
+}
+
+export const postStatus = {
+  WAIT: '待入账',
+  FROZEN: '冻结入账 ',
+  RECORDED: '已入账 ',
+  CANCEL: '取消'
+}
+
+// 评测难度
+export const difficulty = {
+  BEGINNER: '入门级',
+  ADVANCED: '进阶级',
+  PERFORMER: '大师级'
+}
+
+// 考勤状态
+export const attType = {
+  NORMAL: '正常',
+  LATE: '迟到',
+  LEAVE: '请假',
+  TRUANCY: '旷课'
+}
+
+// 课程类型
+export const courseEmnu = {
+  PERCUSSION_SINGLE: '打击乐-单技',
+  FLUTE_SINGLE: '长笛-单技',
+  SAX_SINGLE: '萨克斯-单技',
+  CLARINET_SINGLE: '单簧管-单技',
+  TRUMPET_SINGLE: '小号-单技',
+  TROMBONE_SINGLE: '长号-单技',
+  HORN_SINGLE: '圆号-单技',
+  BARITONE_TUBA_SINGLE: '上低音号-大号-单技',
+  MUSIC_THEORY: '乐理',
+  INSTRUMENTAL_ENSEMBLE: '合奏'
+}

+ 2 - 1
src/helpers/request.ts

@@ -62,6 +62,7 @@ request.interceptors.request.use(
       options: {
         ...options,
         params: cleanDeep(options.params),
+        data: cleanDeep(options.data),
         headers: {
           ...options.headers,
           ...authHeaders
@@ -73,7 +74,7 @@ request.interceptors.request.use(
 )
 
 request.interceptors.response.use(
-  async res => {
+  async (res) => {
     toast = setTimeout(() => {
       closeToast()
     }, 100)

+ 16 - 0
src/router/routes-school.ts

@@ -82,6 +82,22 @@ export default [
         meta: {
           title: '测评详情'
         }
+      },
+      {
+        path: '/attendance',
+        name: 'attendance',
+        component: () => import('@/school/attendance/index'),
+        meta: {
+          title: '考勤管理'
+        }
+      },
+      {
+        path: '/student-att-day',
+        name: 'student-att-day',
+        component: () => import('@/school/attendance/student-att-day'),
+        meta: {
+          title: '考勤详情'
+        }
       }
     ]
   },

+ 250 - 0
src/school/attendance/components/attend-student.tsx

@@ -0,0 +1,250 @@
+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
+} from 'vant'
+import { defineComponent, reactive, ref, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+import styles from './attent-student.module.less'
+import request from '@/helpers/request'
+import { state as globalState } from '@/state'
+import StudentAttItem from '../modals/studentAtt-item'
+export default defineComponent({
+  name: 'attend-student',
+  setup() {
+    const router = useRouter()
+    const state = reactive({
+      showPopoverTime: false,
+      showPopoverOrchestra: false,
+      showPopoverSubject: false,
+      actions: [],
+      subjects: [] 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] + '月',
+      orchestraId: '',
+      orchestraName: '全部乐团',
+      subjectId: '',
+      subjectName: '全部声部',
+      page: 1,
+      rows: 20
+    })
+    const minDate = ref(new Date(dayjs().subtract(5, 'year').format('YYYY-MM-DD')))
+    const maxDate = ref(new Date(dayjs().add(5, '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 () => {
+      console.log('getList')
+      loading.value = true
+      try {
+        if (refreshing.value) {
+          // forms.page = 1
+          list.value = []
+          refreshing.value = false
+        }
+
+        const res = await request.post(
+          '/api-school/courseScheduleStudentAttendance/studentAttendance',
+          {
+            params: { ...forms }
+          }
+        )
+
+        if (list.value.length > 0 && res.data.pages === 1) {
+          return
+        }
+
+        // forms.page = res.data.current + 1
+        list.value = res.data
+        // showContact.value = list.value.length > 0
+        // console.log(showContact.value, ' showContact.value ')
+        loading.value = false
+
+        finished.value = true
+      } catch (e: any) {
+        // console.log(e, 'e')
+        const message = e.message
+        showToast(message)
+        showContact.value = false
+        finished.value = true
+      }
+    }
+
+    const checkTimer = (val: any) => {
+      forms.time = val.selectedValues[0] + val.selectedValues[1]
+      forms.timeName = val.selectedValues[0] + '年' + val.selectedValues[1] + '月'
+      state.showPopoverTime = false
+      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.subjectId = val.value
+      forms.subjectName = val.name
+      console.log(val, forms)
+      refreshing.value = true
+      getList()
+    }
+    const getOrchestraList = async () => {
+      const schoolId = globalState.user.data.schoolInfos
+        .map((item) => {
+          return item.id
+        })
+        .join(',')
+      try {
+        const res = await request.post('/api-school/orchestra/page', {
+          params: { page: 1, rows: 9999, schoolId }
+        })
+        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)
+      }
+    }
+
+    const getSubjects = async () => {
+      try {
+        const res = await request.post('/api-school/subject/page', {
+          params: { 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(() => {
+      getSubjects()
+      getOrchestraList()
+      getList()
+    })
+    const onRefresh = () => {
+      finished.value = false
+      // 重新加载数据
+      // 将 loading 设置为 true,表示处于加载状态
+      loading.value = true
+      getList()
+    }
+    return () => (
+      <>
+        {/* <OSticky position="top" background="#F8F8F8"> */}
+
+        <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.subjectName}
+              <Icon name={state.showPopoverSubject ? 'arrow-up' : 'arrow-down'} />
+            </div>
+          </div>
+        </div>
+        {/* </OSticky> */}
+
+        {}
+        <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) => (
+              <StudentAttItem item={item}></StudentAttItem>
+            ))}
+          </List>
+        </PullRefresh>
+
+        <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.subjects}
+          onSelect={checkSubject}
+        ></ActionSheet>
+      </>
+    )
+  }
+})

+ 248 - 0
src/school/attendance/components/attend-teacher.tsx

@@ -0,0 +1,248 @@
+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
+} from 'vant'
+import { defineComponent, reactive, ref, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+import styles from './attent-student.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',
+  setup() {
+    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 minDate = ref(new Date(dayjs().subtract(5, 'year').format('YYYY-MM-DD')))
+    const maxDate = ref(new Date(dayjs().add(5, '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-school/courseSchedule/teacherAttendance', {
+          params: { ...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 || [])
+        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
+      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 () => {
+      const schoolId = globalState.user.data.schoolInfos
+        .map((item) => {
+          return item.id
+        })
+        .join(',')
+      try {
+        const res = await request.post('/api-school/orchestra/page', {
+          params: { page: 1, rows: 9999, schoolId }
+        })
+        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)
+      }
+    }
+
+    onMounted(() => {
+      getOrchestraList()
+      getList()
+      getCourseList()
+    })
+    const onRefresh = () => {
+      finished.value = false
+      // 重新加载数据
+      // 将 loading 设置为 true,表示处于加载状态
+      loading.value = true
+      getList()
+    }
+    return () => (
+      <>
+        {/* <OSticky position="top" background="#F8F8F8"> */}
+        <OSearch
+          placeholder="请输入伴学指导姓名"
+          inputBackground="white"
+          background="#f6f6f6"
+          onSearch={(val: any) => {
+            console.log(val, 'onSearch')
+            forms.keyword = val
+            refreshing.value = true
+            getList()
+          }}
+        ></OSearch>
+        <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>
+        {/* </OSticky> */}
+
+        {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>
+      </>
+    )
+  }
+})

+ 9 - 0
src/school/attendance/components/attent-student.module.less

@@ -0,0 +1,9 @@
+.chioseWrap {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: flex-start;
+  background-color: #f8f8f8;
+  color: #333;
+  font-weight: 500;
+}

BIN
src/school/attendance/images/clock-icon.png


BIN
src/school/attendance/images/error-icon.png


BIN
src/school/attendance/images/go-icon.png


BIN
src/school/attendance/images/pass-icon.png


BIN
src/school/attendance/images/san-icon.png


BIN
src/school/attendance/images/success-icon.png


+ 14 - 0
src/school/attendance/index.module.less

@@ -0,0 +1,14 @@
+.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);
+}

+ 31 - 0
src/school/attendance/index.tsx

@@ -0,0 +1,31 @@
+import OHeader from '@/components/o-header'
+import OSticky from '@/components/o-sticky'
+import { Tabs, Tab } from 'vant'
+import { defineComponent, reactive, ref } from 'vue'
+import { useRouter } from 'vue-router'
+import AttendStudent from './components/attend-student'
+import AttendTeacher from './components/attend-teacher'
+const activeName = ref('student')
+export default defineComponent({
+  name: 'school-attendance',
+  setup() {
+    const router = useRouter()
+    const state = reactive({})
+
+    return () => (
+      <>
+        <OSticky position="top" background="#F8F8F8">
+          <OHeader isBack={true}></OHeader>
+          <Tabs v-model:active={activeName.value}>
+            <Tab name="student" title="学生考勤">
+              <AttendStudent></AttendStudent>
+            </Tab>
+            <Tab name="teacher" title="伴学指导考勤">
+              <AttendTeacher></AttendTeacher>
+            </Tab>
+          </Tabs>
+        </OSticky>
+      </>
+    )
+  }
+})

+ 73 - 0
src/school/attendance/modals/student-cell.module.less

@@ -0,0 +1,73 @@
+.itemWrap {
+  background-color: #ffffff;
+  padding: 18px 0px 0 13px;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  &:nth-last-child(1) {
+    .itemRight {
+      border-bottom: none !important;
+    }
+  }
+  .itemLeft {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    padding-bottom: 18px;
+    .headerWrap {
+      width: 48px;
+      height: 48px;
+      border-radius: 50%;
+      margin-right: 10px;
+      overflow: hidden;
+      img {
+        width: 100%;
+        height: 100%;
+      }
+    }
+  }
+  .itemRight {
+    height: 48px;
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    justify-content: space-between;
+    border-bottom: 1px solid #f2f2f2;
+    padding-bottom: 18px;
+    padding-right: 13px;
+    width: 302px;
+
+    .infoWrap {
+      p {
+        font-size: 16px;
+        font-weight: 500;
+        color: #333333;
+        line-height: 22px;
+      }
+      .statusTag {
+        width: 48px;
+        height: 20px;
+        border-radius: 4px;
+        font-size: 14px;
+        font-weight: 500;
+        line-height: 20px;
+        color: #ffffff;
+        text-align: center;
+      }
+      .LATE {
+        background-color: #8f80ff;
+      }
+      .LEAVE {
+        background-color: #64a9ff;
+      }
+      .TRUANCY {
+        background-color: #ff5c5f;
+      }
+    }
+    .msgIcon {
+      width: 24px;
+      height: 24px;
+    }
+  }
+}

+ 51 - 0
src/school/attendance/modals/student-cell.tsx

@@ -0,0 +1,51 @@
+import { defineComponent, reactive, ref } from 'vue'
+import styles from './student-cell.module.less'
+import { Icon, ActionSheet, showToast } from 'vant'
+import { useRouter } from 'vue-router'
+import defaultIcon from '@/school/images/default-icon.jpg'
+import msgIcon from '@/school/images/msg-icon.png'
+import { postMessage } from '@/helpers/native-message'
+import { attType } from '@/constant'
+
+export default defineComponent({
+  props: ['item'],
+  name: 'student-cell',
+  setup(props) {
+    const router = useRouter()
+    const gotoMsg = async () => {
+      try {
+        await postMessage({
+          api: 'joinChatGroup',
+          content: {
+            type: 'single', // single 单人 multi 多人
+            id: props.item.studentImId
+          }
+        })
+      } catch (e) {
+        showToast('发起聊天失败')
+      }
+    }
+
+    return () => (
+      <>
+        <div class={styles.itemWrap} onClick={gotoMsg}>
+          <div class={styles.itemLeft}>
+            <div class={styles.headerWrap}>
+              <img src={props.item.studentAvatar ? props.item.studentAvatar : defaultIcon} alt="" />
+            </div>
+          </div>
+          <div class={styles.itemRight}>
+            <div class={styles.infoWrap}>
+              <p>{props.item.nickName}</p>
+
+              <div class={[styles[props.item.status], styles.statusTag]}>
+                {attType[props.item.status]}
+              </div>
+            </div>
+            <img class={styles.msgIcon} src={msgIcon} alt="" />
+          </div>
+        </div>
+      </>
+    )
+  }
+})

+ 79 - 0
src/school/attendance/modals/studentAtt-item.module.less

@@ -0,0 +1,79 @@
+.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;
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+    justify-content: space-between;
+    .passWrap,
+    .goWrap {
+      padding: 11px 15px;
+      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;
+      }
+      img {
+        width: 50px;
+        height: 50px;
+        margin-right: 16px;
+      }
+    }
+    .passWrap {
+      background-color: #ddecff;
+    }
+    .goWrap {
+      background-color: #ffe1e1;
+    }
+  }
+}

+ 50 - 0
src/school/attendance/modals/studentAtt-item.tsx

@@ -0,0 +1,50 @@
+import { defineComponent, reactive, ref } from 'vue'
+import styles from './studentAtt-item.module.less'
+import clockIcon from '../images/clock-icon.png'
+import goIcon from '../images/go-icon.png'
+import passIcon from '../images/pass-icon.png'
+import { Icon, ActionSheet } from 'vant'
+import { useRouter } from 'vue-router'
+export default defineComponent({
+  props: ['item'],
+  name: 'studentAtt-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}>{props.item.time} </p>
+            </div>
+            <div class={styles.itemWrapTopRight}>
+              <Icon name="arrow"></Icon>
+            </div>
+          </div>
+          <div class={styles.itemWrapBottom}>
+            <div class={styles.passWrap}>
+              <img src={passIcon} alt="" />
+              <div>
+                <p class={styles.itemBottomMain}>{props.item.leaveNum}</p>
+                <p class={styles.itemBottomSub}>请假人数</p>
+              </div>
+            </div>
+            <div class={styles.goWrap}>
+              <img src={goIcon} alt="" />
+              <div>
+                <p class={styles.itemBottomMain}>{props.item.truancyNum}</p>
+                <p class={styles.itemBottomSub}>旷课人数</p>
+              </div>
+            </div>
+          </div>
+        </div>
+      </>
+    )
+  }
+})

+ 125 - 0
src/school/attendance/modals/teacherAtt-item.module.less

@@ -0,0 +1,125 @@
+.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;
+      .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;
+        }
+      }
+    }
+    .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;
+    }
+  }
+}

+ 85 - 0
src/school/attendance/modals/teacherAtt-item.tsx

@@ -0,0 +1,85 @@
+import { defineComponent, reactive, ref } from 'vue'
+import styles from './teacherAtt-item.module.less'
+import clockIcon from '../images/clock-icon.png'
+import errorIcon from '../images/error-icon.png'
+import successIcon from '../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}>
+              <img class={styles.headImgs} src={defaultIcon} alt="" />
+              <div class={styles.infoMsg}>
+                <p class={styles.infoMsgMain}>
+                  {props.item.classGroupName}-{props.item.teacherName}
+                </p>
+                <p class={styles.infoMsgSub}>{props.item.orchestraName}</p>
+              </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>
+      </>
+    )
+  }
+})

+ 159 - 0
src/school/attendance/student-att-day.tsx

@@ -0,0 +1,159 @@
+import OEmpty from '@/components/o-empty'
+import OHeader from '@/components/o-header'
+import OSearch from '@/components/o-search'
+import dayjs from 'dayjs'
+import {
+  DatePicker,
+  DatePickerColumnType,
+  Popup,
+  List,
+  PullRefresh,
+  ActionSheet,
+  showToast
+} from 'vant'
+import { defineComponent, reactive, ref, onMounted } from 'vue'
+import sanIcon from './images/san-icon.png'
+import { useRoute, useRouter } from 'vue-router'
+import styles from './index.module.less'
+import request from '@/helpers/request'
+import { state as globalState } from '@/state'
+import StudentCell from './modals/student-cell'
+export default defineComponent({
+  name: 'student-att-day',
+  setup() {
+    const router = useRouter()
+    const route = useRoute()
+    const myTimer = route.query.time as string
+    const state = reactive({
+      showPopoverTime: false,
+      currentDate: myTimer ? myTimer.split('-') : [2022, 12, 19],
+      currentDateName: ''
+    })
+    const forms = reactive({
+      nickName: '',
+      time: myTimer,
+      timeName:
+        myTimer.split('-')[0] + '年' + myTimer.split('-')[1] + '月' + myTimer.split('-')[2] + '日',
+      page: 1,
+      rows: 20,
+      timeType: 'DAY'
+    })
+
+    const columnsType = ref<DatePickerColumnType[]>(['year', 'month', 'day'])
+    const minDate = ref(new Date(dayjs().subtract(5, 'year').format('YYYY-MM-DD')))
+    const maxDate = ref(new Date(dayjs().add(5, '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 getList = async () => {
+      console.log('getList')
+      loading.value = true
+      try {
+        if (refreshing.value) {
+          forms.page = 1
+          list.value = []
+          refreshing.value = false
+        }
+
+        const res = await request.post('/api-school/courseScheduleStudentAttendance/page', {
+          params: { ...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 || [])
+        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 checkTimer = (val: any) => {
+      console.log(val, 'val')
+      forms.time = val.selectedValues[0] + val.selectedValues[1] + val.selectedValues[2]
+      forms.timeName =
+        val.selectedValues[0] + '年' + val.selectedValues[1] + '月' + val.selectedValues[2] + '日'
+      state.showPopoverTime = false
+      getList()
+    }
+
+    onMounted(() => {
+      getList()
+    })
+    const onRefresh = () => {
+      finished.value = false
+      // 重新加载数据
+      // 将 loading 设置为 true,表示处于加载状态
+      loading.value = true
+      getList()
+    }
+    return () => (
+      <>
+        {/* </OSticky> */}
+        <OHeader isBack={true}>
+          {{
+            title: () => (
+              <div class={styles.NavTitle} onClick={() => (state.showPopoverTime = true)}>
+                {forms.timeName}{' '}
+                <img
+                  class={[styles.sanIcons, state.showPopoverTime ? styles.isReversal : '']}
+                  src={sanIcon}
+                  alt=""
+                />
+              </div>
+            )
+          }}
+        </OHeader>
+        <OSearch
+          placeholder="请输入学生姓名"
+          inputBackground="white"
+          background="#f6f6f6"
+          onSearch={(val: any) => {
+            forms.nickName = val
+            getList()
+          }}
+          modelValue={forms.nickName}
+        ></OSearch>
+        {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) => (
+                <StudentCell item={item}></StudentCell>
+              ))}
+            </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="选择日期"
+            columnsType={columnsType.value}
+          />
+        </Popup>
+      </>
+    )
+  }
+})

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

@@ -45,7 +45,7 @@
           font-family: PingFangSC-Medium, PingFang SC;
           font-weight: 500;
           color: #ffffff;
-          line-height: 20px;
+          line-height: 24px;
           text-align: center;
         }
       }

+ 78 - 32
src/school/exercise-record/exercis-detail.tsx

@@ -1,5 +1,6 @@
 import OHeader from '@/components/o-header'
 import OSticky from '@/components/o-sticky'
+import OEmpty from '@/components/o-empty'
 import dayjs from 'dayjs'
 import {
   Icon,
@@ -13,16 +14,18 @@ import {
   Dialog
 } from 'vant'
 import DetailItem from './modals/detail-item'
-import { defineComponent, reactive, ref } from 'vue'
-import { useRouter } from 'vue-router'
+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 defaultIcon from '@/school/images/default-icon.jpg'
+
 export default defineComponent({
   name: 'exercis-detail',
   setup() {
     const router = useRouter()
+    const route = useRoute()
     const state = reactive({
       showPopoverTime: false,
       showPopoverOrchestra: false,
@@ -31,15 +34,23 @@ export default defineComponent({
         { text: '全部乐团', color: 'var(--van-primary-color)' },
         { text: '交付团' },
         { text: '晋升团' }
-      ]
+      ],
+      id: route.query.id
     })
+    console.log(route.query)
     const forms = reactive({
-      practiceMonth: state.currentDate[0] + '' + state.currentDate[1],
-      practiceMonthName: state.currentDate[0] + '年' + state.currentDate[1] + '月',
+      practiceMonth: route.query.practiceMonth
+        ? route.query.practiceMonth
+        : state.currentDate[0] + '' + state.currentDate[1],
+      practiceMonthName: route.query.practiceMonthName
+        ? route.query.practiceMonthName
+        : state.currentDate[0] + '年' + state.currentDate[1] + '月',
       orchestraId: '',
       orchestraName: '',
       page: 1,
-      rows: 20
+      rows: 20,
+      userId: route.query.id,
+      clientType: 'STUDENT'
     })
     const showTip = ref(false)
     const minDate = ref(new Date(dayjs().subtract(5, 'year').format('YYYY-MM-DD')))
@@ -49,24 +60,27 @@ export default defineComponent({
     const loading = ref(false)
     const finished = ref(false)
     const showContact = ref(false)
+    const infoDetail = ref({} as any)
     const list = ref([])
     const getList = async () => {
       loading.value = true
+      if (refreshing.value) {
+        list.value = []
+        forms.page = 1
+        refreshing.value = false
+      }
       try {
-        const res = await request.post('/api-school/student/page', {
+        const res = await request.post('/api-school/musicPracticeRecord/page', {
           data: { ...forms }
         })
-        if (refreshing.value) {
-          // list.value = []
-          refreshing.value = false
-        }
+
         if (list.value.length > 0 && res.data.pageNo === 1) {
           return
         }
 
-        showContact.value = list.value.length > 0
         forms.page = res.data.current + 1
         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
@@ -78,14 +92,32 @@ export default defineComponent({
         finished.value = true
       }
     }
+
+    const getDetail = async () => {
+      try {
+        const res = await request.get(`/api-backend/student/detail/${state.id}`)
+        console.log(res)
+        infoDetail.value = { ...res.data }
+      } catch (e: any) {
+        // console.log(e, 'e')
+        const message = e.message
+        showToast(message)
+      }
+    }
+    onMounted(() => {
+      getList()
+      getDetail()
+    })
     const onBack = () => {
       console.log('返回')
+      router.push('/exercise-record')
     }
 
     const checkTimer = (val: any) => {
       forms.practiceMonth = val.selectedValues[0] + val.selectedValues[1]
       forms.practiceMonthName = val.selectedValues[0] + '年' + val.selectedValues[1] + '月'
       state.showPopoverTime = false
+      refreshing.value = true
       getList()
     }
 
@@ -100,7 +132,12 @@ export default defineComponent({
       <>
         <OSticky position="top" background="#F8F8F8">
           <div class={styles.topWrap}>
-            <OHeader isBack={true} onHeaderBack={onBack} border={false} background={'transparent'}>
+            <OHeader
+              isBack={true}
+              // onHeaderBack={onBack}
+              border={false}
+              background={'transparent'}
+            >
               {{
                 right: () => (
                   <Icon
@@ -117,23 +154,28 @@ export default defineComponent({
             <div class={styles.topInfo}>
               <div class={styles.topInfoLeft}>
                 <div class={styles.headWrap}>
-                  <img src={defaultIcon} alt="" />
+                  <img
+                    src={infoDetail.value.avatar ? infoDetail.value.avatar : defaultIcon}
+                    alt=""
+                  />
                 </div>
                 <div class={styles.infoMsg}>
-                  <p>邓同学</p>
-                  <div class={styles.tag}>长笛</div>
+                  <p>{infoDetail.value.nickname}</p>
+                  <div class={styles.tag}>{infoDetail.value.subjectNames}</div>
                 </div>
               </div>
               <div class={styles.topInfoRight}>
                 <div class={styles.infoDay}>
                   <p class={styles.infoDayMain}>
-                    10 <span>天</span>
+                    {infoDetail.value.practiceDays ? infoDetail.value.practiceDays : 0}{' '}
+                    <span>天</span>
                   </p>
                   <p class={styles.infoDaysub}>练习天数</p>
                 </div>
                 <div class={styles.infoTime}>
                   <p class={styles.infoDayMain}>
-                    260 <span>分钟</span>
+                    {infoDetail.value.practiceTimes ? infoDetail.value.practiceTimes : 0}{' '}
+                    <span>分钟</span>
                   </p>
                   <p class={styles.infoDaysub}>练习天数</p>
                 </div>
@@ -152,7 +194,7 @@ export default defineComponent({
                 </div>
               </div>
 
-              <div style={{ padding: '12px 13px', background: 'transparent' }}>
+              {/* <div style={{ padding: '12px 13px', background: 'transparent' }}>
                 <Popover
                   v-model:show={state.showPopoverOrchestra}
                   actions={state.actions}
@@ -169,22 +211,26 @@ export default defineComponent({
                     )
                   }}
                 </Popover>
-              </div>
+              </div> */}
             </div>
           </div>
         </OSticky>
-        <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) => (
-              <DetailItem item={item} />
-            ))}
-          </List>
-        </PullRefresh>
+        {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) => (
+                <DetailItem item={item} />
+              ))}
+            </List>
+          </PullRefresh>
+        ) : (
+          <OEmpty />
+        )}
 
         <Popup v-model:show={state.showPopoverTime} position="bottom" style="{ height: '30%' }">
           <DatePicker

+ 135 - 68
src/school/exercise-record/index.tsx

@@ -1,6 +1,7 @@
 import OHeader from '@/components/o-header'
 import OSearch from '@/components/o-search'
 import OSticky from '@/components/o-sticky'
+import OEmpty from '@/components/o-empty'
 import dayjs from 'dayjs'
 import {
   Cell,
@@ -16,7 +17,8 @@ import {
   showToast
 } from 'vant'
 import StudentItem from './modals/student-item'
-import { defineComponent, reactive, ref } from 'vue'
+import { defineComponent, reactive, ref, onMounted } from 'vue'
+import { state as globalState } from '@/state'
 import { useRouter } from 'vue-router'
 import styles from './index.module.less'
 import request from '@/helpers/request'
@@ -30,11 +32,8 @@ export default defineComponent({
       showPopoverOrchestra: false,
       showPopoverSubject: false,
       showPopoverSort: false,
-      actions: [
-        { text: '全部乐团', color: 'var(--van-primary-color)' },
-        { text: '交付团' },
-        { text: '晋升团' }
-      ],
+      actions: [] as any,
+      subjects: [] as any,
       actionSorts: [
         { text: '按天数', value: 'PRACTICE_DAY' },
         { text: '按时长', value: 'PRACTICE_TIMES' }
@@ -45,9 +44,9 @@ export default defineComponent({
       practiceMonth: state.currentDate[0] + '' + state.currentDate[1],
       practiceMonthName: state.currentDate[0] + '年' + state.currentDate[1] + '月',
       orchestraId: '',
-      orchestraName: '',
+      orchestraName: '全部乐团',
       subjectId: '',
-      subjectName: '',
+      subjectName: '全部声部',
       sortType: '',
       sortTypeName: '',
       page: 1,
@@ -61,32 +60,26 @@ export default defineComponent({
     const finished = ref(false)
     const showContact = ref(false)
     const list = ref([])
-    const onDetail = (item: any) => {
-      console.log(item)
-      router.push({
-        path: '/orchestra-detail',
-        query: {
-          id: item
-        }
-      })
-    }
     const getList = async () => {
       loading.value = true
       try {
-        const res = await request.post('/api-school/student/page', {
-          data: { ...forms }
-        })
         if (refreshing.value) {
+          forms.page = 1
           list.value = []
           refreshing.value = false
         }
-        if (list.value.length > 0 && res.data.pageNo === 1) {
+        const res = await request.post('/api-school/student/page', {
+          params: { ...forms }
+        })
+
+        if (list.value.length > 0 && res.data.pages === 1) {
           return
         }
 
-        showContact.value = list.value.length > 0
         forms.page = res.data.current + 1
         list.value = list.value.concat(res.data.rows || [])
+        showContact.value = list.value.length > 0
+        console.log(showContact.value, ' showContact.value ')
         loading.value = false
 
         finished.value = res.data.current >= res.data.pages
@@ -98,22 +91,83 @@ export default defineComponent({
         finished.value = true
       }
     }
+    onMounted(() => {
+      getList()
+      getOrchestraList()
+      getSubjects()
+    })
+
     const onBack = () => {
       console.log('返回')
     }
 
+    const checkSort = (val: any) => {
+      forms.sortType = val.value
+      forms.sortTypeName = val.text
+      refreshing.value = true
+      getList()
+    }
     const checkTimer = (val: any) => {
       forms.practiceMonth = val.selectedValues[0] + val.selectedValues[1]
       forms.practiceMonthName = val.selectedValues[0] + '年' + val.selectedValues[1] + '月'
       state.showPopoverTime = false
       getList()
     }
-    const checkSort = (val: any) => {
-      forms.sortType = val.value
-      forms.sortTypeName = val.text
+    const checkOrchestra = (val: any) => {
+      forms.orchestraId = val.value
+      forms.orchestraName = val.name
+      state.showPopoverOrchestra = false
+      refreshing.value = true
       getList()
     }
 
+    const checkSubject = (val: any) => {
+      forms.subjectId = val.value
+      forms.subjectName = val.name
+      console.log(val, forms)
+      refreshing.value = true
+      getList()
+    }
+    const getOrchestraList = async () => {
+      const schoolId = globalState.user.data.schoolInfos
+        .map((item) => {
+          return item.id
+        })
+        .join(',')
+      try {
+        const res = await request.post('/api-school/orchestra/page', {
+          params: { page: 1, rows: 9999, schoolId }
+        })
+        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)
+      }
+    }
+
+    const getSubjects = async () => {
+      try {
+        const res = await request.post('/api-school/subject/page', {
+          params: { 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)
+      }
+    }
     const onRefresh = () => {
       finished.value = false
       // 重新加载数据
@@ -121,11 +175,18 @@ export default defineComponent({
       loading.value = true
       getList()
     }
+
     return () => (
       <>
         <OSticky position="top" background="#F8F8F8">
           <OHeader isBack={true} onHeaderBack={onBack}></OHeader>
-          <OSearch placeholder="学生编号" onSearch={getList}></OSearch>
+          <OSearch
+            placeholder="学生编号"
+            onSearch={() => {
+              refreshing.value = true
+              getList()
+            }}
+          ></OSearch>
           <div class={styles.chioseWrap}>
             <div style={{ padding: '12px 13px', background: '#F8F8F8' }}>
               <div
@@ -140,40 +201,26 @@ export default defineComponent({
             </div>
 
             <div style={{ padding: '12px 13px', background: '#F8F8F8' }}>
-              <Popover
-                v-model:show={state.showPopoverSubject}
-                actions={state.actions}
-                showArrow={false}
-                placement="bottom-start"
-                offset={[0, 12]}
-              >
-                {{
-                  reference: () => (
-                    <div class={styles.searchBand}>
-                      全部乐团
-                      <Icon name={state.showPopoverSubject ? 'arrow-up' : 'arrow-down'} />
-                    </div>
-                  )
+              <div
+                class={styles.searchBand}
+                onClick={() => {
+                  state.showPopoverOrchestra = true
                 }}
-              </Popover>
+              >
+                {forms.orchestraName}
+                <Icon name={state.showPopoverOrchestra ? 'arrow-up' : 'arrow-down'} />
+              </div>
             </div>
             <div style={{ padding: '12px 13px', background: '#F8F8F8' }}>
-              <Popover
-                v-model:show={state.showPopoverSubject}
-                actions={state.actions}
-                showArrow={false}
-                placement="bottom-start"
-                offset={[0, 12]}
-              >
-                {{
-                  reference: () => (
-                    <div class={styles.searchBand}>
-                      全部声部
-                      <Icon name={state.showPopoverSubject ? 'arrow-up' : 'arrow-down'} />
-                    </div>
-                  )
+              <div
+                class={styles.searchBand}
+                onClick={() => {
+                  state.showPopoverSubject = true
                 }}
-              </Popover>
+              >
+                {forms.subjectName}
+                <Icon name={state.showPopoverSubject ? 'arrow-up' : 'arrow-down'} />
+              </div>
             </div>
 
             <div style={{ padding: '12px 13px', background: '#F8F8F8' }}>
@@ -197,18 +244,22 @@ export default defineComponent({
             </div>
           </div>
         </OSticky>
-        <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) => (
-              <StudentItem item={item} />
-            ))}
-          </List>
-        </PullRefresh>
+        {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) => (
+                <StudentItem item={item} forms={forms} />
+              ))}
+            </List>
+          </PullRefresh>
+        ) : (
+          <OEmpty></OEmpty>
+        )}
 
         <Popup v-model:show={state.showPopoverTime} position="bottom" style="{ height: '30%' }">
           <DatePicker
@@ -223,6 +274,22 @@ export default defineComponent({
             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.subjects}
+          onSelect={checkSubject}
+        ></ActionSheet>
       </>
     )
   }

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

@@ -26,6 +26,9 @@
       }
     }
     .itemTopRight {
+      display: flex;
+      flex-direction: row;
+      align-items: center;
       .imgWrap {
         width: 87px;
         height: 33px;
@@ -36,6 +39,11 @@
           height: 100%;
         }
       }
+      .imgIcon {
+        font-size: 16px;
+        color: #d8d8d8;
+        margin-left: 6px;
+      }
     }
   }
   .itemBottom {

+ 8 - 6
src/school/exercise-record/modals/detail-item.tsx

@@ -15,37 +15,39 @@ export default defineComponent({
           <div class={styles.itemWrap}>
             <div class={styles.itemTop}>
               <div class={styles.itemTopLeft}>
-                <p class={styles.itemTopMain}>洋娃娃和小熊跳舞</p>
-                <p class={styles.itemTopSub}>2022-07-07 10:20:36</p>
+                <p class={styles.itemTopMain}>{props.item.musicSheetName}</p>
+                <p class={styles.itemTopSub}>{props.item.createTime}</p>
               </div>
               <div class={styles.itemTopRight}>
                 <div class={styles.imgWrap}>
                   <img src="" alt="" />
                 </div>
+                <Icon name="arrow" class={styles.imgIcon} />
               </div>
             </div>
             <div class={styles.itemBottom}>
               <div class={styles.itemBottomDot}>
                 <p class={styles.dotMain} style={{ color: '#F67146' }}>
-                  38 <span>分</span>{' '}
+                  {props.item.score} <span>分</span>{' '}
                 </p>
                 <p class={styles.dotSub}> 综合得分</p>
               </div>
               <div class={styles.itemBottomDot}>
                 <p class={styles.dotMain}>
-                  40 <span>分</span>{' '}
+                  {props.item.intonation}
+                  <span>分</span>{' '}
                 </p>
                 <p class={styles.dotSub}>音准 </p>
               </div>
               <div class={styles.itemBottomDot}>
                 <p class={styles.dotMain}>
-                  40 <span>分</span>{' '}
+                  {props.item.cadence} <span>分</span>{' '}
                 </p>
                 <p class={styles.dotSub}>节奏 </p>
               </div>
               <div class={styles.itemBottomDot}>
                 <p class={styles.dotMain}>
-                  40 <span>分</span>{' '}
+                  {props.item.integrity} <span>分</span>{' '}
                 </p>
                 <p class={styles.dotSub}>完成度 </p>
               </div>

+ 97 - 13
src/school/exercise-record/modals/student-item.tsx

@@ -1,14 +1,18 @@
-import { defineComponent, reactive, ref } from 'vue'
+import { defineComponent, reactive, ref, watch } from 'vue'
 import styles from './student-item.module.less'
 import defaultIcon from '@/school/images/default-icon.jpg'
 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 { postMessage } from '@/helpers/native-message'
+import { Icon, ActionSheet, showToast } from 'vant'
+import { useRouter } from 'vue-router'
+const myForms = ref({}) as any
 export default defineComponent({
-  props: ['item'],
+  props: ['item', 'forms'],
   name: 'student-item',
   setup(props) {
+    const router = useRouter()
     const showContact = ref(false)
     const startContact = () => {
       showContact.value = true
@@ -16,20 +20,78 @@ export default defineComponent({
     const closeSheet = () => {
       showContact.value = false
     }
+
+    watch(
+      () => props.forms,
+      (val) => {
+        myForms.value = val
+      },
+      {
+        deep: true
+      }
+    )
+    const postMsg = async () => {
+      try {
+        await postMessage({
+          api: 'joinChatGroup',
+          content: {
+            type: 'single', // single 单人 multi 多人
+            id: props.item.imUserId
+          }
+        })
+        closeSheet()
+      } catch (e) {
+        showToast('发起聊天失败')
+        closeSheet()
+      }
+    }
+
+    const callPhone = async () => {
+      try {
+        await postMessage({
+          api: 'callPhone',
+          content: {
+            phone: props.item.phone
+          }
+        })
+        closeSheet()
+      } catch (e) {
+        showToast('发起聊天失败')
+        closeSheet()
+      }
+    }
+    const gotoDetail = () => {
+      console.log(myForms.value.practiceMonth, myForms.value.practiceMonthName)
+      router.push({
+        path: '/exercis-detail',
+        query: {
+          id: props.item.id,
+          practiceMonth: myForms.value.practiceMonth,
+          practiceMonthName: myForms.value.practiceMonthName
+        }
+      })
+    }
     return () => (
       <>
         <div>
-          <div class={styles.itemWrap}>
+          <div class={styles.itemWrap} onClick={gotoDetail}>
             <div class={styles.itemTop}>
               <div class={styles.itemTopLeft}>
                 <div class={styles.headIcon}>
-                  <img src={defaultIcon} alt="" />
+                  <img src={props.item.avatar ? props.item.avatar : defaultIcon} alt="" />
                 </div>
-                <p class={styles.name}>xxx</p>
-                <div class={styles.tag}>长笛</div>
+                <p class={styles.name}>{props.item.nickname}</p>
+                <div class={styles.tag}>{props.item.subjectNames}</div>
               </div>
               <div class={styles.itemTopRight}>
-                <div class={styles.msgIcon} onClick={startContact}>
+                <div
+                  class={styles.msgIcon}
+                  onClick={(e: any) => {
+                    e.stopPropagation()
+                    e.preventDefault()
+                    startContact()
+                  }}
+                >
                   <img src={msgIcon} alt="" />
                 </div>
               </div>
@@ -37,13 +99,14 @@ export default defineComponent({
             <div class={styles.itemBottom}>
               <div class={styles.itemBottomLeft}>
                 <p class={styles.msgMain}>
-                  10 <span>天</span>
+                  {props.item.practiceDays ? props.item.practiceDays : 0} <span>天</span>
                 </p>
                 <p class={styles.msgsub}>练习天数</p>
               </div>
               <div class={styles.itemBottomRight}>
                 <p class={styles.msgMain}>
-                  10 <span>分钟</span>
+                  {props.item.practiceTimes ? props.item.practiceTimes : 0}
+                  <span>分钟</span>
                 </p>
                 <p class={styles.msgsub}>练习时长</p>
                 <Icon class={styles.arrow} name="arrow"></Icon>
@@ -61,7 +124,14 @@ export default defineComponent({
                   <span></span>
                   <p>联系方式</p>
                 </div>
-                <div class={styles.bottomTitleRight} onClick={closeSheet}>
+                <div
+                  class={styles.bottomTitleRight}
+                  onClick={(e: any) => {
+                    e.stopPropagation()
+                    e.preventDefault()
+                    closeSheet()
+                  }}
+                >
                   <Icon class={styles.cross} name="cross"></Icon>
                 </div>
               </div>
@@ -69,13 +139,27 @@ export default defineComponent({
           }}
         >
           <div class={styles.bottomConent}>
-            <div class={styles.bottomConentLeft}>
+            <div
+              class={styles.bottomConentLeft}
+              onClick={(e: any) => {
+                e.stopPropagation()
+                e.preventDefault()
+                postMsg()
+              }}
+            >
               <div class={styles.bottomImgWrap}>
                 <img src={sendmsgIcon} alt="" />
               </div>
               <p>发送消息</p>
             </div>
-            <div class={styles.bottomConentRight}>
+            <div
+              class={styles.bottomConentRight}
+              onClick={(e: any) => {
+                e.stopPropagation()
+                e.preventDefault()
+                callPhone()
+              }}
+            >
               <div class={styles.bottomImgWrap}>
                 <img src={phoneIcon} alt="" />
               </div>

BIN
src/school/images/beto-icon.png


BIN
src/school/images/good-icon.png


+ 5 - 1
vite.config.ts

@@ -63,6 +63,10 @@ export default defineConfig({
       '/api-student': {
         target: proxyUrl,
         changeOrigin: true
+      },
+      '/api-backend': {
+        target: proxyUrl,
+        changeOrigin: true
       }
     }
   },
@@ -73,7 +77,7 @@ export default defineConfig({
         teacher: resolve('teacher.html'),
         school: resolve('school.html')
       }
-    },
+    }
     // target: 'es2015',
     // cssTarget: 'chrome80',
     // reportCompressedSize: false,