Browse Source

添加课时和gps

lex 2 years ago
parent
commit
73f3ff3fb0
35 changed files with 2108 additions and 206 deletions
  1. 6 1
      src/components/m-uploader/index.tsx
  2. 13 6
      src/components/m-uploader/inside.tsx
  3. 11 0
      src/helpers/constant.ts
  4. 16 0
      src/router/routes-common.ts
  5. 35 22
      src/views/activity-record/components/cast-item.tsx
  6. 20 12
      src/views/activity-record/components/cast-modal.tsx
  7. 5 0
      src/views/activity-record/detail.module.less
  8. 83 14
      src/views/activity-record/detail.tsx
  9. BIN
      src/views/activity-record/images/icon-edit.png
  10. 15 0
      src/views/activity-record/operation.module.less
  11. 143 27
      src/views/activity-record/operation.tsx
  12. 208 0
      src/views/lesson-list/components/component.module.less
  13. 125 0
      src/views/lesson-list/components/evaluating.tsx
  14. 109 0
      src/views/lesson-list/components/practice.tsx
  15. 92 0
      src/views/lesson-list/components/student.tsx
  16. 173 0
      src/views/lesson-list/components/teacher.tsx
  17. BIN
      src/views/lesson-list/detail-modal/images/1.png
  18. BIN
      src/views/lesson-list/detail-modal/images/2.png
  19. BIN
      src/views/lesson-list/detail-modal/images/3.png
  20. BIN
      src/views/lesson-list/detail-modal/images/bottom.png
  21. BIN
      src/views/lesson-list/detail-modal/images/top.png
  22. 160 0
      src/views/lesson-list/detail-modal/index.module.less
  23. 189 0
      src/views/lesson-list/detail-modal/index.tsx
  24. 156 0
      src/views/lesson-list/detail.tsx
  25. BIN
      src/views/lesson-list/images/icon-member.png
  26. BIN
      src/views/lesson-list/images/icon-no-status.png
  27. BIN
      src/views/lesson-list/images/icon-not-standard.png
  28. BIN
      src/views/lesson-list/images/icon-pople.png
  29. BIN
      src/views/lesson-list/images/icon-standard.png
  30. 174 0
      src/views/lesson-list/index.module.less
  31. 146 0
      src/views/lesson-list/index.tsx
  32. 105 0
      src/views/lesson-list/skeleton-index-modal.tsx
  33. 1 1
      src/views/patrol-evaluation/index.tsx
  34. 122 122
      src/views/schedule-manage/index.module.less
  35. 1 1
      src/views/teacher-attendance/amap-gps.tsx

+ 6 - 1
src/components/m-uploader/index.tsx

@@ -84,7 +84,12 @@ export default defineComponent({
       postMessage(
         {
           api: 'chooseFile',
-          content: { type: type, max: 1, bucket: this.bucket, path: this.path }
+          content: {
+            type: type,
+            max: this.maxCount,
+            bucket: this.bucket,
+            path: this.path
+          }
         },
         (res: any) => {
           console.log(res, 'fileUrl');

+ 13 - 6
src/components/m-uploader/inside.tsx

@@ -84,7 +84,12 @@ export default defineComponent({
       postMessage(
         {
           api: 'chooseFile',
-          content: { type: type, max: 1, bucket: this.bucket, path: this.path }
+          content: {
+            type: type,
+            max: this.maxCount,
+            bucket: this.bucket,
+            path: this.path
+          }
         },
         (res: any) => {
           console.log(res, 'fileUrl');
@@ -209,7 +214,7 @@ export default defineComponent({
         {this.modelValue.length > 0 &&
           this.maxCount > 1 &&
           this.modelValue.map((item: any) => (
-            <div class={[styles.uploader, styles[this.size]]}>
+            <div class={['van-uploader', styles.uploader, styles[this.size]]}>
               {/* 删除按钮 */}
               {this.deletable && !this.disabled && (
                 <Icon
@@ -238,7 +243,7 @@ export default defineComponent({
             // 小于长度才显示
             this.modelValue.length < this.maxCount && (
               <div
-                class={[styles.uploader, styles[this.size]]}
+                class={['van-uploader', styles.uploader, styles[this.size]]}
                 onClick={this.nativeUpload}>
                 <Icon
                   name={this.uploadIcon}
@@ -249,7 +254,7 @@ export default defineComponent({
             )
           ) : (
             <div
-              class={[styles.uploader, styles[this.size]]}
+              class={['van-uploader', styles.uploader, styles[this.size]]}
               onClick={this.nativeUpload}>
               {this.modelValue.length > 0 ? (
                 <div class={['van-uploader__upload']}>
@@ -294,7 +299,7 @@ export default defineComponent({
           // 小于长度才显示
           this.modelValue.length < this.maxCount && (
             <Uploader
-              class={[styles.uploader, styles[this.size]]}
+              class={['van-uploader', styles.uploader, styles[this.size]]}
               afterRead={this.afterRead}
               beforeRead={this.beforeRead}
               beforeDelete={this.beforeDelete}
@@ -306,7 +311,7 @@ export default defineComponent({
           )
         ) : (
           <Uploader
-            class={[styles.uploader, styles[this.size]]}
+            class={['van-uploader', styles.uploader, styles[this.size]]}
             afterRead={this.afterRead}
             beforeRead={this.beforeRead}
             beforeDelete={this.beforeDelete}
@@ -353,6 +358,8 @@ export default defineComponent({
             )}
           </Uploader>
         )}
+
+        {this.$slots.default && this.$slots.default()}
       </>
     );
   }

+ 11 - 0
src/helpers/constant.ts

@@ -103,3 +103,14 @@ export const activeStatus = {
   PROCESSING: '进行中',
   END: '已结束'
 } as any;
+
+
+// 考勤类型
+export const clockingIn = {
+  NORMAL: "正常",
+  TRUANT: "旷课",
+  LEAVE: "请假",
+  QUIT_SCHOOL: "休学",
+  DROP_OUT: "退学",
+  LATE: "迟到"
+} as any

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

@@ -130,6 +130,22 @@ export default [
           title: '退团审批'
         }
       },
+      {
+        path: '/lesson-list',
+        name: 'lesson-list',
+        component: () => import('@/views/lesson-list/index'),
+        meta: {
+          title: '课时列表'
+        }
+      },
+      {
+        path: '/lesson-detail',
+        name: 'lesson-detail',
+        component: () => import('@/views/lesson-list/detail'),
+        meta: {
+          title: '课时详情'
+        }
+      },
     ]
   },
   ...rootRouter

+ 35 - 22
src/views/activity-record/components/cast-item.tsx

@@ -1,5 +1,12 @@
 import { Cell, Checkbox, CheckboxGroup, Icon, Image } from 'vant';
-import { defineComponent, onMounted, reactive, ref, watch } from 'vue';
+import {
+  PropType,
+  defineComponent,
+  onMounted,
+  reactive,
+  ref,
+  watch
+} from 'vue';
 import styles from '../index.module.less';
 // import class1 from '../images/1.png';
 import activeButtonIcon from '@/common/images/icon-check-active.png';
@@ -8,6 +15,10 @@ import iconStudent from '@/common/images/icon-student-default.png';
 export default defineComponent({
   name: 'group-chat',
   props: {
+    type: {
+      type: String as PropType<'edit' | 'look'>,
+      default: 'edit'
+    },
     height: {
       type: [Number],
       default: 0
@@ -38,6 +49,7 @@ export default defineComponent({
     });
 
     const onToggle = (index: number) => {
+      if (props.type === 'look') return;
       //
       checkboxRefs.value[index].toggle();
       const list: any = [];
@@ -107,27 +119,28 @@ export default defineComponent({
                     <div class={styles.infoContent}>{item.subjectName}</div>
                   </div>
                 ),
-                'right-icon': () => (
-                  <Checkbox
-                    name={item.studentId}
-                    ref={e => (checkboxRefs.value[index] = e)}
-                    onClick={(e: MouseEvent) => {
-                      e.stopPropagation();
-                    }}
-                    v-slots={{
-                      icon: (props: any) => (
-                        <Icon
-                          class={styles.boxStyle}
-                          name={
-                            props.checked
-                              ? activeButtonIcon
-                              : inactiveButtonIcon
-                          }
-                        />
-                      )
-                    }}
-                  />
-                )
+                'right-icon': () =>
+                  props.type === 'edit' && (
+                    <Checkbox
+                      name={item.studentId}
+                      ref={e => (checkboxRefs.value[index] = e)}
+                      onClick={(e: MouseEvent) => {
+                        e.stopPropagation();
+                      }}
+                      v-slots={{
+                        icon: (props: any) => (
+                          <Icon
+                            class={styles.boxStyle}
+                            name={
+                              props.checked
+                                ? activeButtonIcon
+                                : inactiveButtonIcon
+                            }
+                          />
+                        )
+                      }}
+                    />
+                  )
               }}
             </Cell>
           ))}

+ 20 - 12
src/views/activity-record/components/cast-modal.tsx

@@ -1,7 +1,7 @@
 import MHeader from '@/components/m-header';
 import MSticky from '@/components/m-sticky';
 import { Button, Tab, Tabs } from 'vant';
-import { defineComponent, onMounted, reactive, watch } from 'vue';
+import { PropType, defineComponent, onMounted, reactive, watch } from 'vue';
 import styles from '../index.module.less';
 import CastItem from './cast-item';
 import { useRect } from '@vant/use';
@@ -10,6 +10,10 @@ import deepClone from '@/helpers/deep-clone';
 export default defineComponent({
   name: 'cast-modal',
   props: {
+    type: {
+      type: String as PropType<'edit' | 'look'>,
+      default: 'edit'
+    },
     subjectAllList: {
       type: Array,
       default: () => []
@@ -98,6 +102,7 @@ export default defineComponent({
               name={item.subjectId}
               title={item.subjectName + `(${item.studentCount})`}>
               <CastItem
+                type={props.type}
                 height={forms.height}
                 headerHeight={forms.popupHeight}
                 bottomHeight={forms.bottomHeight}
@@ -107,17 +112,20 @@ export default defineComponent({
             </Tab>
           ))}
         </Tabs>
-        <MSticky
-          position="bottom"
-          onBarHeight={(height: any) => {
-            forms.bottomHeight = height;
-          }}>
-          <div class={'btnGroupFixed'}>
-            <Button round block type="primary" onClick={onSubmit}>
-              确认
-            </Button>
-          </div>
-        </MSticky>
+        {/* 判断是否是编辑模式 */}
+        {props.type === 'edit' && (
+          <MSticky
+            position="bottom"
+            onBarHeight={(height: any) => {
+              forms.bottomHeight = height;
+            }}>
+            <div class={'btnGroupFixed'}>
+              <Button round block type="primary" onClick={onSubmit}>
+                确认
+              </Button>
+            </div>
+          </MSticky>
+        )}
       </div>
     );
   }

+ 5 - 0
src/views/activity-record/detail.module.less

@@ -1,6 +1,10 @@
 .detail {
   // height: 100vh;
   // overflow: hidden;
+
+  .iconEdit {
+    font-size: 24px;
+  }
 }
 
 .cellGroup {
@@ -110,6 +114,7 @@
 
   .photoList {
     display: flex;
+    margin-top: 15px;
   }
 
   .photo {

+ 83 - 14
src/views/activity-record/detail.tsx

@@ -8,15 +8,19 @@ import MImagePreview from '@/components/m-image-preview';
 import SkeletionIndexModal from './skeletion-index-modal';
 import SkeletionDetailModal from './skeletion-detail-modal';
 import request from '@/helpers/request';
-import { useRoute } from 'vue-router';
+import { useRoute, useRouter } from 'vue-router';
 import { activeStatus, activityStatus } from '@/helpers/constant';
 import dayjs from 'dayjs';
 import { formatterTimer } from './operation';
+import MPopup from '@/components/m-popup';
+import CastModal from './components/cast-modal';
+import iconEdit from './images/icon-edit.png';
 
 export default defineComponent({
   name: 'detail-page',
   setup() {
     const route = useRoute();
+    const router = useRouter();
     // console.log(toChinesNum(11));
     const forms = reactive({
       id: route.query.id,
@@ -26,7 +30,9 @@ export default defineComponent({
       headerLoading: true,
       detailLoading: true,
       detail: [] as any[],
-      headerDetail: {} as any
+      headerDetail: {} as any,
+      castStatus: false,
+      studentAllList: [] as any
     });
 
     const getDetail = async () => {
@@ -35,6 +41,11 @@ export default defineComponent({
           '/api-web/schoolActivity/detail/' + forms.id
         );
         const { detail, ...res } = data || {};
+        detail.forEach((item: any) => {
+          item.attachmentUrl = item.attachmentUrl
+            ? item.attachmentUrl.split(',')
+            : [];
+        });
         forms.detail = detail;
         forms.headerDetail = { ...res };
       } catch {
@@ -45,6 +56,32 @@ export default defineComponent({
       }
     };
 
+    const formatterStudentList = (list: any[]) => {
+      const tempList: any[] = [];
+      list.map((student: any) => {
+        let count = 0;
+        const students: any[] = [];
+
+        student.studentList.forEach((item: any) => {
+          if (item.selected) {
+            count++;
+            students.push(item);
+          }
+        });
+
+        if (count > 0) {
+          tempList.push({
+            studentCount: student.studentCount,
+            subjectId: student.subjectId,
+            subjectName: student.subjectName,
+            studentList: students
+          });
+        }
+      });
+      forms.studentAllList = tempList;
+      forms.castStatus = true;
+    };
+
     onMounted(() => {
       // setTimeout(() => {
       //   // forms.headerLoading = false;
@@ -54,14 +91,31 @@ export default defineComponent({
     });
     return () => (
       <div class={styles.detail}>
-        <MHeader />
+        <MHeader>
+          {{
+            right: () => (
+              <Icon
+                name={iconEdit}
+                class={styles.iconEdit}
+                onClick={() => {
+                  router.push({
+                    path: '/activity-record-operation',
+                    query: {
+                      id: forms.id
+                    }
+                  });
+                }}
+              />
+            )
+          }}
+        </MHeader>
 
         <SkeletionIndexModal
           v-model:show={forms.headerLoading}
           showCount={[1]}
           isLink={false}>
           <CellGroup inset class={styles.cellGroup}>
-            <Cell center isLink class={styles.cellTitle} clickable={false}>
+            <Cell center class={styles.cellTitle}>
               {{
                 icon: () => (
                   <Tag plain type="primary" class={styles.tag}>
@@ -124,12 +178,16 @@ export default defineComponent({
                     </Col>
                     <Col span={19} class={styles.content}>
                       {item.musicGroupName}
-                      <span>
+                      <span
+                        onClick={() => {
+                          if (item.studentNum <= 0) return;
+                          formatterStudentList(item.studentList);
+                        }}>
                         共{item.studentNum}名 <Icon name="arrow" />
                       </span>
                     </Col>
                   </Row>
-                  <Row class={styles.item}>
+                  <Row class={styles.item} style={{ marginBottom: '0' }}>
                     <Col span={5} class={styles.label}>
                       表演团队
                     </Col>
@@ -139,14 +197,16 @@ export default defineComponent({
                   </Row>
                   {item.attachmentUrl ? (
                     <div class={styles.photoList}>
-                      {[1, 2, 3, 4, 5].map((i: any, index: number) => (
-                        <div class={styles.photo}>
-                          <Image
-                            src={
-                              'https://lanhu-dds-backend.oss-cn-beijing.aliyuncs.com/merge_image/imgs/4854b7cb932d461fbaa4cc329666dbd6_mergeImage.png'
-                            }
-                          />
-                          {[1, 2, 3, 4, 5].length > 4 && index === 3 ? (
+                      {item.attachmentUrl.map((i: any, index: number) => (
+                        <div
+                          class={styles.photo}
+                          onClick={() => {
+                            forms.imagePreview = item.attachmentUrl;
+                            forms.imageShow = true;
+                            forms.startPosition = index;
+                          }}>
+                          <Image src={i} fit="contain" />
+                          {item.attachmentUrl.length > 4 && index === 3 ? (
                             <div class={styles.photoMore}>+8</div>
                           ) : (
                             ''
@@ -168,6 +228,15 @@ export default defineComponent({
           images={forms.imagePreview}
           startPosition={forms.startPosition}
         />
+
+        {/* 演员名单 */}
+        <MPopup v-model:modelValue={forms.castStatus}>
+          <CastModal
+            type="look"
+            subjectAllList={forms.studentAllList}
+            onClose={() => (forms.castStatus = false)}
+          />
+        </MPopup>
       </div>
     );
   }

BIN
src/views/activity-record/images/icon-edit.png


+ 15 - 0
src/views/activity-record/operation.module.less

@@ -200,6 +200,21 @@
   }
 }
 
+.uploadGroup {
+  display: flex;
+  align-items: center;
+  --upload-file-size: 72px !important;
+
+  // 处理显示问题
+  :global {
+    .van-uploader:nth-child(4n + 4) {
+      .van-uploader__upload {
+        margin-right: 0;
+      }
+    }
+  }
+}
+
 .addButtonGroup {
   margin: 12px 13px;
 

+ 143 - 27
src/views/activity-record/operation.tsx

@@ -31,6 +31,7 @@ import MPopup from '@/components/m-popup';
 import CastModal from './components/cast-modal';
 import dayjs from 'dayjs';
 import { useRoute, useRouter } from 'vue-router';
+import { checkFile } from '@/helpers/toolsValidate';
 
 type detailItemType = {
   id: string | number | null;
@@ -87,7 +88,7 @@ export default defineComponent({
     const forms = reactive({
       activityDetailId: route.query.id, // 活动编号
       timerStatus: false,
-      currentDate: [],
+      currentDate: [] as any,
       orchestraStatus: false, // 乐团列表状态
       orchestraColumns: [] as any,
       programType: '',
@@ -136,7 +137,6 @@ export default defineComponent({
           }
         );
         const tempData = data || [];
-        console.log(tempData, 'tempData');
         forms.selectOrchestra.subjectAllList = tempData;
         forms.selectOrchestra.performerList = tempData;
 
@@ -191,8 +191,99 @@ export default defineComponent({
       forms.selectOrchestra.performerList = tempList;
     };
 
+    // 初始化已经先中的学生
+    const formatterStudentList = (list: any[]) => {
+      const tempList: any[] = [];
+      list.map((student: any) => {
+        let count = 0;
+        const students: any[] = [];
+
+        student.studentList.forEach((item: any) => {
+          if (item.selected) {
+            count++;
+            students.push(item);
+          }
+        });
+
+        if (count > 0) {
+          tempList.push({
+            studentCount: students.length,
+            subjectId: student.subjectId,
+            subjectName: student.subjectName,
+            studentList: students
+          });
+        }
+      });
+
+      return tempList || [];
+    };
+
+    const getDetail = async () => {
+      try {
+        const { data } = await request.get(
+          '/api-web/schoolActivity/detail/' + forms.activityDetailId
+        );
+        const { detail, name, startTime, type } = data || {};
+
+        // startTime: forms.startTime,
+        //   endTime: forms.startTime + ' 23:59:59',
+        //   name: forms.name,
+        //   type: forms.type,
+        //   detail: [] as any[]
+        forms.startTime = dayjs(startTime).format('YYYY-MM-DD');
+        forms.name = name;
+        forms.type = type;
+
+        forms.currentDate = [
+          dayjs(startTime).format('YYYY'),
+          dayjs(startTime).format('MM'),
+          dayjs(startTime).format('DD')
+        ];
+        const initDetails: any[] = [];
+
+        detail.forEach((item: any) => {
+          const url: string[] = item.attachmentUrl
+            ? item.attachmentUrl.split(',')
+            : [];
+          const videoUrl: string[] = [];
+          const imgUrl: string[] = [];
+          url.forEach((url: string) => {
+            if (checkFile(url, 'image')) {
+              imgUrl.push(url);
+            } else {
+              videoUrl.push(url);
+            }
+          });
+          const tmp: any = {
+            id: item.id,
+            name: item.name,
+            type: item.type,
+            musicGroupId: item.musicGroupId,
+            musicGroupName: item.musicGroupName,
+            subjectAllList: item.studentList,
+            subjectIdList: item.subjectIdList
+              ? item.subjectIdList.split(',').map((i: any) => Number(i))
+              : [],
+            time: item.time,
+            performerList: formatterStudentList(item.studentList),
+            attachmentUrl: url,
+            attachmentVideoUrl: videoUrl,
+            attachmentImgUrl: imgUrl
+          };
+          initDetails.push(tmp);
+        });
+        forms.detail = initDetails;
+      } catch {
+        //
+      }
+    };
+
     onMounted(() => {
       musicGroupPage();
+
+      if (forms.activityDetailId) {
+        getDetail();
+      }
     });
 
     const checkForms = () => {
@@ -246,6 +337,7 @@ export default defineComponent({
         if (!checkForms()) return;
 
         const params = {
+          id: forms.activityDetailId || null,
           startTime: forms.startTime,
           endTime: forms.startTime + ' 23:59:59',
           name: forms.name,
@@ -254,36 +346,46 @@ export default defineComponent({
         };
         const tempDetail: any[] = [];
         forms.detail.forEach((item: any, index: number) => {
+          console.log(item);
           tempDetail.push({
+            id: item.id || null,
             sort: index + 1,
             name: item.name,
             type: item.type,
-            musicGroupId: item.musicGroupId,
+            musicGroupId: item.musicGroupId + '',
             subjectIdList: item.subjectIdList.join(','),
             studentNum: formatterStudents(item.performerList),
             studentList: item.performerList,
             time: item.time,
             attachmentUrl: [
               ...item.attachmentImgUrl,
-              ...item.attachmentUrl
+              ...item.attachmentVideoUrl
             ].join(',')
           });
         });
         params.detail = tempDetail;
 
-        await request.post('/api-web/schoolActivity/save', {
-          hideLoading: false,
-          data: params
-        });
+        if (forms.activityDetailId) {
+          await request.post('/api-web/schoolActivity/update', {
+            hideLoading: false,
+            data: params
+          });
+        } else {
+          await request.post('/api-web/schoolActivity/save', {
+            hideLoading: false,
+            data: params
+          });
+        }
 
-        router.push('/activity-record');
+        // router.push('/activity-record');
+        router.back();
       } catch {
         //
       }
     };
     return () => (
       <div class={styles.operation}>
-        <MHeader />
+        <MHeader title={forms.activityDetailId ? '修改活动' : '新增活动'} />
 
         <CellGroup inset class={styles.topCellGroup}>
           <Field
@@ -446,6 +548,8 @@ export default defineComponent({
                   showToast('请选择表演团队');
                   return;
                 }
+                forms.selectOrchestra = [];
+                forms.selectOrchestra = item;
                 forms.castStatus = true;
               }}
               v-slots={{
@@ -480,19 +584,23 @@ export default defineComponent({
             <Field label="上传附件" labelAlign="top">
               {{
                 input: () => (
-                  <MUploader
-                    uploadIcon={iconUploadImg}
-                    maxCount={5}
-                    v-model:modelValue={item.attachmentImgUrl}
-                    style={{ marginTop: '6px' }}>
-                    <MUploaderInside
-                      uploadIcon={iconUploadVideo}
-                      uploadType="VIDEO"
-                      accept=".mp4"
-                      maxCount={3}
-                      v-model:modelValue={item.attachmentVideoUrl}
-                    />
-                  </MUploader>
+                  <div class={styles.uploadGroup}>
+                    <MUploader
+                      uploadIcon={iconUploadImg}
+                      maxCount={5}
+                      native
+                      v-model:modelValue={item.attachmentImgUrl}
+                      style={{ marginTop: '6px' }}>
+                      <MUploaderInside
+                        uploadIcon={iconUploadVideo}
+                        native
+                        uploadType="VIDEO"
+                        accept=".mp4"
+                        maxCount={3}
+                        v-model:modelValue={item.attachmentVideoUrl}
+                      />
+                    </MUploader>
+                  </div>
                 )
               }}
             </Field>
@@ -539,10 +647,18 @@ export default defineComponent({
             columns={forms.orchestraColumns}
             onCancel={() => (forms.orchestraStatus = false)}
             onConfirm={({ selectedOptions }) => {
-              forms.selectOrchestra.musicGroupName = selectedOptions[0].text;
-              forms.selectOrchestra.musicGroupId = selectedOptions[0].value;
-              forms.orchestraStatus = false;
-              getUserList();
+              if (
+                forms.selectOrchestra.musicGroupId != selectedOptions[0].value
+              ) {
+                forms.selectOrchestra.subjectAllList = [];
+                forms.selectOrchestra.performerList = [];
+                forms.selectOrchestra.subjectIdList = [];
+
+                forms.selectOrchestra.musicGroupName = selectedOptions[0].text;
+                forms.selectOrchestra.musicGroupId = selectedOptions[0].value;
+                forms.orchestraStatus = false;
+                getUserList();
+              }
             }}
           />
         </Popup>

+ 208 - 0
src/views/lesson-list/components/component.module.less

@@ -0,0 +1,208 @@
+.student-list {
+
+  :global {
+    .van-cell__value {
+      flex: 0 auto;
+      text-align: center;
+    }
+  }
+
+  .iconGroup {
+    position: relative;
+    margin-right: 10px;
+  }
+
+  .iconStudent {
+    width: 48px;
+    height: 48px;
+    border-radius: 50%;
+    overflow: hidden;
+  }
+
+  .iconMember {
+    position: absolute;
+    top: 0;
+    right: 0;
+    font-size: 16px;
+  }
+
+  .userInfo {
+    .name {
+      font-size: 16px;
+      font-weight: 600;
+      color: var(--k-gray-1);
+      line-height: 22px;
+    }
+
+    span {
+      font-weight: 500;
+      font-size: 12px;
+      color: #00B2A7;
+      padding: 1px 8px;
+      background: rgba(96, 218, 214, 0.18);
+      border-radius: 4px;
+    }
+
+    // 旷课
+    .TRUANT {
+      color: #FF5C5F;
+      background: rgba(255, 92, 95, 0.18);
+    }
+
+    // 请假
+    .LEAVE {
+      color: #F08226;
+      background: rgba(255, 179, 87, 0.18);
+    }
+
+    // 休学
+    .QUIT_SCHOOL {
+      color: #5099F5;
+      background: rgba(100, 169, 255, 0.18);
+    }
+
+    // 退学
+    .DROP_OUT {
+      color: #5099F5;
+      background: rgba(100, 169, 255, 0.18);
+    }
+
+    // 迟到
+    .LATE {
+      color: #5099F5;
+      background: rgba(100, 169, 255, 0.18);
+    }
+  }
+
+  .after {
+    font-size: 12px;
+    color: var(--k-gray-3);
+    line-height: 17px;
+
+    .standard {
+      height: 15px;
+      width: 30px;
+    }
+
+    .notStandard {
+      width: 45px;
+      height: 15px;
+    }
+
+    .noStatus {
+      width: 12px;
+      height: 3px;
+    }
+
+    .txt {
+      padding-top: 5px;
+    }
+  }
+}
+
+.error {
+  color: #FF5A56 !important;
+}
+
+.success {
+  color: var(--k-font-primary) !important;
+}
+
+.teacher-list {
+  .detailGroup {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    flex-wrap: wrap;
+    width: 100%;
+  }
+
+  .detailItem {
+    width: 158px;
+    padding: 12px;
+    background: #F6F6F6;
+    border-radius: 6px;
+    margin-bottom: 9px;
+  }
+
+  .detailStatus {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    font-size: 16px;
+    font-weight: 600;
+
+    .statusName {
+      color: var(--k-gray-1);
+    }
+
+    .img {
+      width: 16px;
+      height: 16px;
+    }
+  }
+
+  .sign {
+    padding-top: 10px;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    font-size: 14px;
+    color: var(--k-gray-1);
+
+    .locate {
+      font-size: 12px;
+      color: #999999;
+    }
+  }
+}
+
+.practice-list {
+
+  .iconGroup {
+    position: relative;
+    margin-right: 10px;
+  }
+
+  .iconStudent {
+    width: 48px;
+    height: 48px;
+    border-radius: 50%;
+    overflow: hidden;
+  }
+
+  .iconMember {
+    position: absolute;
+    top: 0;
+    right: 0;
+    font-size: 16px;
+  }
+
+  .userInfo {
+    .name {
+      font-size: 16px;
+      font-weight: 600;
+      color: var(--k-gray-1);
+      line-height: 22px;
+    }
+
+    span {
+      font-size: 12px;
+      color: var(--k-gray-3);
+    }
+  }
+
+  .after {
+    font-size: 14px;
+    font-weight: 500;
+  }
+}
+
+.popup {
+  --van-popup-close-icon-color: #1A413E;
+}
+
+.emptyContainer {
+  background-color: #fff;
+  padding-top: 40px;
+}

+ 125 - 0
src/views/lesson-list/components/evaluating.tsx

@@ -0,0 +1,125 @@
+import { Cell, Icon, Image, Popup } from 'vant';
+import { defineComponent, onMounted, reactive } from 'vue';
+import styles from './component.module.less';
+import iconStudent from '@/common/images/icon-student-default.png';
+import iconMember from '../images/icon-member.png';
+import DetailModal from '../detail-modal';
+import request from '@/helpers/request';
+import MEmpty from '@/components/m-empty';
+
+export default defineComponent({
+  name: 'evaluating-list',
+  props: {
+    lessonExaminationId: {
+      type: Number,
+      default: null
+    }
+  },
+  setup(props) {
+    const forms = reactive({
+      showPopup: false,
+      list: [] as any[],
+      dataShow: true,
+      detail: {} as any
+    });
+
+    const getList = async () => {
+      try {
+        const { data } = await request.post(
+          '/api-web/schoolStudentHomework/queryLessonStudent',
+          {
+            data: {
+              lessonExaminationId: props.lessonExaminationId
+            }
+          }
+        );
+        forms.list = data.rows || [];
+        forms.dataShow = forms.list.length ? true : false;
+      } catch {
+        //
+      }
+    };
+
+    const onDetail = async (item: any) => {
+      try {
+        const { data } = await request.post(
+          '/api-web/schoolStudentHomework/queryStudentLessonDetail',
+          {
+            hideLoading: false,
+            data: {
+              lessonExaminationId: props.lessonExaminationId,
+              userId: item.userId
+            }
+          }
+        );
+        forms.detail = {
+          avatar: item.avatar,
+          memberFlag: item.memberFlag,
+          username: item.username,
+          subjectName: item.subjectName,
+          trainingScore: item.trainingScore,
+          list: data || []
+        };
+        forms.showPopup = true;
+      } catch {
+        //
+      }
+    };
+
+    onMounted(() => {
+      getList();
+    });
+    return () => (
+      <div class={styles['practice-list']}>
+        {forms.dataShow ? (
+          forms.list.map((item: any) => (
+            <Cell center isLink onClick={() => onDetail(item)}>
+              {{
+                icon: () => (
+                  <div class={styles.iconGroup}>
+                    <Image
+                      src={item.avatar || iconStudent}
+                      class={styles.iconStudent}
+                      fit="contain"
+                    />
+                    {item.memberFlag && (
+                      <Icon name={iconMember} class={styles.iconMember} />
+                    )}
+                  </div>
+                ),
+                title: () => (
+                  <div class={styles.userInfo}>
+                    <p class={styles.name}>{item.username}</p>
+                    <span>{item.subjectName}</span>
+                  </div>
+                ),
+                value: () => (
+                  <div
+                    class={[
+                      styles.after,
+                      item.trainingScore >= 60 ? styles.success : styles.error
+                    ]}>
+                    {item.trainingScore}分
+                  </div>
+                )
+              }}
+            </Cell>
+          ))
+        ) : (
+          <div class={styles.emptyContainer}>
+            <MEmpty description="暂无数据" />
+          </div>
+        )}
+
+        <Popup
+          v-model:show={forms.showPopup}
+          position="bottom"
+          round
+          closeable
+          class={styles.popup}>
+          <DetailModal type="evaluating" detail={forms.detail} />
+        </Popup>
+      </div>
+    );
+  }
+});

+ 109 - 0
src/views/lesson-list/components/practice.tsx

@@ -0,0 +1,109 @@
+import { Cell, Icon, Image, Popup } from 'vant';
+import { PropType, defineComponent, onMounted, reactive } from 'vue';
+import styles from './component.module.less';
+import iconStudent from '@/common/images/icon-student-default.png';
+import iconMember from '../images/icon-member.png';
+import DetailModal from '../detail-modal';
+import request from '@/helpers/request';
+
+export default defineComponent({
+  name: 'practice-list',
+  props: {
+    courseId: {
+      type: String as PropType<any>,
+      default: null
+    }
+  },
+  setup(props) {
+    // schoolStudentHomework/findCourseStudentsPublic/v2
+
+    const forms = reactive({
+      showPopup: false,
+      list: [] as any[],
+      dataShow: true,
+      detail: {} as any
+    });
+
+    const getList = async () => {
+      try {
+        const { data } = await request.post(
+          '/api-web/schoolStudentHomework/findCourseStudentsPublic/v2',
+          {
+            data: {
+              courseScheduleId: props.courseId,
+              type: 'HOMEWORK'
+            }
+          }
+        );
+        forms.list = data || [];
+        forms.dataShow = forms.list.length ? true : false;
+      } catch {
+        //
+      }
+    };
+
+    const onDetail = async (item: any) => {
+      try {
+        forms.detail = {
+          avatar: item.avatar,
+          memberFlag: item.vipFlag,
+          username: item.userName,
+          subjectName: item.subjectName,
+          finishFlag: item.finishFlag,
+          list: item.studentLessonTrainingDetail || []
+        };
+        forms.showPopup = true;
+      } catch {
+        //
+      }
+    };
+
+    onMounted(() => {
+      getList();
+    });
+    return () => (
+      <div class={styles['practice-list']}>
+        {forms.list.map((item: any) => (
+          <Cell center isLink onClick={() => onDetail(item)}>
+            {{
+              icon: () => (
+                <div class={styles.iconGroup}>
+                  <Image
+                    src={item.avatar || iconStudent}
+                    class={styles.iconStudent}
+                    fit="contain"
+                  />
+                  <Icon name={iconMember} class={styles.iconMember} />
+                </div>
+              ),
+              title: () => (
+                <div class={styles.userInfo}>
+                  <p class={styles.name}>{item.userName}</p>
+                  <span>{item.subjectName}</span>
+                </div>
+              ),
+              value: () => (
+                <div
+                  class={[
+                    styles.after,
+                    item.finishFlag ? styles.success : styles.error
+                  ]}>
+                  {item.finishFlag ? '完成' : '未完成'}
+                </div>
+              )
+            }}
+          </Cell>
+        ))}
+
+        <Popup
+          v-model:show={forms.showPopup}
+          position="bottom"
+          round
+          closeable
+          class={styles.popup}>
+          <DetailModal detail={forms.detail} />
+        </Popup>
+      </div>
+    );
+  }
+});

+ 92 - 0
src/views/lesson-list/components/student.tsx

@@ -0,0 +1,92 @@
+import { Cell, Icon, Image } from 'vant';
+import { defineComponent, onMounted, reactive } from 'vue';
+import styles from './component.module.less';
+import iconStudent from '@/common/images/icon-student-default.png';
+import iconMember from '../images/icon-member.png';
+import iconStandard from '../images/icon-standard.png';
+import iconNotStandard from '../images/icon-not-standard.png';
+// import iconNoStatus from '../images/icon-no-status.png';
+import request from '@/helpers/request';
+import { useRoute } from 'vue-router';
+import { clockingIn } from '@/helpers/constant';
+import MEmpty from '@/components/m-empty';
+
+export default defineComponent({
+  name: 'student-list',
+  setup() {
+    const route = useRoute();
+    const forms = reactive({
+      courseId: route.query.courseId,
+      dataShow: true,
+      list: [] as any[]
+    });
+
+    const getList = async () => {
+      try {
+        const { data } = await request.get(
+          '/api-web/schoolCourseSchedule/queryCourseStudentList',
+          {
+            params: {
+              courseId: forms.courseId
+            }
+          }
+        );
+        forms.list = data || [];
+        forms.dataShow = forms.list.length ? true : false;
+      } catch {
+        //
+      }
+    };
+
+    onMounted(() => {
+      getList();
+    });
+
+    return () => (
+      <div class={styles['student-list']}>
+        {forms.dataShow ? (
+          forms.list.map((item: any) => (
+            <Cell center>
+              {{
+                icon: () => (
+                  <div class={styles.iconGroup}>
+                    <Image
+                      src={item.studentAvatar || iconStudent}
+                      class={styles.iconStudent}
+                      fit="contain"
+                    />
+                    {item.memberFlag && (
+                      <Icon name={iconMember} class={styles.iconMember} />
+                    )}
+                  </div>
+                ),
+                title: () => (
+                  <div class={styles.userInfo}>
+                    <p class={styles.name}>{item.studentName}</p>
+                    <span class={styles[item.attendanceStatus]}>
+                      {clockingIn[item.attendanceStatus]}
+                    </span>
+                  </div>
+                ),
+                value: () => (
+                  <div class={styles.after}>
+                    {item.qualifiedFlag ? (
+                      <img src={iconStandard} class={styles.standard} />
+                    ) : (
+                      <img src={iconNotStandard} class={styles.notStandard} />
+                    )}
+                    <p class={styles.txt}>课后评价</p>
+                  </div>
+                )
+              }}
+            </Cell>
+          ))
+        ) : (
+          <div class={styles.emptyContainer}>
+            <MEmpty description="暂无数据" />
+          </div>
+        )}
+      </div>
+    );
+  }
+});

+ 173 - 0
src/views/lesson-list/components/teacher.tsx

@@ -0,0 +1,173 @@
+import { Cell, Icon, Image } from 'vant';
+import { defineComponent, onMounted, reactive } from 'vue';
+import styles from './component.module.less';
+import iconTeacher from '@/common/images/icon-teacher-default.png';
+import iconSuccess from '@/common/images/icon-check-active.png';
+import iconWarn from '@/common/images/icon-warn.png';
+import { useRoute, useRouter } from 'vue-router';
+import dayjs from 'dayjs';
+import request from '@/helpers/request';
+
+export default defineComponent({
+  name: 'teacher-list',
+  setup() {
+    const route = useRoute();
+    const router = useRouter();
+    const forms = reactive({
+      courseId: route.query.courseId,
+      teacherId: route.query.teacherId,
+      detail: {} as any
+    });
+
+    // schoolTeacherAttendance/getCourseTeacherAttendance
+
+    const onAddressGps = (item: any) => {
+      //
+      router.push({
+        path: '/amap-gps',
+        query: {
+          sLngLat: item.schoolLongitudeLatitude, // 教学点
+          inLngLat: item.signInLongitudeLatitude, // 签到
+          outLngLat: item.signOutLongitudeLatitude // 签退
+        }
+      });
+    };
+
+    const getList = async () => {
+      try {
+        const { data } = await request.get(
+          '/api-web/schoolTeacherAttendance/getCourseTeacherAttendance',
+          {
+            params: {
+              courseId: forms.courseId,
+              teacherId: forms.teacherId
+            }
+          }
+        );
+        forms.detail = data || {};
+      } catch {
+        //
+      }
+    };
+
+    onMounted(() => {
+      getList();
+    });
+
+    return () => (
+      <div class={styles['teacher-list']}>
+        <Cell center>
+          <div class={styles.detailGroup}>
+            <div class={styles.detailItem}>
+              <div class={styles.detailStatus}>
+                <span
+                  class={[
+                    styles.statusName,
+                    forms.detail.signInStatus ? '' : styles.error
+                  ]}>
+                  {forms.detail.signInStatus ? '正常' : '异常'}
+                </span>
+                <img
+                  src={forms.detail.signInStatus ? iconSuccess : iconWarn}
+                  class={styles.img}
+                />
+              </div>
+              <div class={styles.sign}>
+                <span class={styles.signTime}>
+                  签到时间{' '}
+                  {forms.detail.signInTime
+                    ? dayjs(forms.detail.signInTime).format('HH:mm:ss')
+                    : '--'}
+                </span>
+              </div>
+            </div>
+            {forms.detail.teachMode === 'OFFLINE' ? (
+              <div class={styles.detailItem}>
+                <div class={styles.detailStatus}>
+                  <span
+                    class={[
+                      styles.statusName,
+                      forms.detail.signInAddressStatus ? '' : styles.error
+                    ]}>
+                    {forms.detail.signInAddressStatus ? '正常' : '异常'}
+                  </span>
+                  <img
+                    src={
+                      forms.detail.signInAddressStatus ? iconSuccess : iconWarn
+                    }
+                    class={styles.img}
+                  />
+                </div>
+                <div class={styles.sign}>
+                  <span class={styles.signTime}>签到地点</span>
+                  <span
+                    class={styles.locate}
+                    onClick={() => onAddressGps(forms.detail)}>
+                    查看定位
+                    <Icon name="arrow" class={styles.iconArrow} />
+                  </span>
+                </div>
+              </div>
+            ) : (
+              ''
+            )}
+
+            <div class={styles.detailItem}>
+              <div class={styles.detailStatus}>
+                <span
+                  class={[
+                    styles.statusName,
+                    forms.detail.signOutStatus ? '' : styles.error
+                  ]}>
+                  {forms.detail.signOutStatus ? '正常' : '异常'}
+                </span>
+                <img
+                  src={forms.detail.signOutStatus ? iconSuccess : iconWarn}
+                  class={styles.img}
+                />
+              </div>
+              <div class={styles.sign}>
+                <span class={styles.signTime}>
+                  签退时间{' '}
+                  {forms.detail.signOutTime
+                    ? dayjs(forms.detail.signOutTime).format('HH:mm:ss')
+                    : '--'}
+                </span>
+              </div>
+            </div>
+            {forms.detail.teachMode === 'OFFLINE' ? (
+              <div class={styles.detailItem}>
+                <div class={styles.detailStatus}>
+                  <span
+                    class={[
+                      styles.statusName,
+                      forms.detail.signOutAddressStatus ? '' : styles.error
+                    ]}>
+                    {forms.detail.signOutAddressStatus ? '正常' : '异常'}
+                  </span>
+                  <img
+                    src={
+                      forms.detail.signOutAddressStatus ? iconSuccess : iconWarn
+                    }
+                    class={styles.img}
+                  />
+                </div>
+                <div class={styles.sign}>
+                  <span class={styles.signTime}>签到地点</span>
+                  <span
+                    class={styles.locate}
+                    onClick={() => onAddressGps(forms.detail)}>
+                    查看定位
+                    <Icon name="arrow" class={styles.iconArrow} />
+                  </span>
+                </div>
+              </div>
+            ) : (
+              ''
+            )}
+          </div>
+        </Cell>
+      </div>
+    );
+  }
+});

BIN
src/views/lesson-list/detail-modal/images/1.png


BIN
src/views/lesson-list/detail-modal/images/2.png


BIN
src/views/lesson-list/detail-modal/images/3.png


BIN
src/views/lesson-list/detail-modal/images/bottom.png


BIN
src/views/lesson-list/detail-modal/images/top.png


+ 160 - 0
src/views/lesson-list/detail-modal/index.module.less

@@ -0,0 +1,160 @@
+.details {
+  position: relative;
+  background: linear-gradient(135deg, #B1FDB7 0%, #41D2DB 69%, #A2EAE2 100%);
+
+  &::before {
+    position: absolute;
+    top: 0;
+    left: 0;
+    content: " ";
+    z-index: 0;
+
+    width: 73px;
+    height: 56px;
+    background: url('./images/top.png') no-repeat center;
+    background-size: contain;
+  }
+
+  &::after {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    content: " ";
+    z-index: 0;
+
+    right: 0;
+    height: 105px;
+    background: url('./images/bottom.png') no-repeat center;
+    background-size: cover;
+  }
+
+  min-height: 30vh;
+
+  .userCell {
+    border-radius: 10px;
+    margin-bottom: 8px;
+  }
+
+  .content {
+    position: relative;
+    z-index: 1;
+    padding: 20px 14px;
+  }
+
+  .title {
+    font-size: 18px;
+    font-weight: 600;
+    color: #131415;
+    line-height: 25px;
+    text-align: center;
+    padding-bottom: 20px;
+  }
+
+  .iconGroup {
+    position: relative;
+    margin-right: 10px;
+    line-height: 0;
+
+  }
+
+  .iconStudent {
+    width: 48px;
+    height: 48px;
+    border-radius: 50%;
+    overflow: hidden;
+  }
+
+  .iconMember {
+    position: absolute;
+    top: -4px;
+    right: 0;
+    font-size: 16px;
+  }
+
+  .userInfo {
+    .name {
+      font-size: 16px;
+      font-weight: 600;
+      color: var(--k-gray-1);
+      line-height: 22px;
+    }
+
+    span {
+      font-size: 12px;
+      color: var(--k-gray-3);
+    }
+  }
+
+  .after {
+    font-size: 14px;
+    font-weight: 500;
+  }
+
+  .error {
+    color: #FF5A56 !important;
+  }
+
+  .success {
+    color: var(--k-font-primary) !important;
+  }
+
+
+  .tables {
+    background-color: #FFf;
+    border-radius: 10px;
+
+    .thead,
+    .tbody {
+      padding: 16px 0;
+      margin: 0 12px;
+    }
+
+    .thead {
+      font-size: 13px;
+      color: var(--k-gray-3);
+    }
+
+    .tContainer {
+      max-height: 30vh;
+      // overflow-x: hidden;
+      overflow-y: auto;
+    }
+
+    .col2 {
+      text-align: center;
+    }
+
+    .col3 {
+      text-align: right;
+    }
+
+    .img {
+      width: 44px;
+      height: 18px;
+    }
+
+    .tbody {
+      font-size: 15px;
+
+      .col1 {
+        color: var(--k-gray-1);
+        font-weight: 500;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+
+      }
+
+      .col2 {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+      }
+
+      .col2,
+      .col3 {
+        color: var(--k-gray-2);
+      }
+    }
+  }
+}

+ 189 - 0
src/views/lesson-list/detail-modal/index.tsx

@@ -0,0 +1,189 @@
+import { PropType, defineComponent, reactive, watch } from 'vue';
+import styles from './index.module.less';
+import { Cell, Col, Icon, Image, Row } from 'vant';
+import iconStudent from '@/common/images/icon-student-default.png';
+import iconMember from '../images/icon-member.png';
+import img1 from './images/1.png';
+import img2 from './images/2.png';
+import img3 from './images/3.png';
+import MEmpty from '@/components/m-empty';
+
+export default defineComponent({
+  name: 'detail-modal',
+  props: {
+    type: {
+      type: String as PropType<'proctice' | 'evaluating'>,
+      default: 'proctice'
+    },
+    title: {
+      type: String,
+      default: '学员练习详情'
+    },
+    detail: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  setup(props) {
+    const forms = reactive({
+      detail: props.detail
+    });
+
+    watch(
+      () => props.detail,
+      () => {
+        forms.detail = props.detail;
+      }
+    );
+    return () => (
+      <div class={styles.details}>
+        <div class={styles.content}>
+          <div class={styles.title}>{props.title}</div>
+
+          <Cell center class={styles.userCell}>
+            {{
+              icon: () => (
+                <div class={styles.iconGroup}>
+                  <Image
+                    src={forms.detail.avatar || iconStudent}
+                    class={styles.iconStudent}
+                    fit="contain"
+                  />
+                  {forms.detail.memberFlag && (
+                    <Icon name={iconMember} class={styles.iconMember} />
+                  )}
+                </div>
+              ),
+              title: () => (
+                <div class={styles.userInfo}>
+                  <p class={styles.name}>{forms.detail.username}</p>
+                  <span>{forms.detail.subjectName}</span>
+                </div>
+              ),
+              value: () =>
+                props.type === 'proctice' ? (
+                  <div
+                    class={[
+                      styles.after,
+                      forms.detail.finishFlag ? styles.success : styles.error
+                    ]}>
+                    {forms.detail.finishFlag ? '完成' : '未完成'}
+                  </div>
+                ) : (
+                  <div
+                    class={[
+                      styles.after,
+                      forms.detail.trainingScore >= 60
+                        ? styles.success
+                        : styles.error
+                    ]}>
+                    {forms.detail.trainingScore}分
+                  </div>
+                )
+            }}
+          </Cell>
+
+          <div class={styles.tables}>
+            {props.type === 'proctice' ? (
+              <>
+                <Row class={styles.thead}>
+                  <Col span={12} class={styles.col1}>
+                    练习曲目
+                  </Col>
+                  <Col span={6} class={styles.col2}>
+                    练习速度
+                  </Col>
+                  <Col span={6} class={styles.col3}>
+                    次数/总计
+                  </Col>
+                </Row>
+                <div class={styles.tContainer}>
+                  {forms.detail.list.map((item: any) => (
+                    <Row class={[styles.tbody, 'van-hairline--top']}>
+                      <Col span={12} class={styles.col1}>
+                        {item.musicScoreName}
+                      </Col>
+                      <Col span={6} class={styles.col2}>
+                        <span class={styles.success}>{item.trainingSpeed}</span>
+                        速度
+                      </Col>
+                      <Col span={6} class={styles.col3}>
+                        <span
+                          class={
+                            item.trainingTimes < item.times
+                              ? styles.error
+                              : styles.success
+                          }>
+                          {item.trainingTimes}
+                        </span>
+                        /{item.times}次
+                      </Col>
+                    </Row>
+                  ))}
+                  {forms.detail.list.length <= 0 && (
+                    <MEmpty
+                      description="暂无练习"
+                      style={{ paddingBottom: 0 }}
+                    />
+                  )}
+                </div>
+              </>
+            ) : (
+              <>
+                <Row class={styles.thead}>
+                  <Col span={12} class={styles.col1}>
+                    评测曲目
+                  </Col>
+                  <Col span={6} class={styles.col2}>
+                    难度
+                  </Col>
+                  <Col span={6} class={styles.col3}>
+                    得分/达标
+                  </Col>
+                </Row>
+                <div class={styles.tContainer}>
+                  {forms.detail.list.map((item: any) => (
+                    <Row class={[styles.tbody, 'van-hairline--top']}>
+                      <Col span={12} class={styles.col1}>
+                        {item.musicScoreName}
+                      </Col>
+                      <Col span={6} class={styles.col2}>
+                        {item.heardLevel === 'BEGINNER' && (
+                          <img src={img1} class={styles.img} />
+                        )}
+                        {item.heardLevel === 'ADVANCED' && (
+                          <img src={img2} class={styles.img} />
+                        )}
+                        {item.heardLevel === 'PERFORMER' && (
+                          <img src={img3} class={styles.img} />
+                        )}
+                      </Col>
+                      <Col span={6} class={styles.col3}>
+                        <span
+                          class={
+                            item.actualAvgScore < item.standardScore
+                              ? styles.error
+                              : styles.error
+                          }>
+                          {item.actualAvgScore}
+                        </span>
+                        /{item.standardScore}分
+                      </Col>
+                    </Row>
+                  ))}
+
+                  {forms.detail.list.length <= 0 && (
+                    <MEmpty
+                      description="暂无评测"
+                      style={{ paddingBottom: 0 }}
+                    />
+                  )}
+                </div>
+              </>
+            )}
+          </div>
+        </div>
+      </div>
+    );
+  }
+});

+ 156 - 0
src/views/lesson-list/detail.tsx

@@ -0,0 +1,156 @@
+import { defineComponent, onMounted, reactive } from 'vue';
+import styles from './index.module.less';
+import MHeader from '@/components/m-header';
+import { Cell, CellGroup, Col, Icon, Image, Row, Tab, Tabs } from 'vant';
+import iconTeacher from '@/common/images/icon-teacher-default.png';
+import iconMusic from '@/common/images/icon-music.png';
+import iconPeople from '../lesson-list/images/icon-pople.png';
+import SkeletonIndexModal from './skeleton-index-modal';
+import Student from './components/student';
+import Teacher from './components/teacher';
+import Practice from './components/practice';
+import Evaluating from './components/evaluating';
+import { coursesStatus } from '@/helpers/constant';
+import request from '@/helpers/request';
+import { useRoute } from 'vue-router';
+
+export default defineComponent({
+  name: 'lesson-list-detail',
+  setup() {
+    const route = useRoute();
+    const forms = reactive({
+      courseId: route.query.courseId,
+      tabs: 'student' as 'student' | 'teacher' | 'practice' | 'evaluating',
+      loading: true,
+      headerDetail: {} as any
+    });
+
+    const getClassDetail = async () => {
+      try {
+        const { data } = await request.get(
+          '/api-web/schoolCourseSchedule/getCourseDetail',
+          {
+            params: {
+              courseId: forms.courseId
+            }
+          }
+        );
+        forms.headerDetail = data || {};
+      } catch {
+        //
+      } finally {
+        forms.loading = false;
+      }
+    };
+
+    onMounted(() => {
+      getClassDetail();
+    });
+
+    return () => (
+      <>
+        <MHeader />
+
+        <SkeletonIndexModal v-model:show={forms.loading} showCount={[1]}>
+          <CellGroup inset class={styles.lessonCellGroup}>
+            <Cell center class={styles.timeCell}>
+              {{
+                title: () => <span>{forms.headerDetail.classDate}</span>,
+                value: () => (
+                  <span>{coursesStatus[forms.headerDetail.courseStatus]}</span>
+                )
+              }}
+            </Cell>
+            <Cell center border={false}>
+              {{
+                icon: () => (
+                  <Image
+                    src={forms.headerDetail.teacherAvatar || iconTeacher}
+                    fit="contain"
+                    class={styles.iconStudent}
+                  />
+                ),
+                title: () => (
+                  <div class={styles.userInfo}>
+                    <p class={styles.name}>{forms.headerDetail.teacherName}</p>
+                    <p class={styles.subject}>
+                      {forms.headerDetail.courseName}
+                    </p>
+                  </div>
+                ),
+                value: () => (
+                  <div class={styles.people}>
+                    <img src={iconPeople} class={styles.iconPeople} />
+                    {forms.headerDetail.studentNum}人
+                  </div>
+                )
+              }}
+            </Cell>
+            <Cell center class={styles.musicCell}>
+              {{
+                title: () => (
+                  <div class={styles.musicInfo}>
+                    <Icon name={iconMusic} class={styles.iconMusic} />
+                    <span class="van-ellipsis">
+                      {forms.headerDetail.musicGroupName}
+                    </span>
+                  </div>
+                ),
+                label: () => (
+                  <Row class={styles.rowGroup}>
+                    <Col>
+                      正常出勤{' '}
+                      <span>{forms.headerDetail.attendanceNormalNum}</span>
+                    </Col>
+                    <Col>
+                      迟到
+                      <span class={styles.c}>
+                        {forms.headerDetail.attendanceLateNum}
+                      </span>
+                    </Col>
+                    <Col>
+                      请假
+                      <span class={styles.q}>
+                        {forms.headerDetail.attendanceLeaveNum}
+                      </span>
+                    </Col>
+                    <Col>
+                      旷课
+                      <span class={styles.k}>
+                        {forms.headerDetail.attendanceTruantNum}
+                      </span>
+                    </Col>
+                  </Row>
+                )
+              }}
+            </Cell>
+          </CellGroup>
+        </SkeletonIndexModal>
+
+        <div class={styles.tabs}>
+          <Tabs v-model:active={forms.tabs} lazyRender>
+            <Tab title="学员考勤" name="student">
+              <Student />
+            </Tab>
+            <Tab title="老师考勤" name="teacher">
+              <Teacher />
+            </Tab>
+            {forms.headerDetail.homeworkType === 'HOMEWORK' && (
+              <Tab title="练习内容" name="practice">
+                <Practice courseId={forms.courseId} />
+              </Tab>
+            )}
+
+            {forms.headerDetail.homeworkType === 'LESSON' && (
+              <Tab title="评测内容" name="evaluating">
+                <Evaluating
+                  lessonExaminationId={forms.headerDetail.homeworkId}
+                />
+              </Tab>
+            )}
+          </Tabs>
+        </div>
+      </>
+    );
+  }
+});

BIN
src/views/lesson-list/images/icon-member.png


BIN
src/views/lesson-list/images/icon-no-status.png


BIN
src/views/lesson-list/images/icon-not-standard.png


BIN
src/views/lesson-list/images/icon-pople.png


BIN
src/views/lesson-list/images/icon-standard.png


+ 174 - 0
src/views/lesson-list/index.module.less

@@ -0,0 +1,174 @@
+.lessonCellGroup {
+  margin-top: 12px;
+
+  .timeCell {
+    :global {
+      .van-cell__title {
+        flex: 0 auto;
+      }
+
+      .van-cell__value {
+        color: var(--k-gray-4);
+      }
+    }
+  }
+
+  .iconStudent {
+    margin-right: 10px;
+    width: 48px;
+    height: 48px;
+    overflow: hidden;
+    flex-shrink: 0;
+  }
+
+  .userInfo {
+    font-size: 14px;
+    color: var(--k-gray-3);
+    line-height: 20px;
+
+    .name {
+      font-size: 16px;
+      font-weight: 600;
+      color: var(--k-gray-1);
+      line-height: 22px;
+    }
+  }
+
+  .people {
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    font-size: 14px;
+    color: var(--k-gray-1);
+
+    .iconPeople {
+      width: 18px;
+      height: 16px;
+      margin-right: 4px;
+    }
+  }
+
+  .musicCell {
+    padding-top: 0;
+  }
+
+  .musicInfo {
+    display: inline-flex;
+    align-items: center;
+    font-size: 13px;
+    color: var(--k-gray-3);
+    line-height: 18px;
+    padding: 4px 10px;
+    background: rgba(200, 200, 200, 0.2);
+    border-radius: 15px;
+
+    .iconMusic {
+      font-size: 18px;
+      margin-right: 6px;
+    }
+
+    span {
+      max-width: 200px;
+    }
+  }
+
+  .rowGroup {
+    margin-top: 10px;
+    font-size: 12px;
+    color: var(--k-gray-3);
+
+    span {
+      padding-left: 4px;
+      font-size: 20px;
+      font-family: DINAlternate-Bold, DINAlternate;
+      font-weight: bold;
+      color: var(--k-font-primary);
+
+      &.c {
+        color: #4498F5;
+      }
+
+      &.q {
+        color: #F08226;
+      }
+
+      &.k {
+        color: #FC1A19;
+      }
+    }
+
+    :global {
+      .van-col {
+        position: relative;
+        padding-right: 10px;
+        margin-right: 10px;
+
+        &::after {
+          display: inline-block;
+          position: absolute;
+          right: 0;
+          top: 3px;
+          content: ' ';
+          width: 1px;
+          height: 14px;
+          background: #E9E9E9;
+          border-radius: 1px;
+        }
+
+        &:last-child {
+          margin-right: 0;
+          padding-right: 0;
+        }
+      }
+    }
+  }
+
+  .skeletonClass {
+    display: flex;
+    justify-content: flex-end;
+  }
+}
+
+
+.tabs {
+  margin: 12px 13px;
+  border-radius: 10px;
+  overflow: hidden;
+
+  :global {
+    // .van-tabs {
+    //   position: fixed;
+    //   left: 0;
+    //   right: 0;
+    //   top: var(--header-height);
+    // }
+
+    // .van-tabs__content {
+    //   height: calc(100vh - var(--header-height) - var(--van-tabs-line-height));
+    // }
+
+    .van-tab {
+      font-size: 16px !important;
+      z-index: 1;
+    }
+
+    .van-tab__panel {
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+    }
+
+    .van-pull-refresh {
+      height: 100%;
+    }
+
+
+    .van-tabs__line {
+      bottom: 26px;
+      width: 64px;
+      height: 6px;
+      background: linear-gradient(250deg, rgba(45, 199, 170, 0.22) 0%, #2DC7AA 100%);
+      z-index: 0;
+    }
+  }
+}

+ 146 - 0
src/views/lesson-list/index.tsx

@@ -0,0 +1,146 @@
+import { defineComponent, onMounted, reactive } from 'vue';
+import styles from './index.module.less';
+import MSticky from '@/components/m-sticky';
+import MHeader from '@/components/m-header';
+import { Cell, CellGroup, Col, Icon, Image, Row } from 'vant';
+import iconTeacher from '@/common/images/icon-teacher-default.png';
+import iconMusic from '@/common/images/icon-music.png';
+import iconPeople from '../lesson-list/images/icon-pople.png';
+import SkeletonIndexModal from './skeleton-index-modal';
+import { useRoute, useRouter } from 'vue-router';
+import request from '@/helpers/request';
+import { coursesStatus } from '@/helpers/constant';
+import MEmpty from '@/components/m-empty';
+
+export default defineComponent({
+  name: 'lesson-list',
+  setup() {
+    const router = useRouter();
+    const route = useRoute();
+    const forms = reactive({
+      classDate: route.query.classDate,
+      loading: true,
+      dataShow: true,
+      list: [] as any[]
+    });
+
+    const getList = async () => {
+      try {
+        const { data } = await request.get(
+          '/api-web/schoolCourseSchedule/queryListClassesForDay',
+          {
+            params: {
+              classDate: forms.classDate
+            }
+          }
+        );
+        forms.list = data || [];
+      } catch {
+        //
+      } finally {
+        forms.loading = false;
+        forms.dataShow = forms.list.length ? true : false;
+      }
+    };
+
+    onMounted(() => {
+      getList();
+    });
+    return () => (
+      <div>
+        <MSticky position="top">
+          <MHeader />
+        </MSticky>
+
+        <SkeletonIndexModal v-model:show={forms.loading}>
+          {forms.dataShow ? (
+            forms.list.map((item: any) => (
+              <CellGroup
+                inset
+                class={styles.lessonCellGroup}
+                onClick={() => {
+                  router.push({
+                    path: '/lesson-detail',
+                    query: {
+                      courseId: item.courseId,
+                      teacherId: item.teacherId
+                    }
+                  });
+                }}>
+                <Cell center class={styles.timeCell}>
+                  {{
+                    title: () => <span>{item.classDate}</span>,
+                    value: () => <span>{coursesStatus[item.courseStatus]}</span>
+                  }}
+                </Cell>
+                <Cell center border={false}>
+                  {{
+                    icon: () => (
+                      <Image
+                        src={item.teacherAvatar || iconTeacher}
+                        fit="contain"
+                        class={styles.iconStudent}
+                      />
+                    ),
+                    title: () => (
+                      <div class={styles.userInfo}>
+                        <p class={styles.name}>{item.teacherName}</p>
+                        <p class={styles.subject}>{item.courseName}</p>
+                      </div>
+                    ),
+                    value: () => (
+                      <div class={styles.people}>
+                        <img src={iconPeople} class={styles.iconPeople} />
+                        {item.studentNum}人
+                      </div>
+                    )
+                  }}
+                </Cell>
+                <Cell center class={styles.musicCell}>
+                  {{
+                    title: () => (
+                      <div class={styles.musicInfo}>
+                        <Icon name={iconMusic} class={styles.iconMusic} />
+                        <span class="van-ellipsis">{item.musicGroupName}</span>
+                      </div>
+                    ),
+                    label: () => (
+                      <Row class={styles.rowGroup}>
+                        <Col>
+                          正常出勤 <span>{item.attendanceNormalNum}</span>
+                        </Col>
+                        <Col>
+                          迟到
+                          <span class={styles.c}>{item.attendanceLateNum}</span>
+                        </Col>
+                        <Col>
+                          请假
+                          <span class={styles.q}>
+                            {item.attendanceLeaveNum}
+                          </span>
+                        </Col>
+                        <Col>
+                          旷课
+                          <span class={styles.k}>
+                            {item.attendanceTruantNum}
+                          </span>
+                        </Col>
+                      </Row>
+                    )
+                  }}
+                </Cell>
+              </CellGroup>
+            ))
+          ) : (
+            <MEmpty
+              style={{
+                minHeight: `calc(100vh - var(--header-height))`
+              }}
+              description="暂无数据"
+            />
+          )}
+        </SkeletonIndexModal>
+      </div>
+    );
+  }
+});

+ 105 - 0
src/views/lesson-list/skeleton-index-modal.tsx

@@ -0,0 +1,105 @@
+import {
+  Cell,
+  CellGroup,
+  Skeleton,
+  SkeletonImage,
+  SkeletonParagraph
+} from 'vant';
+import { defineComponent, onMounted, reactive, watch } from 'vue';
+import styles from './index.module.less';
+
+export default defineComponent({
+  name: 'skeleton-modal',
+  props: {
+    show: {
+      type: Boolean,
+      default: false
+    },
+    showCount: {
+      type: Array,
+      default: () => [1, 2, 3, 4, 5]
+    }
+  },
+  setup(props, { slots }) {
+    const forms = reactive({
+      loading: false
+    });
+
+    onMounted(() => {
+      forms.loading = props.show;
+    });
+
+    watch(
+      () => props.show,
+      () => {
+        forms.loading = props.show;
+      }
+    );
+    return () => (
+      <Skeleton loading={forms.loading} style="flex-wrap: wrap">
+        {{
+          template: () => (
+            <div
+              style={{
+                height: `calc(100vh - var(--header-height))`,
+                overflow: 'hidden',
+                width: '100%'
+              }}>
+              {props.showCount.map(() => (
+                <CellGroup inset class={styles.lessonCellGroup}>
+                  <Cell center class={styles.timeCell}>
+                    {{
+                      title: () => <SkeletonParagraph rowWidth={'40%'} />,
+                      value: () => <SkeletonParagraph rowWidth={'40%'} />
+                    }}
+                  </Cell>
+                  <Cell center border={false} valueClass={styles.skeletonClass}>
+                    {{
+                      icon: () => <SkeletonImage class={styles.iconStudent} />,
+                      title: () => (
+                        <div class={styles.userInfo}>
+                          <SkeletonParagraph
+                            class={styles.name}
+                            rowWidth={'40%'}
+                          />
+                          <SkeletonParagraph
+                            class={styles.subject}
+                            rowWidth={'50%'}
+                          />
+                        </div>
+                      ),
+                      value: () => (
+                        <SkeletonParagraph
+                          class={styles.people}
+                          rowWidth={'40%'}
+                        />
+                      )
+                    }}
+                  </Cell>
+                  <Cell center class={styles.musicCell}>
+                    {{
+                      title: () => (
+                        <SkeletonParagraph
+                          class={styles.musicInfo}
+                          rowWidth={'40%'}
+                          style={{ marginTop: '10px' }}
+                        />
+                      ),
+                      label: () => (
+                        <SkeletonParagraph
+                          class={styles.rowGroup}
+                          rowWidth={'80%'}
+                        />
+                      )
+                    }}
+                  </Cell>
+                </CellGroup>
+              ))}
+            </div>
+          ),
+          default: () => slots.default && slots.default()
+        }}
+      </Skeleton>
+    );
+  }
+});

+ 1 - 1
src/views/patrol-evaluation/index.tsx

@@ -39,7 +39,7 @@ export default defineComponent({
         page: 1,
         rows: 20
       },
-      statusValue: 'term',
+      statusValue: 'week',
       statusColumns: [
         { text: '本周', value: 'week' },
         { text: '本月', value: 'month' },

+ 122 - 122
src/views/schedule-manage/index.module.less

@@ -1,149 +1,149 @@
 .schedule-manage {
-    :global {
-        .van-tabs {
-            position: fixed;
-            left: 0;
-            right: 0;
-            top: var(--header-height);
-        }
-
-        .van-tabs__content {
-            height: calc(100vh - var(--header-height) - var(--van-tabs-line-height));
-        }
-
-        .van-tab__panel {
-            height: 100%;
-            display: flex;
-            flex-direction: column;
-        }
-
-        .van-pull-refresh {
-            height: 100%;
-        }
-
-        .van-empty {
-            // height: 100% !important;
-        }
-
-        .van-tabs__line {
-            bottom: 26px;
-            width: 45px;
-            height: 6px;
-            background: linear-gradient(250deg, rgba(45, 199, 170, 0.22) 0%, #2DC7AA 100%);
-            z-index: 0;
-        }
+  :global {
+    .van-tabs {
+      position: fixed;
+      left: 0;
+      right: 0;
+      top: var(--header-height);
     }
+
+    .van-tabs__content {
+      height: calc(100vh - var(--header-height) - var(--van-tabs-line-height));
+    }
+
+    .van-tab__panel {
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+    }
+
+    .van-pull-refresh {
+      height: 100%;
+    }
+
+    .van-empty {
+      // height: 100% !important;
+    }
+
+    .van-tabs__line {
+      bottom: 26px;
+      width: 45px;
+      height: 6px;
+      background: linear-gradient(250deg, rgba(45, 199, 170, 0.22) 0%, #2DC7AA 100%);
+      z-index: 0;
+    }
+  }
 }
 
 .list {
-    padding: 12px;
-    flex: 1;
-    overflow-y: auto;
+  padding: 12px;
+  flex: 1;
+  overflow-y: auto;
 
-    &::-webkit-scrollbar {
-        display: none;
-    }
+  &::-webkit-scrollbar {
+    display: none;
+  }
 }
 
 .listHide {
-    padding: 0;
-    overflow: hidden;
+  padding: 0;
+  overflow: hidden;
 }
 
 .listItem {
-    border-radius: 10px;
-    background-color: #fff;
-    overflow: hidden;
-    margin-bottom: 12px;
-
-    .itemTop {
-        display: flex;
-        align-items: center;
-        padding: 13px 12px;
-        border-bottom: 1px solid #F2F2F2;
-        font-size: 14px;
-
-        .icon {
-            display: block;
-            width: 17px;
-            height: 17px;
-        }
-
-        .timeTitle {
-            margin-left: 4px;
-            color: var(--text-color);
-        }
-
-        .time {
-            color: var(--text-color-2);
-        }
-
-        .timeBtn {
-            margin-left: auto;
-            font-weight: 600;
-
-            &.success {
-                color: var(--van-primary-color);
-            }
-        }
-    }
-
-    .iconTeacher {
-        width: 48px;
-        height: 48px;
-        margin-right: 8px;
-    }
-
-    :global {
-        .van-cell {
-            padding: 14px 12px;
-        }
-
-        .van-cell__title {
-            font-weight: bold;
-        }
-
-        .van-cell__value {
-            color: var(--text-color);
-        }
+  border-radius: 10px;
+  background-color: #fff;
+  overflow: hidden;
+  margin-bottom: 12px;
+
+  .itemTop {
+    display: flex;
+    align-items: center;
+    padding: 13px 12px;
+    border-bottom: 1px solid #F2F2F2;
+    font-size: 14px;
+
+    .icon {
+      display: block;
+      width: 17px;
+      height: 17px;
+    }
+
+    .timeTitle {
+      margin-left: 4px;
+      color: var(--text-color);
+    }
+
+    .time {
+      color: var(--text-color-2);
     }
+
+    .timeBtn {
+      margin-left: auto;
+      font-weight: 600;
+
+      &.success {
+        color: var(--van-primary-color);
+      }
+    }
+  }
+
+  .iconTeacher {
+    width: 48px;
+    height: 48px;
+    margin-right: 8px;
+  }
+
+  :global {
+    .van-cell {
+      padding: 14px 12px;
+    }
+
+    .van-cell__title {
+      font-weight: bold;
+    }
+
+    .van-cell__value {
+      color: var(--text-color);
+    }
+  }
 }
 
 .menubox {
-    display: flex;
-    align-items: center;
-    background-color: #fff;
+  display: flex;
+  align-items: center;
+  background-color: #fff;
 
-    :global {
-        .van-search {
-            flex: 1
-        }
+  :global {
+    .van-search {
+      flex: 1
+    }
 
-        .van-dropdown-menu {
-            min-width: 100px;
-        }
+    .van-dropdown-menu {
+      min-width: 100px;
+    }
 
-        .van-dropdown-menu__title {
-            font-size: 14px;
-            padding: 0 25px 0 12px;
+    .van-dropdown-menu__title {
+      font-size: 14px;
+      padding: 0 25px 0 12px;
 
-            &::after {
-                right: 10px;
-            }
-        }
+      &::after {
+        right: 10px;
+      }
     }
+  }
 }
 
 :global {
-    .van-dropdown-item {
-        .van-dropdown-item__content .van-dropdown-item__option .van-cell__title {
-            font-size: 16px;
-            color: var(--text-color);
-        }
-
-        .van-dropdown-item__content .van-dropdown-item__option--active {
-            color: var(--van-primary-color);
-        }
+  .van-dropdown-item {
+    .van-dropdown-item__content .van-dropdown-item__option .van-cell__title {
+      font-size: 16px;
+      color: var(--text-color);
+    }
 
+    .van-dropdown-item__content .van-dropdown-item__option--active {
+      color: var(--van-primary-color);
     }
+
+  }
 }

+ 1 - 1
src/views/teacher-attendance/amap-gps.tsx

@@ -253,7 +253,7 @@ export default defineComponent({
     onMounted(async () => {
       try {
         const { data } = await request.get(
-          '/api-web/sysConfig/queryByParamName',
+          '/api-web/open/school/queryByParamName',
           {
             params: {
               paramName: 'attendance_range'