Browse Source

Merge branch 'iteration-20240924' into jenkins

lex-xin 8 months ago
parent
commit
e8cd09cde3
35 changed files with 1948 additions and 993 deletions
  1. 1 1
      dev-dist/sw.js
  2. 8 1
      src/App.tsx
  3. BIN
      src/common/images/icon-question.png
  4. 15 14
      src/components/CSelect/index.tsx
  5. 25 8
      src/components/card-type/index.module.less
  6. 6 0
      src/components/card-type/index.tsx
  7. 18 0
      src/styles/index.less
  8. 39 0
      src/utils/dateFormat.ts
  9. 34 0
      src/utils/index.ts
  10. 9 0
      src/views/classList/api.ts
  11. 21 9
      src/views/classList/components/testRecode.tsx
  12. 8 8
      src/views/classList/index.module.less
  13. 7 0
      src/views/data-module/api.tsx
  14. 411 71
      src/views/home/components/practiceData.tsx
  15. 13 23
      src/views/home/components/practiceRanking.tsx
  16. 3 3
      src/views/home/components/trainData.tsx
  17. 9 1
      src/views/home/index2.module.less
  18. 15 0
      src/views/prepare-lessons/api.ts
  19. 11 32
      src/views/prepare-lessons/components/lesson-main/courseware-head/index.tsx
  20. 70 4
      src/views/prepare-lessons/components/lesson-main/courseware-presets/index.tsx
  21. 159 168
      src/views/prepare-lessons/components/lesson-main/courseware/addCourseware.tsx
  22. 0 5
      src/views/prepare-lessons/components/lesson-main/index.tsx
  23. 54 23
      src/views/prepare-lessons/components/lesson-main/train/assign-homework.tsx
  24. 1 1
      src/views/prepare-lessons/components/lesson-main/train/assign-student/index.module.less
  25. 412 398
      src/views/prepare-lessons/components/lesson-main/train/assign-student/index.tsx
  26. 0 25
      src/views/prepare-lessons/components/lesson-main/train/index.tsx
  27. 5 3
      src/views/prepare-lessons/components/resource-main/components/resource-item/index.tsx
  28. 0 1
      src/views/prepare-lessons/components/resource-main/components/resource-item/resource-search-group/index.tsx
  29. 2 0
      src/views/prepare-lessons/model/add-other-source/index.tsx
  30. 3 0
      src/views/prepare-lessons/model/select-resources/select-item/index.tsx
  31. 1 0
      src/views/prepare-lessons/model/subject-sync/index.tsx
  32. 9 0
      src/views/studentList/api.ts
  33. 402 77
      src/views/studentList/components/evaluationRecords.tsx
  34. 87 53
      src/views/studentList/components/practiceData.tsx
  35. 90 64
      src/views/studentList/index.module.less

+ 1 - 1
dev-dist/sw.js

@@ -82,7 +82,7 @@ define(['./workbox-88bf3160'], (function (workbox) { 'use strict';
     "revision": "3ca0b8505b4bec776b69afdba2768812"
   }, {
     "url": "index.html",
-    "revision": "0.2kkppsecoao"
+    "revision": "0.phhl6vcc60g"
   }], {});
   workbox.cleanupOutdatedCaches();
   workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {

+ 8 - 1
src/App.tsx

@@ -46,11 +46,18 @@ export default defineComponent({
       const appTheme = setting.appTheme;
       const lightenStr = lighten(setting.appTheme, 6);
 
+    //   errorColor: string;
+    // errorColorHover: string;
+    // errorColorPressed: string;
+    // errorColorSuppl: string;
       return {
         common: {
           primaryColor: appTheme,
           primaryColorHover: lightenStr,
-          primaryColorPressed: lightenStr
+          primaryColorPressed: lightenStr,
+          errorColor: '#F51C08',
+          errorColorHover: '#F51C08',
+          errorColorPressed: '#F51C08'
         },
         LoadingBar: {
           colorLoading: appTheme

BIN
src/common/images/icon-question.png


+ 15 - 14
src/components/CSelect/index.tsx

@@ -32,20 +32,21 @@ export default defineComponent({
             onUpdate:show={(flag: boolean) => {
               isFocus.value = flag;
             }}
-            v-slots={{
-              arrow: () =>
-                isFocus.value ? (
-                  <NImage
-                    class={styles.arrow}
-                    previewDisabled
-                    src={activeArrow}></NImage>
-                ) : (
-                  <NImage
-                    class={styles.arrow}
-                    previewDisabled
-                    src={arrow}></NImage>
-                )
-            }}></NSelect>
+            // v-slots={{
+            //   arrow: () =>
+            //     isFocus.value ? (
+            //       <NImage
+            //         class={styles.arrow}
+            //         previewDisabled
+            //         src={activeArrow}></NImage>
+            //     ) : (
+            //       <NImage
+            //         class={styles.arrow}
+            //         previewDisabled
+            //         src={arrow}></NImage>
+            //     )
+            // }}
+            ></NSelect>
         </div>
       </>
     );

+ 25 - 8
src/components/card-type/index.module.less

@@ -1,12 +1,4 @@
-:global {
-  .n-card--bordered {
-    border: 1Px solid rgba(202, 228, 244, 1) !important;
 
-    &:hover {
-      border: 1Px solid rgba(0, 122, 254, 1) !important;
-    }
-  }
-}
 
 .card-section-content {
   border-radius: 14px;
@@ -18,6 +10,8 @@
   //   }
   // }
 
+  
+
   // 图片禁止拖动
   img {
     -moz-user-select: none;
@@ -50,6 +44,29 @@
   display: inline-flex;
   transition: all .3s ease-in-out;
 
+  :global {
+    .n-card--bordered {
+      border: 1Px solid rgba(202, 228, 244, 1) !important;
+  
+      &:hover {
+        border: 1Px solid rgba(0, 122, 254, 1) !important;
+      }
+    }
+  }
+
+  &.isError {
+    :global {
+      .n-card--bordered {
+        border: 1Px solid #F51C08 !important;
+    
+        &:hover {
+          border: 1Px solid #F51C08 !important;
+          box-shadow: 0 0 0 2px rgba(245, 28, 8, 0.2);
+        }
+      }
+    }
+  }
+
   &.cardDrag {
     cursor: move;
 

+ 6 - 0
src/components/card-type/index.tsx

@@ -112,6 +112,11 @@ export default defineComponent({
     audioPlayTypeSize: {
       type: String as PropType<'default' | 'small'>,
       deafult: 'default'
+    },
+    /** 是否开始错误提示 border isError */
+    isError: {
+      type: Boolean,
+      default: false,
     }
   },
   /**
@@ -267,6 +272,7 @@ export default defineComponent({
         draggable={!props.draggable ? false : props.item.exist ? false : true}
         class={[
           styles['card-section'],
+          props.isError ? styles.isError : '',
           'card-section-container',
           !props.draggable ? '' : props.item.exist ? '' : styles.cardDrag
         ]}

+ 18 - 0
src/styles/index.less

@@ -123,6 +123,24 @@ body>.n-drawer-container-relative {
   --n-padding: 0 28px !important;
 }
 
+.searchDate, .searchDateDefault {
+  --n-padding: 0 28px !important;
+  background: linear-gradient( 312deg, #1B7AF8 0%, #3CBBFF 100%);
+  border-radius: 8px;
+  // line-height: 41px;
+  font-size: 18px !important;
+  font-weight: 600 !important;
+
+  .n-button__state-border, .n-button__border {
+    border: none !important;
+  }
+}
+
+.searchDateDefault {
+  background: #F1F2F6;
+  color: #1E2022;
+}
+
 // .n-data-table {
 //   border-radius: 10px 10px 0 0;
 //   overflow: hidden;

+ 39 - 0
src/utils/dateFormat.ts

@@ -104,6 +104,16 @@ export function formatTime(time: number) {
   return str;
 }
 
+export function getHours(time: number) {
+  const minutes = Math.floor(time / 60 / 60);
+  return minutes;
+}
+
+export function getLastMinutes(time: number) {
+  const minutes = Math.floor(time / 60);
+  return Math.floor(minutes % 60)
+}
+
 export function getMinutes(time: number) {
   const minutes = Math.floor(time / 60);
   return minutes;
@@ -113,3 +123,32 @@ export function getSecend(time: number) {
   const seconds = Math.floor(time % 60);
   return seconds;
 }
+
+
+// 秒转时分秒
+export function formateSeconds(endTime: string, pad = 2) {
+  let secondTime = parseInt(endTime) //将传入的秒的值转化为Number
+  let min = 0 // 初始化分
+  let h = 0 // 初始化小时
+  let result = ''
+  if (secondTime >= 60) {
+    //如果秒数大于等于60,将秒数转换成整数
+    min = parseInt(secondTime / 60 + '') //获取分钟,除以60取整数,得到整数分钟
+    secondTime = parseInt((secondTime % 60) + '') //获取秒数,秒数取佘,得到整数秒数
+    if (min >= 60) {
+      //如果分钟大于等于60,将分钟转换成小时
+      h = parseInt(min / 60 + '') //获取小时,获取分钟除以60,得到整数小时
+      min = parseInt((min % 60) + '') //获取小时后取佘的分,获取分钟除以60取佘的分
+    }
+  }
+  if (h) {
+    result = `${h.toString().padStart(pad, '0')}时${min.toString().padStart(pad, '0')}分${secondTime
+      .toString()
+      .padStart(pad, '0')}秒`
+  } else if (min) {
+    result = `${min.toString().padStart(pad, '0')}分${secondTime.toString().padStart(pad, '0')}秒`
+  } else {
+    result = `${secondTime.toString().padStart(pad, '0')}秒`
+  }
+  return result
+}

+ 34 - 0
src/utils/index.ts

@@ -787,4 +787,38 @@ export const getAuthForAdmin = () => {
   //   sessionStorage.removeItem('authLoadNum');
   //   storage.remove(ACCESS_TOKEN_ADMIN);
   // }
+}
+
+
+export function convertToChineseNumeral(num: number) {
+  if (num == 10) {
+    return '十'
+  } else if (num == 1) {
+    return '一'
+  }
+  const digits = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']
+  const units = ['', '十', '百', '千', '万']
+  let result = ''
+  let numStr = num.toString()
+  for (let i = 0; i < numStr.length; i++) {
+    let digit = parseInt(numStr.charAt(i))
+    let unit = units[numStr.length - i - 1]
+    if (digit === 0) {
+      // 当前数字为0时不需要输出汉字,但需要考虑上一个数字是否为0,避免出现连续的零
+      if (result.charAt(result.length - 1) !== '零') {
+        result += '零'
+      }
+    } else {
+      result += digits[digit] + unit
+    }
+  }
+  // 对于一些特殊的数字,如10、100等,需要在最前面加上“一”
+  if (result.charAt(0) === '一') {
+    result = result.substr(1, result.length)
+  } else if (result.charAt(0) === '百') {
+    result = '一' + result
+  } else if (result.charAt(0) === '千') {
+    result = '一' + result
+  }
+  return result
 }

+ 9 - 0
src/views/classList/api.ts

@@ -169,6 +169,15 @@ export const getTrainingStatList = (params: any) => {
   });
 };
 
+/**
+ * @description: 后台练习统计列表
+ */
+export const api_practiceStatPage = (params: object) => {
+  return request.post('/edu-app/musicPracticeRecordStat/practiceStatPage', {
+      data: params
+    })
+}
+
 /***
  * 创建班级群聊
  */

+ 21 - 9
src/views/classList/components/testRecode.tsx

@@ -23,7 +23,10 @@ import {
   getNowDateAndSunday,
   getTimes,
   getMinutes,
-  getSecend
+  getSecend,
+  formateSeconds,
+  getHours,
+  getLastMinutes
 } from '/src/utils/dateFormat';
 import { getTestList, getTrainingStat } from '../api';
 import CDatePicker from '/src/components/CDatePicker';
@@ -238,12 +241,7 @@ export default defineComponent({
         return (
           <>
             {row.practiceDuration
-              ? getMinutes(row.practiceDuration) > 0
-                ? getMinutes(row.practiceDuration) +
-                  '分' +
-                  getSecend(row.practiceDuration) +
-                  '秒'
-                : getSecend(row.practiceDuration) + '秒'
+              ? formateSeconds(row.practiceDuration, 1)
               : 0 + '秒'}
           </>
         );
@@ -451,12 +449,26 @@ export default defineComponent({
             <NGi>
               <div class={styles.TrainDataItem}>
                 <p class={styles.TrainDataItemTitle}>
-                  {getMinutes(state.testInfo.practiceDurationAvg) > 0 ? (
+                  {getHours(state.testInfo.practiceDurationAvg) > 0 ? (
                     <div>
                       <span>
                         <NNumberAnimation
                           from={0}
-                          to={getMinutes(
+                          to={getHours(
+                            state.testInfo.practiceDurationAvg
+                          )}></NNumberAnimation>
+                      </span>
+                      <i style={{ width: '4px', display: 'inline-block' }}></i>
+                      时
+                      <i style={{ width: '4px', display: 'inline-block' }}></i>
+                    </div>
+                  ) : null}
+                  {getHours(state.testInfo.practiceDurationAvg) > 0 || getLastMinutes(state.testInfo.practiceDurationAvg) > 0 ? (
+                    <div>
+                      <span>
+                        <NNumberAnimation
+                          from={0}
+                          to={getLastMinutes(
                             state.testInfo.practiceDurationAvg
                           )}></NNumberAnimation>
                       </span>{' '}

+ 8 - 8
src/views/classList/index.module.less

@@ -433,12 +433,12 @@
   display: flex;
   flex-direction: row;
   align-items: center;
-  margin-bottom: 32px;
+  margin-bottom: 24px;
 
   .teacherHeader {
-    width: 100px;
-    height: 100px;
-    padding: 4px;
+    width: 70px;
+    height: 70px;
+    padding: 2px;
     border-radius: 99px;
     background: linear-gradient(228deg,
         rgba(2, 186, 255, 1),
@@ -455,13 +455,13 @@
       flex-direction: row;
       align-items: center;
       justify-content: center;
-      padding: 4px;
+      padding: 3px;
     }
   }
 
   .teacherHeaderImg {
-    width: 84px;
-    height: 84px;
+    width: 60px;
+    height: 60px;
     border-radius: 50%;
     overflow: hidden;
   }
@@ -472,7 +472,7 @@
       line-height: 30px;
       font-weight: 600;
       color: #131415;
-      margin-bottom: 12px;
+      margin-bottom: 6px;
     }
 
     p {

+ 7 - 0
src/views/data-module/api.tsx

@@ -24,6 +24,13 @@ export const getTestStat = (params: any) => {
   });
 };
 
+export const getPracticePageStat = (params: any) => {
+  return request.post('/edu-app/musicPracticeRecordStat/practicePageStat', {
+    data: params
+    // requestType: 'form'
+  });
+};
+
 
 export const getTrainingRanking = (params: any) => {
   return request.post('/edu-app/musicPracticeRecordStat/trainingRanking', {

+ 411 - 71
src/views/home/components/practiceData.tsx

@@ -1,15 +1,19 @@
-import { Ref, computed, defineComponent, onMounted, reactive, ref } from 'vue';
+import { Ref, computed, defineComponent,  reactive, ref } from 'vue';
 import styles from '../index2.module.less';
-import { NButton, NDataTable, NNumberAnimation } from 'naive-ui';
-import numeral from 'numeral';
+import { NButton, NDataTable, NIcon, NNumberAnimation, NTooltip, useMessage } from 'naive-ui';
 import { useECharts } from '@/hooks/web/useECharts';
-import Pagination from '/src/components/pagination';
-import { getTestStat } from '@/views/data-module/api';
-import { getMinutes, getSecend, getTimes } from '/src/utils/dateFormat';
-import { useRoute, useRouter } from 'vue-router';
-import { getTrainingStatList } from '../../classList/api';
-import dayjs from 'dayjs';
+// import Pagination from '/src/components/pagination';
+import { getPracticePageStat, getTestStat } from '@/views/data-module/api';
+import { formateSeconds, getHours, getLastMinutes, getMinutes, getSecend, getTimes } from '/src/utils/dateFormat';
+import { api_practiceStatPage } from '../../classList/api';
 import TheEmpty from '/src/components/TheEmpty';
+import iconSortDefault from '@/common/images/icon-sort-default.png';
+import iconSortDesc from '@/common/images/icon-sort-desc.png';
+import iconSortAsc from '@/common/images/icon-sort-asc.png';
+import { convertToChineseNumeral } from '/src/utils';
+import { useRouter } from 'vue-router';
+import { setTabsCaches } from '/src/hooks/use-async';
+import iconQuestion from '/src/common/images/icon-question.png'
 export default defineComponent({
   name: 'home-practiceData',
   props: {
@@ -19,12 +23,19 @@ export default defineComponent({
     }
   },
   setup(props, { expose }) {
+    const router = useRouter()
+    const message = useMessage()
     const chartRef = ref<HTMLDivElement | null>(null);
     const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
     const practiceFlag = ref(true);
     const payForm = reactive({
       height: '360px',
       width: '100%',
+      practiceDuration: 0,
+      evaluateUserCount: 0,
+      evaluateFrequency: 0,
+      publishUserCount: 0,
+      publishCount: 0,
       practiceUserCount: 0,
       paymentAmount: 0,
       practiceDurationAvg: 0,
@@ -41,53 +52,280 @@ export default defineComponent({
         rows: 10,
         pageTotal: 4
       },
+      searchForm: {
+        orderBy: null as any,
+        sort: null as any,
+      },
       tableList: [] as any,
       goCourseVisiable: false
     });
     const currentTimer = computed(() => {
       return props.timer;
     });
+
+    const toolTitleTips = (title: string, item: any) => {
+      return <NTooltip showArrow={false} placement="top-start">
+      {{
+        trigger: () => (
+          <div class={styles.cell}>
+            {title}
+            <img
+              class={styles.sortIcon}
+              src={
+                item.sortOrder === 'descend'
+                  ? iconSortDesc
+                  : item.sortOrder === 'ascend'
+                  ? iconSortAsc
+                  : iconSortDefault
+              }
+            />
+          </div>
+        ),
+        default:
+          item.sortOrder === 'descend'
+            ? '点击升序'
+            : item.sortOrder === 'ascend'
+            ? '取消排序'
+            : '点击降序'
+      }}
+    </NTooltip>
+    }
+
+    const practiceDurationRef = reactive({
+      title() {
+        return (
+          toolTitleTips('练习总时长', practiceDurationRef)
+        );
+      },
+      key: 'practiceDuration',
+      sorter: true,
+      sortOrder: false as any,
+      render(row: any) {
+        return <>{formateSeconds((row.practiceDuration as any) || 0)}</>
+      }
+    });
+
+    const practiceDaysRef = reactive({
+      title() {
+        return (
+          toolTitleTips('练习天数', practiceDaysRef)
+        );
+      },
+      key: 'practiceDays',
+      sorter: true,
+      sortOrder: false as any
+    });
+
+    const practiceDurationAvgRef = reactive({
+      title() {
+        return (
+          toolTitleTips('平均练习时长', practiceDurationAvgRef)
+        );
+      },
+      key: 'practiceDurationAvg',
+      sorter: true,
+      sortOrder: false as any,
+      render(row: any) {
+        return <>{formateSeconds((row.practiceDurationAvg as any) || 0)}</>
+      }
+    });
+
+    const evaluateFrequencyRef = reactive({
+      title() {
+        return (
+          toolTitleTips('评测次数', evaluateFrequencyRef)
+        );
+      },
+      key: 'evaluateFrequency',
+      sorter: true,
+      sortOrder: false as any
+    });
+
+    const publishCountRef = reactive({
+      title() {
+        return (
+          toolTitleTips('作品数量', publishCountRef)
+        );
+      },
+      key: 'publishCount',
+      sorter: true,
+      sortOrder: false as any
+    });
+
+    const publishScoreRef = reactive({
+      title() {
+        return (
+          toolTitleTips('最新作品分数', publishScoreRef)
+        );
+      },
+      key: 'publishScore',
+      sorter: true,
+      sortOrder: false as any
+    });
+
+    const publishTimeRef = reactive({
+      title() {
+        return (
+          toolTitleTips('最新作品时间', publishTimeRef)
+        );
+      },
+      key: 'publishTime',
+      sorter: true,
+      sortOrder: false as any
+    });
+
+    const copyTo = (text: string) => {
+      const input = document.createElement('input');
+      input.value = text;
+      document.body.appendChild(input);
+      input.select();
+      input.setSelectionRange(0, input.value.length);
+      document.execCommand('Copy');
+      document.body.removeChild(input);
+      message.success('复制成功');
+    };
     const columns = () => {
       return [
         {
-          title: '日期',
-          key: 'date'
-        },
-        {
-          title: '练习人数',
-          key: 'practiceUserCount',
-          render(row: any) {
-            return <>{row.practiceUserCount}人</>;
+          title: '学生姓名',
+          key: 'studentName',
+          render: (row: any) => {
+            return (
+              <NTooltip showArrow={false} placement="top-start">
+                {{
+                  trigger: () => (
+                    <div
+                      style={{ userSelect: 'all', cursor: 'pointer' }}
+                      onClick={() => copyTo(row.studentName)}>
+                      {row.studentName}
+                    </div>
+                  ),
+                  default: '点击复制'
+                }}
+              </NTooltip>
+            );
           }
         },
         {
-          title: '平均每天练习时长',
-          key: 'practiceDuration',
+          title: '年级班级',
+          key: 'date',
           render(row: any) {
             return (
               <>
-                {' '}
-                <>
-                  {row.practiceDuration
-                    ? getMinutes(row.practiceDuration) > 0
-                      ? getMinutes(row.practiceDuration) +
-                        '分' +
-                        getSecend(row.practiceDuration) +
-                        '秒'
-                      : getSecend(row.practiceDuration) + '秒'
-                    : 0 + '分钟'}
-                </>
+                {row.currentGradeNum && row.currentClass
+                  ? convertToChineseNumeral(row.currentGradeNum) + '年级' + row.currentClass + '班'
+                  : ''}
               </>
-            );
+            )
           }
+        },
+        {
+          title: '乐器',
+          key: 'instrumentName'
+        },
+        practiceDurationRef,
+        practiceDaysRef,
+        practiceDurationAvgRef,
+        evaluateFrequencyRef,
+        {
+          title: () => <span style={{ display: 'flex', alignItems: 'center' }}>发布作品 <NTooltip showArrow={false}>
+          {{
+            trigger: () => (
+                <img src={iconQuestion} style={{ width: '16px', height: '16px', marginLeft: '4px', cursor: 'pointer' }}  />
+            ),
+            default: () => '筛选时间段内评测是否发布作品'
+          }}
+        </NTooltip></span>,
+          key: 'publishFlag',
+          render: (row: any) => row.publishFlag ? '是' : '否'
+        },
+        publishCountRef,
+        publishScoreRef,
+        publishTimeRef,
+        {
+          title: '操作',
+          key: 'titleImg',
+          render: (row: any) => (
+            <NButton
+              type="primary"
+              text
+              onClick={() => {
+                setTabsCaches('evaluatingRcode', 'tabName', {
+                  path: '/studentDetail'
+                });
+                router.push({
+                  path: '/studentDetail',
+                  query: { studentId: row.studentId, studentName: row.studentName, times: JSON.stringify(currentTimer.value) }
+                });
+              }}
+            >
+              详情
+            </NButton>
+          )
         }
       ];
     };
-    const getList = async () => {
+
+    // 统计排序
+    const handleSorterChange = (sorter: any) => {
+      if (!sorter.order) {
+        state.searchForm.orderBy = '' as string
+        state.searchForm.sort = '' as string
+        practiceDurationRef.sortOrder = false
+        practiceDaysRef.sortOrder = false
+        practiceDurationAvgRef.sortOrder = false
+        evaluateFrequencyRef.sortOrder = false
+        publishCountRef.sortOrder = false
+        publishScoreRef.sortOrder = false
+        publishTimeRef.sortOrder = false
+      } else {
+        state.searchForm.orderBy = sorter.columnKey
+        practiceDurationRef.sortOrder = false
+        practiceDaysRef.sortOrder = false
+        practiceDurationAvgRef.sortOrder = false
+        evaluateFrequencyRef.sortOrder = false
+        publishCountRef.sortOrder = false
+        publishScoreRef.sortOrder = false
+        publishTimeRef.sortOrder = false
+        if (sorter.columnKey == 'practiceDuration') {
+          practiceDurationRef.sortOrder = sorter.order
+        }
+        if (sorter.columnKey == 'practiceDays') {
+          practiceDaysRef.sortOrder = sorter.order
+        }
+
+        if (sorter.columnKey == 'practiceDurationAvg') {
+          practiceDurationAvgRef.sortOrder = sorter.order
+        }
+
+        if (sorter.columnKey == 'evaluateFrequency') {
+          evaluateFrequencyRef.sortOrder = sorter.order
+        }
+
+        if (sorter.columnKey == 'publishCount') {
+          publishCountRef.sortOrder = sorter.order
+        }
+
+        if (sorter.columnKey == 'publishScore') {
+          publishScoreRef.sortOrder = sorter.order
+        }
+
+        if (sorter.columnKey == 'publishTime') {
+          publishTimeRef.sortOrder = sorter.order
+        }
+
+        state.searchForm.sort = sorter.order == 'ascend' ? 'asc' : 'desc'
+      }
+      getList2()
+    }
+
+    const getList2 = async () => {
+      state.loading = true
       try {
-        const res = await getTrainingStatList({
+        const res = await api_practiceStatPage({
           page: 1,
           rows: 999,
+          ...state.searchForm,
           ...getTimes(
             currentTimer.value,
             ['startTime', 'endTime'],
@@ -95,6 +333,16 @@ export default defineComponent({
           )
         });
 
+        state.tableList = res.data.rows;
+      } catch (e) {
+        console.log(e);
+      }
+      state.loading = false
+    };
+
+    const getTestStatList = async () => {
+      state.loading = true
+      try {
         const res2 = await getTestStat({
           page: 1,
           rows: 999,
@@ -104,10 +352,7 @@ export default defineComponent({
             'YYYY-MM-DD'
           )
         });
-        state.tableList = res.data.rows;
 
-        payForm.practiceDurationAvg = res2.data.practiceDurationAvg;
-        payForm.practiceUserCount = res2.data.practiceUserCount;
         payForm.dateList = res2.data.trainingStatDetailList.map((item: any) => {
           return item.date;
         });
@@ -117,10 +362,43 @@ export default defineComponent({
         });
 
         setChart();
-      } catch (e) {
-        console.log(e);
+      } catch {
+        // 
       }
-    };
+      state.loading = false
+    }
+
+    const getPracticePageStatList = async () => {
+      state.loading = true
+      try {
+        const {data} = await getPracticePageStat({
+          page: 1,
+          rows: 999,
+          ...getTimes(
+            currentTimer.value,
+            ['startTime', 'endTime'],
+            'YYYY-MM-DD'
+          )
+        });
+        payForm.practiceDuration = data.practiceDuration;
+        payForm.practiceDurationAvg = data.practiceDurationAvg;
+        payForm.practiceUserCount = data.practiceUserCount;
+        payForm.evaluateUserCount = data.evaluateUserCount
+        payForm.evaluateFrequency = data.evaluateFrequency
+        payForm.publishUserCount = data.publishUserCount
+        payForm.publishCount = data.publishCount
+      } catch {
+        // 
+      }
+      state.loading = false
+    }
+
+    const getList = async () => {
+      await getPracticePageStatList()
+      await getTestStatList()
+      await getList2()
+    }
+
     expose({ getList });
     const setChart = () => {
       setOptions({
@@ -174,33 +452,19 @@ export default defineComponent({
         },
         series: [
           {
-            // smooth: true,
             data: payForm.timeList,
-            symbolSize: 10,
-            type: 'line',
-            symbol: 'circle',
-            smooth: true,
-            // barWidth: '48px',
-            // label: {
-            //   // 柱图头部显示值
-            //   show: true,
-            //   position: 'top',
-            //   color: '#333',
-            //   fontSize: '12px',
-            //   fontWeight: 600
-            // },
+            type: 'bar',
+            barWidth: '48px',
 
             itemStyle: {
               normal: {
                 //这里设置柱形图圆角 [左上角,右上角,右下角,左下角]
                 barBorderRadius: [8, 8, 0, 0],
-                color: '#3583FA'
+                color: '#CDE5FF'
               },
               emphasis: {
+                focus: 'series',
                 color: '#3583FA' //hover时改变柱子颜色
-                // borderWidth: 4,
-                // borderColor: 'rgba(213, 233, 255,.4)',
-                // borderType: 'solid'
               }
             } as any
           }
@@ -221,20 +485,10 @@ export default defineComponent({
             return item;
           }
         }
-        // dataZoom: [
-        //   {
-        //     type: 'slider',
-        //     start: 5,
-        //     end: 100,
-        //     filterMode: 'empty'
-        //   }
-        // ]
       });
     };
 
-    onMounted(() => {
-      getList();
-    });
+    getList();
 
     return () => (
       <>
@@ -256,12 +510,25 @@ export default defineComponent({
               </div>
               <div class={styles.TrainDataItem}>
                 <p class={styles.TrainDataItemTitle}>
-                  {getMinutes(payForm.practiceDurationAvg) > 0 ? (
+                  {getHours(payForm.practiceDurationAvg) > 0 ? (
+                      <div>
+                        <span>
+                          <NNumberAnimation
+                            from={0}
+                            to={getHours(
+                              payForm.practiceDurationAvg
+                            )}></NNumberAnimation>
+                        </span>
+                        时
+                      </div>
+                    ) : null}
+                    
+                  {getHours(payForm.practiceDurationAvg) > 0 || getLastMinutes(payForm.practiceDurationAvg) > 0 ? (
                     <div>
                       <span>
                         <NNumberAnimation
                           from={0}
-                          to={getMinutes(
+                          to={getLastMinutes(
                             payForm.practiceDurationAvg
                           )}></NNumberAnimation>
                       </span>
@@ -281,6 +548,78 @@ export default defineComponent({
                 </p>
                 <p class={styles.TrainDataItemsubTitle}>平均每天练习时长</p>
               </div>
+
+              <div class={styles.TrainDataItem}>
+                <p class={styles.TrainDataItemTitle}>
+                  {getHours(payForm.practiceDuration) > 0 ? (
+                      <div>
+                        <span>
+                          <NNumberAnimation
+                            from={0}
+                            to={getHours(
+                              payForm.practiceDuration
+                            )}></NNumberAnimation>
+                        </span>
+                        时
+                      </div>
+                    ) : null}
+                  {getHours(payForm.practiceDuration) > 0 || getLastMinutes(payForm.practiceDuration) > 0 ? (
+                    <div>
+                      <span>
+                        <NNumberAnimation
+                          from={0}
+                          to={getLastMinutes(
+                            payForm.practiceDuration
+                          )}></NNumberAnimation>
+                      </span>
+                      分
+                    </div>
+                  ) : null}
+                  <div>
+                    <span>
+                      <NNumberAnimation
+                        from={0}
+                        to={getSecend(
+                          payForm.practiceDuration
+                        )}></NNumberAnimation>
+                    </span>
+                    秒
+                  </div>
+                </p>
+                <p class={styles.TrainDataItemsubTitle}>练习总时长</p>
+              </div>
+
+              <div class={styles.TrainDataItem}>
+                <p class={styles.TrainDataItemTitle}>
+                  <div>
+                    <span>
+                      <NNumberAnimation
+                        from={0}
+                        to={payForm.evaluateUserCount}></NNumberAnimation>/
+                        <NNumberAnimation
+                        from={0}
+                        to={payForm.evaluateFrequency}></NNumberAnimation>
+                    </span>
+                  </div>
+                </p>
+                <p class={styles.TrainDataItemsubTitle}>评测人数/次数</p>
+              </div>
+
+              <div class={styles.TrainDataItem}>
+                <p class={styles.TrainDataItemTitle}>
+                  <div>
+                    <span>
+                      <NNumberAnimation
+                        from={0}
+                        to={payForm.publishUserCount}></NNumberAnimation>/
+                        <NNumberAnimation
+                        from={0}
+                        to={payForm.publishCount}></NNumberAnimation>
+                    </span>
+                  </div>
+                </p>
+                <p class={styles.TrainDataItemsubTitle}>作品人数/数量</p>
+              </div>
             </div>
             <div class={styles.TrainDataTopRight}>
               {/* <div
@@ -306,7 +645,7 @@ export default defineComponent({
               ref={chartRef}
               style={{ height: payForm.height, width: payForm.width }}></div>
           </div>
-          <div class={styles.tableWrap}>
+          <div class={[styles.tableWrap, styles.noSort]}>
             <NDataTable
               v-slots={{
                 empty: () => <TheEmpty></TheEmpty>
@@ -314,6 +653,7 @@ export default defineComponent({
               class={styles.classTable}
               loading={state.loading}
               columns={columns()}
+              onUpdate:sorter={handleSorterChange}
               data={state.tableList}></NDataTable>
             {/* <Pagination
               v-model:page={state.pagination.page}

+ 13 - 23
src/views/home/components/practiceRanking.tsx

@@ -2,7 +2,7 @@ import { defineComponent, reactive, onMounted, computed, nextTick } from 'vue';
 import styles from '../index2.module.less';
 import { NDataTable, NTooltip } from 'naive-ui';
 import Pagination from '@/components/pagination';
-import { getMinutes, getSecend, getTimes } from '/src/utils/dateFormat';
+import { formateSeconds, getMinutes, getSecend, getTimes } from '/src/utils/dateFormat';
 import { getTestList } from '../../classList/api';
 import TheEmpty from '/src/components/TheEmpty';
 import iconSortDefault from '@/common/images/icon-sort-default.png';
@@ -41,13 +41,13 @@ export default defineComponent({
       console.log('ranking===>');
       return props.timer;
     });
-    const search = () => {
-      console.log('search', state);
-    };
+    // const search = () => {
+    //   console.log('search', state);
+    // };
 
-    const onReset = () => {
-      console.log('search');
-    };
+    // const onReset = () => {
+    //   console.log('search');
+    // };
     const getList = async () => {
       state.loading = true;
       try {
@@ -62,15 +62,15 @@ export default defineComponent({
         });
         state.tableList = res.data.rows;
         state.pagination.pageTotal = res.data.total;
-        state.loading = false;
       } catch (e) {
-        state.loading = false;
+        
         console.log(e);
       }
+      state.loading = false;
     };
     expose({ getList });
+    getList()
     onMounted(async () => {
-      await getList();
 
       nextTick(() => {
         // 把默认的排序删除
@@ -196,12 +196,7 @@ export default defineComponent({
         return (
           <>
             {row.practiceDuration
-              ? getMinutes(row.practiceDuration) > 0
-                ? getMinutes(row.practiceDuration) +
-                  '分' +
-                  getSecend(row.practiceDuration) +
-                  '秒'
-                : getSecend(row.practiceDuration) + '秒'
+              ? formateSeconds(row.practiceDuration, 1)
               : 0}
           </>
         );
@@ -244,13 +239,8 @@ export default defineComponent({
       render(row: any) {
         return (
           <>
-            {row.practiceDurationAvg
-              ? getMinutes(row.practiceDurationAvg) > 0
-                ? getMinutes(row.practiceDurationAvg) +
-                  '分' +
-                  getSecend(row.practiceDurationAvg) +
-                  '秒'
-                : getSecend(row.practiceDurationAvg) + '秒'
+            {row.practiceDurationAvg ?
+               formateSeconds(row.practiceDuration, 1)
               : 0}
           </>
         );

+ 3 - 3
src/views/home/components/trainData.tsx

@@ -164,6 +164,7 @@ export default defineComponent({
       });
     };
     const getList = async () => {
+      state.loading = true
       try {
         const res = await getTrainingStat({
           ...getTimes(
@@ -206,6 +207,7 @@ export default defineComponent({
         state.loading = false;
         console.log(e);
       }
+      state.loading = false
     };
     expose({ getList });
     const setChart = () => {
@@ -385,9 +387,7 @@ export default defineComponent({
         // ]
       });
     };
-    onMounted(() => {
-      getList();
-    });
+    getList();
 
     return () => (
       <>

+ 9 - 1
src/views/home/index2.module.less

@@ -905,6 +905,14 @@
   width: 514px;
 }
 
+.noSort {
+  :global {
+    .n-data-table-sorter {
+      display: none !important;
+    }
+  }
+}
+
 .cell {
   display: flex;
   align-items: center;
@@ -914,4 +922,4 @@
     width: 13px;
     height: 13px;
   }
-}
+}

+ 15 - 0
src/views/prepare-lessons/api.ts

@@ -304,3 +304,18 @@ export const api_materialDetail = (id: string) => {
 export const api_lessonCoursewareTeacherCategory = () => {
   return request.get('/edu-app/lessonCourseware/teacherCategory');
 };
+
+
+
+/**
+ *  @description: 备课页面检测声部
+ * @param params
+ */
+export const api_courseScheduleCheck = (params: {
+  classGroupId: string
+  chapterLessonCoursewareId: string
+}) => {
+  return request.post('/edu-app/courseSchedule/check', {
+    data: params
+  });
+};

+ 11 - 32
src/views/prepare-lessons/components/lesson-main/courseware-head/index.tsx

@@ -56,10 +56,16 @@ export default defineComponent({
       updateSubjectList(item.subjects || []);
     };
 
-    const checkCoursewareForm = () => {
+    const checkCoursewareForm = (type: string = 'ALL') => {
       //
-      checkForms.value[0] = forms.name ? '' : 'error';
-      checkForms.value[1] = forms.subjects?.length > 0 ? '' : 'error';
+      if(type === 'name') {
+        checkForms.value[0] = forms.name ? '' : 'error';
+      } else if(type === "subject") {
+        checkForms.value[1] = forms.subjects?.length > 0 ? '' : 'error';
+      } else {
+        checkForms.value[0] = forms.name ? '' : 'error';
+        checkForms.value[1] = forms.subjects?.length > 0 ? '' : 'error';
+      }
     };
 
     const updateSubjectList = (ids?: any[]) => {
@@ -150,35 +156,6 @@ export default defineComponent({
             <span class={[styles.btnTitle]}>
               <span>*</span>课件乐器
             </span>
-            {/* <NSelect
-              status={checkForms.value[1]}
-              placeholder="请选择声部(可多选)"
-              class={styles.btnSubjectList}
-              options={prepareStore.getSubjectList}
-              labelField="name"
-              valueField="id"
-              multiple
-              maxTagCount={2}
-              size="small"
-              v-model:value={forms.subjects}
-              clearable
-              v-slots={{
-                action: () => (
-                  <>
-                    <NButton
-                      text
-                      style=" --n-width: 100% "
-                      size="small"
-                      onClick={() => chioseAll(prepareStore.getSubjectList)}>
-                      全选
-                    </NButton>
-                  </>
-                )
-              }}
-              onUpdate:value={() => {
-                checkForms.value[1] = forms.subjects?.length > 0 ? '' : 'error';
-              }}
-            /> */}
             <NCascader
               status={checkForms.value[1]}
               placeholder="请选择乐器(可多选)"
@@ -197,6 +174,8 @@ export default defineComponent({
               v-model:value={forms.subjects}
               onUpdate:value={() => {
                 checkForms.value[1] = forms.subjects?.length > 0 ? '' : 'error';
+                // console.log(form)
+                eventGlobal.emit('coursewareSubjectChange', forms.subjects)
               }}
               v-slots={{
                 action: () => (

+ 70 - 4
src/views/prepare-lessons/components/lesson-main/courseware-presets/index.tsx

@@ -33,7 +33,8 @@ import {
   api_addByOpenCourseware,
   api_teacherChapterLessonCoursewareRemove,
   teacherChapterLessonCoursewareList,
-  courseScheduleStart
+  courseScheduleStart,
+  api_courseScheduleCheck
 } from '../../../api';
 import { useRoute, useRouter } from 'vue-router';
 import TheMessageDialog from '/src/components/TheMessageDialog';
@@ -91,6 +92,7 @@ export default defineComponent({
       showAttendClass: false,
       attendClassType: 'change', //
       attendClassItem: {} as any,
+      attendClassId: null as any,
       previewModal: false,
       previewParams: {
         type: '',
@@ -99,7 +101,9 @@ export default defineComponent({
         detailId: ''
       } as any,
       workVisiable: false,
-      wikiCategoryIdChild: null
+      wikiCategoryIdChild: null,
+      instrumentErrorVisiable: false,
+      instrumentErrorContent: ""
     });
 
     const getCoursewareList = async () => {
@@ -414,6 +418,45 @@ export default defineComponent({
       }
     };
 
+    /** 上课前检测声部 */
+    const onClassCheckInstrument = async (item: any,
+      classGroupId: any,
+      instrumentId?: any) => {
+        
+      try {
+        if(classGroupId) {
+          const {data} = await api_courseScheduleCheck({
+            classGroupId,
+            chapterLessonCoursewareId: item.id
+          })
+          forms.attendClassItem = item;
+          forms.attendClassId = classGroupId;
+          if(!data.chapterLessonCoursewareFlag) {
+            forms.instrumentErrorVisiable = true;
+            forms.instrumentErrorContent = '课件支持的乐器与班级不符,是否继续使用该课件上课?'
+          } else if(!data.materialFlag) {
+            forms.instrumentErrorVisiable = true;
+            forms.instrumentErrorContent = '课件中含有不符合班级乐器的资源,是否继续使用该课件上课?'
+          } else {
+            onStartClass(
+              item,
+              classGroupId,
+              instrumentId
+            );
+          }
+        } else {
+          onStartClass(
+            item,
+            classGroupId,
+            instrumentId
+          );
+        }
+        
+      } catch {
+        // 
+      }
+    }
+
     const selectChildObj = (item: any) => {
       const obj: any = {};
       item?.forEach((child: any) => {
@@ -581,7 +624,7 @@ export default defineComponent({
                             });
                           }}
                           onStartClass={() =>
-                            onStartClass(item, forms.classGroupId)
+                            onClassCheckInstrument(item, forms.classGroupId)
                           }
                           onDelete={() => {
                             forms.selectItem = item;
@@ -741,6 +784,29 @@ export default defineComponent({
           />
         </NModal>
 
+        <NModal
+          maskClosable={modalClickMask}
+          v-model:show={forms.instrumentErrorVisiable}
+          preset="card"
+          class={['modalTitle', styles.removeVisiable1]}
+          title={'温馨提示'}>
+          <TheMessageDialog
+            content={forms.instrumentErrorContent}
+            contentDirection="left"
+            onClose={() => {
+              forms.instrumentErrorVisiable = false;
+              
+            }}
+            onConfirm={() => {
+              // 
+              onStartClass(
+                forms.attendClassItem,
+                forms.attendClassId
+              );
+            }}
+          />
+        </NModal>
+
         {/* 应用内预览或上课 */}
         <PreviewWindow
           v-model:show={forms.previewModal}
@@ -779,7 +845,7 @@ export default defineComponent({
               }
             }}
             onConfirm={async (item: any) => {
-              onStartClass(
+              onClassCheckInstrument(
                 forms.attendClassItem,
                 item.classGroupId,
                 item.instrumentId

+ 159 - 168
src/views/prepare-lessons/components/lesson-main/courseware/addCourseware.tsx

@@ -12,44 +12,28 @@ import {
   NButton,
   NModal,
   NScrollbar,
-  NSelect,
   NSpace,
   NSpin,
   useMessage,
-  useDialog,
-  NSwitch,
   NInput,
   NTooltip,
-  NImage,
-  NIcon,
   NForm,
   NFormItem
 } from 'naive-ui';
 import CardType from '/src/components/card-type';
-// import AttendClass from '/src/views/prepare-lessons/model/attend-class';
 import { usePrepareStore } from '/src/store/modules/prepareLessons';
-// import { useCatchStore } from '/src/store/modules/catchData';
-// import TheEmpty from '/src/components/TheEmpty';
 import {
   api_teacherChapterLessonCoursewareAdd,
   api_teacherChapterLessonCoursewareUpdate,
   api_teacherChapterLessonCoursewareDetail,
   api_materialDetail
-  // courseScheduleStart,
-  // queryCourseware,
-  // saveCourseware
 } from '../../../api';
 import Draggable from 'vuedraggable';
 import iconDelete from '../../../images/icon-delete-default.png';
 import iconAddMusic from '../../../images/icon-add-music.png';
-// import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
-// import deepClone from '/src/helpers/deep-clone';
 import CardPreview from '/src/components/card-preview';
 import PreviewWindow from '/src/views/preview-window';
-// import { state } from '/src/state';
-// import SubjectSync from '../../../model/subject-sync';
 import { eventGlobal } from '/src/utils';
-// import iconTips from '../../../images/icon-tips.png';
 import TheMessageDialog from '/src/components/TheMessageDialog';
 import AddItemModel from '../../../model/add-item-model';
 import AddOtherSource from '../../../model/add-other-source';
@@ -67,13 +51,9 @@ export default defineComponent({
   },
   emits: ['change'],
   setup(props, { emit }) {
-    // const catchStore = useCatchStore();
     const userStore = useUserStore();
 
     const prepareStore = usePrepareStore();
-    // const route = useRoute();
-    // const router = useRouter();
-    // const dialog = useDialog();
     const message = useMessage();
 
     const forms = reactive({
@@ -114,7 +94,7 @@ export default defineComponent({
       messageOperation: {
         visiable: false,
         loading: false, // 是否显示加载
-        type: 'delete' as 'delete' | 'addItem' | 'save' | 'pageLive',
+        type: 'delete' as 'delete' | 'addItem' | 'save' | 'pageLive' | 'checkInstrument',
         contentDirection: 'center' as 'left' | 'center' | 'right',
         title: '删除知识点',
         content: '请确认是否删除该知识点,删除知识点后将同步删除知识点下的资源',
@@ -168,6 +148,8 @@ export default defineComponent({
                 type: sub.type,
                 title: sub.bizInfo.name,
                 dataJson: sub.dataJson,
+                instrumentIds: sub.instrumentIds, // 素材编号
+                isError: checkCurrentIsInstrument(sub.instruments, sub.type), // 是否异常 当前素材是否在选中的乐器里面
                 // isCollect: !!sub.favoriteFlag,
                 isSelected: sub.source === 'PLATFORM' ? true : false,
                 audioPlayTypeArray: sub.audioPlayTypes
@@ -209,6 +191,35 @@ export default defineComponent({
       forms.loadingStatus = false;
     };
 
+    /** 检测当前素材是否包含所选声部 */
+    const checkCurrentIsInstrument = (instruments: string, type: string) => {
+      // 当前素材是否在选中的乐器里面
+      let isError = false 
+      if(forms.subjects.length <= 0) {
+        return true
+      }
+      // 过滤一些不做校验的素材
+      const checkType = ['IMG', 'VIDEO', 'SONG', 'MUSIC', 'PPT', 'LISTEN', 'MUSICIAN']
+      if(!checkType.includes(type)) {
+        return false
+      }
+      forms.subjects.forEach((item: any) => {
+        if(!instruments?.includes(item)) {
+          isError = true
+        }
+      })
+      return isError
+    }
+
+    /** 检测之后的提示 */
+    const checkCurrentInstrumentTip = (isError = false) => {
+      if(isError) {
+        message.error('您添加的资源与课件乐器不符')
+      } else {
+        message.success('添加成功');
+      }
+    }
+
     // 删除
     const onDelete = (j: number, index: number) => {
       const coursewareItem = forms.coursewareList[index];
@@ -271,7 +282,7 @@ export default defineComponent({
       } else if (type === 'addItem') {
         forms.coursewareList.push({ name: '', list: [] });
         addCoursewareItem(forms.addCoursewareItem);
-      } else if (type === 'save' || type === 'pageLive') {
+      } else if (type === 'save' || type === 'pageLive' || type === "checkInstrument") {
         if (forms.messageOperation.loading) return;
         if (!forms.name) {
           message.error('请输入课件标题');
@@ -308,6 +319,9 @@ export default defineComponent({
           return;
         }
 
+        // 检测是否有异常资源
+        if(checkInstrumentIdsSubmit() && type !== 'checkInstrument') return
+
         forms.messageOperation.loading = true;
         const resultStatus = await onSaveCourseWare();
         forms.messageOperation.loading = false;
@@ -318,15 +332,7 @@ export default defineComponent({
           ) {
             forms.messageCallBack();
           }
-          emit('change', {
-            status: false,
-            addParam: {
-              isAdd: !props.groupItem.id ? true : false,
-              name: forms.name,
-              id: forms.createId
-            }
-          });
-          eventGlobal.emit('teacher-slideshow', false);
+          onCancelSave()
         }
       }
       forms.messageOperation.visiable = false;
@@ -405,7 +411,11 @@ export default defineComponent({
           materialList.forEach((m: any) => {
             forms.coursewareList[item.index || 0].list.push(m);
           });
-          message.success('添加成功');
+          // if(item.isError) {
+          //   message.error('您添加的资源与课件乐器不符')
+          // } else {
+          //   message.success('添加成功');
+          // }
         }
 
         timer = setTimeout(() => {
@@ -449,6 +459,10 @@ export default defineComponent({
         });
         forms.coursewareList[item.index || 0].list = array;
 
+        if(item.isError) {
+          message.error('您添加的资源与课件乐器不符')
+        }
+
         timer = setTimeout(() => {
           // 内容有更新 - 相关资源会刷新
           eventGlobal.emit('onCoursewareUpdate');
@@ -456,6 +470,50 @@ export default defineComponent({
       });
     };
 
+    /** 取消保存时处理 */
+    const onCancelSave = () => {
+      emit('change', {
+        status: false,
+        addParam: {
+          isAdd: false,
+          name: forms.name,
+          id: forms.createId
+        }
+      });
+      eventGlobal.emit('teacher-slideshow', false);
+    }
+
+    /** 提交数据前做拦截提示 */
+    const checkInstrumentIdsSubmit = () => {
+      let status = false
+
+      // 修改声部后重新检测状态
+      forms.coursewareList.forEach((item: any) => {
+        const childList = item.list || []
+        childList.forEach((child: any) => {
+          if(child.isError) {
+            status = true
+          }
+        })
+      })
+
+      if(status) {
+        forms.messageOperation = {
+          visiable: true,
+          type: 'checkInstrument',
+          loading: false,
+          contentDirection: 'center',
+          title: '温馨提示',
+          content: '课件中含有不符合课件乐器的资源,是否继续保存?',
+          cancelButtonText: '取消',
+          confirmButtonText: '继续保存',
+          index: 0
+        };
+      }
+
+      return status
+    }
+
     // 提交
     const onSubmit = async () => {
       try {
@@ -490,22 +548,18 @@ export default defineComponent({
           return;
         }
 
+        // 检测是否有异常资源
+        if(checkInstrumentIdsSubmit()) return
+
         if (forms.openFlag && !userStore.getReadCoursewareOpenAgreement) {
           showModalMask.value = true;
           return;
         }
+
         const resultStatus = await onSaveCourseWare();
 
         if (resultStatus) {
-          emit('change', {
-            status: false,
-            addParam: {
-              isAdd: !props.groupItem.id ? true : false,
-              name: forms.name,
-              id: forms.createId
-            }
-          });
-          eventGlobal.emit('teacher-slideshow', false);
+          onCancelSave()
         }
       } catch {
         //
@@ -567,6 +621,12 @@ export default defineComponent({
     };
 
     const addItem = (item: any, point?: any) => {
+      if(forms.subjects.length <= 0) {
+        message.error('请先选择乐器')
+        eventGlobal.emit('checkCoursewareForm', 'subject')
+        return
+      }
+      item.isError = checkCurrentIsInstrument(item.instrumentIds, item.type) // 是否异常
       if (forms.coursewareList.length <= 0) {
         // 添加到临时对象
         forms.addCoursewareItem = item;
@@ -586,6 +646,7 @@ export default defineComponent({
         forms.addCoursewareItem = item;
       } else {
         addCoursewareItem(item, point);
+        checkCurrentInstrumentTip(item.isError)
       }
     };
 
@@ -603,16 +664,7 @@ export default defineComponent({
       if (objA === objB && baseA === baseB) {
         if (typeof event == 'function') {
           event();
-
-          emit('change', {
-            status: false,
-            addParam: {
-              isAdd: false,
-              name: forms.name,
-              id: forms.createId
-            }
-          });
-          eventGlobal.emit('teacher-slideshow', false);
+          onCancelSave()
         }
       } else {
         forms.messageCallBack = event;
@@ -648,16 +700,7 @@ export default defineComponent({
       const baseB = JSON.stringify(forms.baseInfo);
 
       if (objA === objB && baseA === baseB) {
-        emit('change', {
-          status: false,
-          addParam: {
-            isAdd: false,
-            name: forms.name,
-            id: forms.createId
-          }
-        });
-
-        eventGlobal.emit('teacher-slideshow', false);
+        onCancelSave()
       } else {
         forms.messageOperation = {
           visiable: true,
@@ -683,6 +726,27 @@ export default defineComponent({
       onSubmit();
     };
 
+    // 声部变化时
+    const onCourseWareSubjectChange = (subjects: any) => {
+      forms.subjects = subjects
+
+      let isTips = false
+      // 修改声部后重新检测状态
+      forms.coursewareList.forEach((item: any) => {
+        const childList = item.list || []
+        childList.forEach((child: any) => {
+          child.isError = checkCurrentIsInstrument(child.instrumentIds, child.type)
+          if(child.isError) {
+            isTips = true
+          }
+        })
+      })
+
+      if(isTips) {
+        message.error('您添加的资源与课件乐器不符')
+      }
+    }
+
     onMounted(async () => {
       // 修改时重置默认数据
       if (props.groupItem?.id) {
@@ -695,6 +759,9 @@ export default defineComponent({
       eventGlobal.on('onPrepareAddItem', addItem);
       eventGlobal.on('pageBeforeLeave', onPageBeforeLeave);
 
+      // 选择乐器改变时
+      eventGlobal.on('coursewareSubjectChange', onCourseWareSubjectChange)
+
       // 取消
       eventGlobal.on('coursewareClosed', onCancelCourseware);
       // 保存
@@ -704,6 +771,7 @@ export default defineComponent({
     onUnmounted(() => {
       eventGlobal.off('onPrepareAddItem', addItem);
       eventGlobal.off('pageBeforeLeave', onPageBeforeLeave);
+      eventGlobal.off('coursewareSubjectChange', onCourseWareSubjectChange)
       eventGlobal.off('coursewareClosed', onCancelCourseware);
       eventGlobal.off('coursewareSave', onSubmitCourseware);
     });
@@ -734,51 +802,8 @@ export default defineComponent({
               {forms.coursewareList.map((item: any, index: number) => (
                 <div
                   class={[styles.listItems, 'row-group']}
-                  // onDragenter={(e: any) => {
-                  //   e.preventDefault();
-                  // }}
-                  // onDragover={(e: any) => {
-                  //   e.preventDefault();
-                  // }}
-                  // onDrop={(e: any) => {
-                  //   let dropItem = e.dataTransfer.getData('text');
-                  //   dropItem =
-                  //     dropItem && e.dataTransfer.effectAllowed === 'all'
-                  //       ? JSON.parse(dropItem)
-                  //       : {};
-                  //   // 判断是否有数据
-                  //   if (dropItem.id) {
-                  //     // 获取拖拽的目标元素
-                  //     eventGlobal.emit(
-                  //       'onPrepareAddItem',
-                  //       {
-                  //         materialId: dropItem.id,
-                  //         coverImg: dropItem.coverImg,
-                  //         type: dropItem.type,
-                  //         title: dropItem.title,
-                  //         refFlag: dropItem.refFlag,
-                  //         isCollect: dropItem.isCollect,
-                  //         isSelected: dropItem.isSelected,
-                  //         content: dropItem.content,
-                  //         removeFlag: false,
-                  //         index,
-                  //         addType: 'drag'
-                  //       },
-                  //       {
-                  //         x: e.clientX,
-                  //         y: e.clientY
-                  //       }
-                  //     );
-                  //   }
-                  // }}
                 >
                   <div class={styles.knowledgePoint}>
-                    {/* <div class={styles.btnItem}>
-                      <span class={styles.btnTitle}>
-                        <span>*</span>知识点名称:
-                      </span>
-
-                    </div> */}
                     <NFormItem
                       class={styles.btnItem}
                       label="知识点名称"
@@ -847,27 +872,19 @@ export default defineComponent({
                     // @ts-ignore
                     group="description"
                     scroll={true}
-                    // scrollSensitivity={120}
-                    // forceAutoScrollFallback={true}
                     animation={200}
-                    // onChange={(evt: any) => {
-                    //   const added = evt.added;
-                    //   // 是否为添加
-                    //   if (!added || (added && !added.element)) {
-                    //     return;
-                    //   }
-
-                    //   if (added.element.sourceForm !== 'resource-item') {
-                    //     return;
-                    //   }
-                    //   // console.log(forms.coursewareList, 'courseware');
-                    // }}
                     onAdd={(evt: any) => {
                       // console.log(
                       const list = forms.coursewareList[index].list;
                       const dropItem = list[evt.newDraggableIndex];
-                      console.log(dropItem, 'dropItem');
                       if (dropItem.sourceForm === 'resource-item') {
+                        if(forms.subjects.length <= 0) {
+                          list.splice(evt.newDraggableIndex, 1)
+                          message.error('请先选择课件乐器')
+                          eventGlobal.emit('checkCoursewareForm', 'subject')
+                          return
+                        }
+
                         addDragCoursewareItem(
                           {
                             materialId: dropItem.id,
@@ -877,6 +894,7 @@ export default defineComponent({
                             refFlag: dropItem.refFlag,
                             isCollect: dropItem.isCollect,
                             isSelected: dropItem.isSelected,
+                            isError: checkCurrentIsInstrument(dropItem.instrumentIds, dropItem.type), // 是否异常
                             content: dropItem.content,
                             audioPlayTypeArray: dropItem.audioPlayTypeArray,
                             removeFlag: false,
@@ -884,6 +902,7 @@ export default defineComponent({
                           },
                           evt.newDraggableIndex
                         );
+                        // checkCurrentInstrumentTip(checkCurrentIsInstrument(dropItem.instrumentIds, dropItem.type))
                       }
                     }}
                     onDrag={(event: any) => {
@@ -927,6 +946,7 @@ export default defineComponent({
                             <div class={styles.itemWrapBox}>
                               <CardType
                                 class={[styles.itemContent]}
+                                isError={item.isError}
                                 isShowCollect={false}
                                 offShelf={item.removeFlag ? true : false}
                                 // onOffShelf={() => onRemove(item)}
@@ -963,6 +983,11 @@ export default defineComponent({
                                 'handle'
                               ]}
                               onClick={() => {
+                                if(forms.subjects.length <= 0) {
+                                  message.error('请先选择课件乐器')
+                                  eventGlobal.emit('checkCoursewareForm', 'subject')
+                                  return
+                                }
                                 forms.addOtherSource = true;
                                 forms.addOtherIndex = index;
                               }}>
@@ -975,29 +1000,6 @@ export default defineComponent({
                       )
                     }}
                   </Draggable>
-                  {/* )}
-                  {item.list <= 0 && (
-                    <div class={styles.list}>
-                      <div class={styles.itemWrap}>
-                        <div class={styles.itemWrapBox}>
-                          <div
-                            class={[
-                              styles.itemContent,
-                              styles.addMusicItem,
-                              'handle'
-                            ]}
-                            onClick={() => {
-                              forms.addOtherSource = true;
-                              forms.addOtherIndex = index;
-                            }}>
-                            <img src={iconAddMusic} />
-
-                            <p class={styles.addMusicName}>添加资源</p>
-                          </div>
-                        </div>
-                      </div>
-                    </div>
-                  )} */}
                 </div>
               ))}
 
@@ -1050,14 +1052,17 @@ export default defineComponent({
             onClose={() => (forms.addCoursewareVisiable = false)}
             onConfirm={(selects: number[]) => {
               if (Array.isArray(selects) && selects.length > 0) {
+                
                 selects.forEach(select => {
                   addCoursewareItem({
                     ...forms.addCoursewareItem,
                     index: select
                   });
                 });
-
+                console.log(forms.addCoursewareItem, '----', forms.subjects)
                 forms.addCoursewareVisiable = false;
+               
+                checkCurrentInstrumentTip(forms.addCoursewareItem.isError)
               } else {
                 message.error('请选择需要添加的知识点');
               }
@@ -1083,15 +1088,7 @@ export default defineComponent({
                 forms.messageOperation.type === 'save' ||
                 forms.messageOperation.type === 'pageLive'
               ) {
-                emit('change', {
-                  status: false,
-                  addParam: {
-                    isAdd: false,
-                    name: forms.name,
-                    id: forms.createId
-                  }
-                });
-                eventGlobal.emit('teacher-slideshow', false);
+                onCancelSave()
                 if (
                   forms.messageOperation.type === 'pageLive' &&
                   typeof forms.messageCallBack === 'function'
@@ -1121,20 +1118,23 @@ export default defineComponent({
             onClose={() => (forms.addOtherSource = false)}
             onComfirm={item => {
               if (Array.isArray(item)) {
-                console.log(item, 'item - item');
+                let isTips = false
                 item.forEach(async (child: any) => {
-                  await addCoursewareItem(
-                    { ...child, index: forms.addOtherIndex },
-                    null,
-                    true
-                  );
+                  child.isError = checkCurrentIsInstrument(child.instrumentIds, child.type)
+                  forms.coursewareList[forms.addOtherIndex || 0].list.push(child);
+                  if(child.isError) {
+                    isTips = true
+                  }
                 });
+                checkCurrentInstrumentTip(isTips)
               } else {
+                item.isError = checkCurrentIsInstrument(item.instrumentIds, item.type)
                 addCoursewareItem(
                   { ...item, index: forms.addOtherIndex },
                   null,
                   true
                 );
+                checkCurrentInstrumentTip(item.isError)
               }
             }}
           />
@@ -1149,16 +1149,7 @@ export default defineComponent({
               try {
                 const resultStatus = await onSaveCourseWare();
                 if (resultStatus) {
-                  userStore.setReadCoursewareOpenAgreement(true);
-                  emit('change', {
-                    status: false,
-                    addParam: {
-                      isAdd: !props.groupItem.id ? true : false,
-                      name: forms.name,
-                      id: forms.createId
-                    }
-                  });
-                  eventGlobal.emit('teacher-slideshow', false);
+                  onCancelSave()
                 }
               } catch {
                 //

+ 0 - 5
src/views/prepare-lessons/components/lesson-main/index.tsx

@@ -34,11 +34,6 @@ export default defineComponent({
     };
 
     const onSaveCourseware = () => {
-      // console.log(
-      //   coursewareHeadRef.value,
-      //   coursewareHeadRef.value?.getForms(),
-      //   '12'
-      // );
       eventGlobal.emit('coursewareSave', coursewareHeadRef.value?.getForms());
     };
 

+ 54 - 23
src/views/prepare-lessons/components/lesson-main/train/assign-homework.tsx

@@ -86,7 +86,8 @@ export default defineComponent({
       chapterLessonCoursewareId: props.chapterLessonCoursewareId || null,
       gradeList: [] as any,
       classList: [] as any,
-      currentGradeNum: null,
+      currentGradeNum: null as any,
+      currentClassList: [], // 当前选择的年级
       expireDate: dayjs().add(7, 'day').format('YYYY-MM-DD HH:mm') as any, // 默认7天
       classGroupId: null as any,
       studentList: [] as any,
@@ -112,6 +113,7 @@ export default defineComponent({
           item.classGroupList.forEach((i: any) => {
             classList.push({
               label: i.currentClass + '班',
+              defaultLabel: i.name,
               value: i.id,
               lastStudy: i.lastStudy,
               preStudentNum: i.preStudentNum
@@ -270,7 +272,6 @@ export default defineComponent({
             }
           }
         );
-        console.log(data, 'data');
         const result = data || [];
         result.forEach((item: any) => {
           if (item.paramName === 'homework_file_expire_time') {
@@ -298,12 +299,12 @@ export default defineComponent({
               .format('YYYY-MM-DD HH:mm');
           }
 
-          console.log(
-            dayjs()
-              .add(Number(maxDeadlineTime.value), 'day')
-              .format('YYYY-MM-DD'),
-            'pickerEndTime.value'
-          );
+          // console.log(
+          //   dayjs()
+          //     .add(Number(maxDeadlineTime.value), 'day')
+          //     .format('YYYY-MM-DD'),
+          //   'pickerEndTime.value'
+          // );
           // state.pickerEndTime =
         }
       } catch {
@@ -323,18 +324,26 @@ export default defineComponent({
       } else {
         status = ts < forms.currentTime;
       }
-      console.log(
-        status,
-        '1212',
-        ts > pickerEndTime.value,
-        forms.currentTime,
-        ts,
-        pickerEndTime.value
-      );
+      // console.log(
+      //   status,
+      //   '1212',
+      //   ts > pickerEndTime.value,
+      //   forms.currentTime,
+      //   ts,
+      //   pickerEndTime.value
+      // );
       // pickerEndTime.value && pickerEndTime.value > ts;
       return status;
     };
 
+    const changeHomeWork = (type: any) => {
+      forms.homeworkObj = type
+      if(!props.classGroupId) {
+        forms.currentGradeNum = null
+        forms.classGroupId = null
+      }
+    }
+
     onMounted(async () => {
       await getDefaultParamConfig();
       await getCurrentGradeYear();
@@ -375,7 +384,7 @@ export default defineComponent({
                   styles.switch,
                   forms.homeworkObj === 'CLASS' ? styles.active : ''
                 ]}
-                onClick={() => (forms.homeworkObj = 'CLASS')}>
+                onClick={() => changeHomeWork("CLASS")}>
                 按班级布置
               </NButton>
               <NButton
@@ -384,7 +393,7 @@ export default defineComponent({
                   styles.switch,
                   forms.homeworkObj === 'PERSON' ? styles.active : ''
                 ]}
-                onClick={() => (forms.homeworkObj = 'PERSON')}>
+                onClick={() => changeHomeWork("PERSON")}>
                 按学生布置
               </NButton>
             </NSpace>
@@ -414,13 +423,14 @@ export default defineComponent({
                 required: true,
                 message: '请选择年级',
                 trigger: 'change',
-                type: 'number'
+                type: forms.homeworkObj === 'CLASS' || props.classGroupId ? 'number' : 'array'
               }
             ]}>
             <NSelect
               disabled={props.classGroupId ? true : false}
               v-model:value={forms.currentGradeNum}
               placeholder="请选择年级"
+              multiple={forms.homeworkObj === 'CLASS' || props.classGroupId ? false : true}
               options={forms.gradeList}
               clearable
               onUpdate:value={() => {
@@ -470,9 +480,30 @@ export default defineComponent({
                   !forms.currentGradeNum && styles.disabled
                 ]}
                 onClick={() => {
-                  if (!forms.currentGradeNum) {
+                  if (forms.currentGradeNum.length <= 0) {
                     return;
                   }
+
+                  console.log(forms.currentGradeNum, 'currentGradeNum', forms.gradeList)
+
+                  const tempClass: any = []
+                  forms.gradeList.forEach((item: any) => {
+                    // 为了区分上课页面和作业页面
+                    if(Array.isArray(forms.currentGradeNum)) {
+                      if(forms.currentGradeNum.includes(item.value)) {
+                        const child = item.childrens || []
+                        tempClass.push(...child)
+                      }
+                    } else {
+                      if(forms.currentGradeNum === item.value) {
+                        const child = item.childrens || []
+                        tempClass.push(...child)
+                      }
+                    }
+                  })
+
+                  forms.currentClassList = tempClass
+
                   const tempIds: any = [];
                   forms.studentList.forEach((item: any) => {
                     tempIds.push(item.id);
@@ -553,11 +584,11 @@ export default defineComponent({
           style={
             props.from === 'class'
               ? {
-                  width: '640px',
+                  width: '780px',
                   ...assignHomeworkStuBoxDragData.styleDrag.value
                 }
               : {
-                  width: '640px'
+                  width: '780px'
                 }
           }
           preset="card"
@@ -570,7 +601,7 @@ export default defineComponent({
             currentGradeNum={forms.currentGradeNum as any}
             selectIds={forms.selectIds}
             studentList={forms.studentList}
-            classList={forms.classList}
+            classList={forms.currentClassList}
             onClose={() => (forms.workVisiable = false)}
             onConfirm={(val: any) => {
               forms.studentList = val || [];

+ 1 - 1
src/views/prepare-lessons/components/lesson-main/train/assign-student/index.module.less

@@ -210,7 +210,7 @@
 
   .btnGroup {
     margin-right: 27px;
-    padding-top: 12px;
+    padding-top: 8px;
 
     :global {
       .n-button {

+ 412 - 398
src/views/prepare-lessons/components/lesson-main/train/assign-student/index.tsx

@@ -1,398 +1,412 @@
-import { computed, defineComponent, onMounted, reactive } from 'vue';
-import styles from './index.module.less';
-import {
-  NAvatar,
-  NButton,
-  NCascader,
-  NCheckbox,
-  NCheckboxGroup,
-  NInput,
-  NScrollbar,
-  NSelect,
-  NSpace,
-  NSpin
-} from 'naive-ui';
-import defultHeade from '@/components/layout/images/teacherIcon.png';
-import SearchInput from '/src/components/searchInput';
-import { useCatchStore } from '/src/store/modules/catchData';
-import { getStudentList } from '/src/views/classList/api';
-import { useThrottleFn } from '@vueuse/core';
-import TheEmpty from '/src/components/TheEmpty';
-import { getGradeYearList } from '/src/views/home/api';
-import { api_getCurrentGradeYear } from '/src/views/studentList/api';
-
-export default defineComponent({
-  name: 'assign-student',
-  props: {
-    /** 班级列表 */
-    classList: {
-      type: Array,
-      default: () => []
-    },
-    /** 所选学生列表 */
-    studentList: {
-      type: Array,
-      default: () => []
-    },
-    /** 学年 */
-    currentGradeNum: {
-      type: [String || Number],
-      default: ''
-    },
-    selectIds: {
-      type: Array,
-      default: () => []
-    },
-    classGroupId: {
-      type: String,
-      default: ''
-    }
-  },
-  emits: ['close', 'confirm'],
-  setup(props, { emit }) {
-    const catchStore = useCatchStore();
-    const state = reactive({
-      studentName: '',
-      loading: false,
-      finshed: false, // 是否加载完
-      checkAllStatus: false,
-      indeterminate: false,
-      searchFrom: {
-        upgradeFlag: true,
-        currentGradeNum: props.currentGradeNum || '',
-        gradeYear: null,
-        classGroupId: props.classGroupId || '',
-        classInstrumentId: '',
-        keyword: ''
-      },
-      pagination: {
-        page: 1,
-        rows: 20,
-        pageTotal: 0
-      },
-      tableList: [] as any,
-      checkboxIds: [] as any,
-      selectStudents: [] as any,
-      selectKeyword: '',
-      popSelectYearList: [] as any
-    });
-
-    // 获取学年
-    const getYearList = async () => {
-      try {
-        const { data } = await api_getCurrentGradeYear({});
-        state.searchFrom.gradeYear = data;
-        // const { data } = await getGradeYearList();
-        // const temp = data || [];
-        // temp.forEach((i: any) => {
-        //   i.name = i.name + '学年';
-        // });
-        // state.popSelectYearList = temp || [];
-        // if (temp.length > 0 && !state.searchFrom.gradeYear) {
-        //   state.searchFrom.gradeYear = temp[0].id;
-        // }
-      } catch {
-        //
-      }
-    };
-
-    const getStudentLists = async () => {
-      try {
-        if (state.pagination.page === 1) {
-          state.loading = true;
-          state.tableList = [];
-        }
-        const { data } = await getStudentList({
-          ...state.searchFrom,
-          ...state.pagination
-        });
-        state.loading = false;
-        const rows = data.rows || [];
-        state.tableList.push(...rows);
-        state.finshed = data.pages <= data.current ? true : false;
-
-        onCheckStudents();
-      } catch {
-        //
-        state.loading = false;
-      }
-    };
-
-    const onSearch = () => {
-      state.pagination.page = 1;
-      getStudentLists();
-    };
-
-    const selectStudentEmpty = computed(() => {
-      let status = true;
-      state.selectStudents.forEach((item: any) => {
-        if (!item.hide) {
-          status = false;
-        }
-      });
-      return status;
-    });
-
-    const throttledFn = useThrottleFn(() => {
-      state.pagination.page = state.pagination.page + 1;
-      getStudentLists();
-    }, 500);
-
-    // 切换学生状态
-    const onCheckStudents = () => {
-      // state.selectStudents = [];
-      if (state.checkboxIds.length <= 0 || state.tableList.length <= 0) {
-        state.indeterminate = false;
-        state.checkAllStatus = false;
-        return;
-      }
-      let count = 0;
-      // 右边数据
-      state.tableList.forEach((item: any) => {
-        if (state.checkboxIds.includes(item.id)) {
-          count++;
-          const index = state.selectStudents.findIndex(
-            (select: any) => select.id === item.id
-          );
-          if (index === -1) state.selectStudents.push(item);
-        }
-      });
-
-      if (count >= state.tableList.length) {
-        state.checkAllStatus = true;
-        state.indeterminate = false;
-      } else {
-        state.checkAllStatus = false;
-        state.indeterminate = true;
-      }
-    };
-
-    // 删除用户
-    const onRemove = (item: any) => {
-      const index = state.checkboxIds.findIndex((id: any) => id === item.id);
-      if (index !== -1) {
-        state.checkboxIds.splice(index, 1);
-        const sIndex = state.selectStudents.findIndex(
-          (select: any) => select.id === item.id
-        );
-        if (sIndex !== -1) {
-          state.selectStudents.splice(sIndex, 1);
-        }
-        onCheckStudents();
-      }
-    };
-
-    const onSave = () => {
-      const studentInfo: any[] = [];
-      state.selectStudents.forEach((item: any) => {
-        studentInfo.push({
-          id: item.id,
-          name: item.nickname,
-          avatar: item.avatar
-        });
-      });
-
-      emit('confirm', studentInfo);
-    };
-
-    onMounted(async () => {
-      state.checkboxIds = props.selectIds || [];
-      state.loading = true;
-      await catchStore.getSubjects();
-      await getYearList();
-      await getStudentLists();
-      // onCheckStudents();
-      // 重置选择的学生
-      state.selectStudents = props.studentList?.map((item: any) => {
-        return {
-          ...item,
-          nickname: item.name
-        };
-      });
-    });
-    return () => (
-      <div class={[styles.assignStudent, 'assignStudent']}>
-        <div class={styles.studentListGroup}>
-          <div class={styles.searchSection}>
-            <div class={styles.searchSpace}>
-              <NSelect
-                placeholder="全部班级"
-                disabled={props.classGroupId ? true : false}
-                v-model:value={state.searchFrom.classGroupId}
-                onUpdate:value={() => onSearch()}
-                options={
-                  [{ label: '全部班级', value: '' }, ...props.classList] as any
-                }
-              />
-              {/* <NSelect
-                options={[
-                  { label: '全部乐器', value: '' },
-                  ...catchStore.getEnableSubjects
-                ]}
-                placeholder="全部乐器"
-                v-model:value={state.searchFrom.classInstrumentId}
-                onUpdate:value={() => onSearch()}
-              /> */}
-              <NCascader
-                options={[
-                  { name: '全部乐器', id: '' },
-                  ...catchStore.getSubjectList
-                ]}
-                placeholder="全部乐器"
-                v-model:value={state.searchFrom.classInstrumentId}
-                onUpdate:value={() => onSearch()}
-                checkStrategy="child"
-                showPath={false}
-                childrenField="instruments"
-                expandTrigger="hover"
-                labelField="name"
-                valueField="id"
-                clearable
-                filterable
-              />
-            </div>
-            <SearchInput
-              {...{ placeholder: '请输入学生姓名/手机号' }}
-              class={styles.searchInput}
-              searchWord={state.searchFrom.keyword}
-              onChangeValue={(val: string) => {
-                state.searchFrom.keyword = val;
-              }}
-              onClear={() => {
-                state.searchFrom.keyword = '';
-                onSearch();
-              }}
-              onKeyup={(e: KeyboardEvent) => {
-                if (e.code === 'Enter') {
-                  onSearch();
-                }
-              }}></SearchInput>
-          </div>
-
-          <div class={styles.studentSection}>
-            <div class={styles.checkboxAll}>
-              <NCheckbox
-                v-model:checked={state.checkAllStatus}
-                indeterminate={state.indeterminate}
-                onUpdate:checked={(val: any) => {
-                  if (val) {
-                    const ids: any = [];
-                    state.tableList.forEach((item: any) => {
-                      ids.push(item.id);
-                    });
-                    state.checkboxIds = ids;
-                  } else {
-                    state.checkboxIds = [];
-                    state.selectStudents = [];
-                    state.indeterminate = false;
-                  }
-                  onCheckStudents();
-                }}></NCheckbox>
-              <p>
-                全选 <span class={styles.nums}>({state.tableList.length})</span>{' '}
-                :
-              </p>
-            </div>
-          </div>
-          <NScrollbar
-            class={styles.student}
-            onScroll={(e: any) => {
-              const clientHeight = e.target?.clientHeight;
-              const scrollTop = e.target?.scrollTop;
-              const scrollHeight = e.target?.scrollHeight;
-              // 是否到底,是否加载完
-              if (
-                clientHeight + scrollTop + 20 >= scrollHeight &&
-                !state.finshed &&
-                !state.loading
-              ) {
-                throttledFn();
-              }
-            }}>
-            <NSpin show={state.loading} class={styles.loadingSection}>
-              <NCheckboxGroup
-                v-model:value={state.checkboxIds}
-                onUpdate:value={() => {
-                  state.selectStudents = [];
-                  onCheckStudents();
-                }}>
-                {state.tableList.map((item: any) => (
-                  <NCheckbox value={item.id} class={[styles.studentItem]}>
-                    <div class={styles.studentInfo}>
-                      <NAvatar
-                        src={item.avatar || defultHeade}
-                        class={styles.studentImg}
-                      />
-                      <div class={styles.studentValue}>
-                        <div class={styles.userInfo}>
-                          <span class={styles.name}>{item.nickname}</span>
-                          {item.membership && <i class={styles.iconMember}></i>}
-                          {item.classGroupName && (
-                            <span class={styles.className}>
-                              {item.classGroupName}
-                            </span>
-                          )}
-                        </div>
-                        <div class={styles.phone}>{item.phone}</div>
-                      </div>
-                    </div>
-                  </NCheckbox>
-                ))}
-              </NCheckboxGroup>
-              {state.tableList.length <= 0 && !state.loading && <TheEmpty />}
-            </NSpin>
-          </NScrollbar>
-        </div>
-
-        <div class={styles.selectStudentGroup}>
-          <div class={styles.selectCount}>
-            当前选中 <span>({state.selectStudents.length}) </span>:
-          </div>
-          <div class={styles.searchSection}>
-            <SearchInput
-              {...{ placeholder: '请输入学生姓名' }}
-              class={styles.searchInput}
-              searchWord={state.selectKeyword}
-              onChangeValue={(val: string) => {
-                state.selectKeyword = val;
-
-                state.selectStudents.forEach((item: any) => {
-                  if (item.nickname?.indexOf(val) === -1) {
-                    item.hide = true;
-                  } else {
-                    item.hide = false;
-                  }
-                });
-              }}></SearchInput>
-          </div>
-          <NScrollbar class={styles.student}>
-            {state.selectStudents.map((student: any) => (
-              <div class={[styles.studentItem, student.hide && styles.hide]}>
-                <div class={styles.studentInfo}>
-                  <NAvatar
-                    src={student.avatar || defultHeade}
-                    class={styles.studentImg}
-                  />
-                  <span class={styles.name}>{student.nickname}</span>
-                </div>
-                <i
-                  class={styles.iconClose}
-                  onClick={() => onRemove(student)}></i>
-              </div>
-            ))}
-            {selectStudentEmpty.value && <TheEmpty />}
-          </NScrollbar>
-
-          <NSpace justify="end" class={styles.btnGroup}>
-            <NButton type="default" onClick={() => emit('close')}>
-              取消
-            </NButton>
-            <NButton type="primary" onClick={onSave}>
-              保存
-            </NButton>
-          </NSpace>
-        </div>
-      </div>
-    );
-  }
-});
+import { computed, defineComponent, onMounted, reactive } from 'vue';
+import styles from './index.module.less';
+import {
+  NAvatar,
+  NButton,
+  NCascader,
+  NCheckbox,
+  NCheckboxGroup,
+  NInput,
+  NScrollbar,
+  NSelect,
+  NSpace,
+  NSpin
+} from 'naive-ui';
+import defultHeade from '@/components/layout/images/teacherIcon.png';
+import SearchInput from '/src/components/searchInput';
+import { useCatchStore } from '/src/store/modules/catchData';
+import { getStudentList } from '/src/views/classList/api';
+import { useThrottleFn } from '@vueuse/core';
+import TheEmpty from '/src/components/TheEmpty';
+import { getGradeYearList } from '/src/views/home/api';
+import { api_getCurrentGradeYear } from '/src/views/studentList/api';
+
+export default defineComponent({
+  name: 'assign-student',
+  props: {
+    /** 班级列表 */
+    classList: {
+      type: Array,
+      default: () => []
+    },
+    /** 所选学生列表 */
+    studentList: {
+      type: Array,
+      default: () => []
+    },
+    /** 学年 */
+    currentGradeNum: {
+      type: [String || Number],
+      default: ''
+    },
+    selectIds: {
+      type: Array,
+      default: () => []
+    },
+    classGroupId: {
+      type: String,
+      default: ''
+    }
+  },
+  emits: ['close', 'confirm'],
+  setup(props, { emit }) {
+    const catchStore = useCatchStore();
+    const state = reactive({
+      studentName: '',
+      loading: false,
+      finshed: false, // 是否加载完
+      checkAllStatus: false,
+      indeterminate: false,
+      searchFrom: {
+        upgradeFlag: true,
+        currentGradeNums: null as any,
+        gradeYear: null,
+        classGroupId: props.classGroupId || '',
+        classInstrumentId: '',
+        keyword: ''
+      },
+      pagination: {
+        page: 1,
+        rows: 20,
+        pageTotal: 0
+      },
+      tableList: [] as any,
+      checkboxIds: [] as any,
+      selectStudents: [] as any,
+      selectKeyword: '',
+      popSelectYearList: [] as any
+    });
+
+    // 获取学年
+    const getYearList = async () => {
+      try {
+        const { data } = await api_getCurrentGradeYear({});
+        state.searchFrom.gradeYear = data;
+      } catch {
+        //
+      }
+    };
+
+    const getStudentLists = async () => {
+      try {
+        if (state.pagination.page === 1) {
+          state.loading = true;
+          state.tableList = [];
+        }
+        const { data } = await getStudentList({
+          ...state.searchFrom,
+          ...state.pagination
+        });
+        state.loading = false;
+        const rows = data.rows || [];
+        state.tableList.push(...rows);
+        state.finshed = data.pages <= data.current ? true : false;
+
+        onCheckStudents();
+      } catch {
+        //
+        state.loading = false;
+      }
+    };
+
+    const onSearch = () => {
+      state.pagination.page = 1;
+      getStudentLists();
+    };
+
+    const selectStudentEmpty = computed(() => {
+      let status = true;
+      state.selectStudents.forEach((item: any) => {
+        if (!item.hide) {
+          status = false;
+        }
+      });
+      return status;
+    });
+
+    const throttledFn = useThrottleFn(() => {
+      state.pagination.page = state.pagination.page + 1;
+      getStudentLists();
+    }, 500);
+
+    // 切换学生状态
+    const onCheckStudents = () => {
+      if (state.tableList.length <= 0) {
+        state.indeterminate = false;
+        state.checkAllStatus = false;
+        return;
+      }
+      // 右边数据
+      state.tableList.forEach((item: any) => {
+        if (state.checkboxIds.includes(item.id)) {
+          const index = state.selectStudents.findIndex(
+            (select: any) => select.id == item.id
+          );
+          if (index === -1) state.selectStudents.push(item);
+        } else {
+          const index = state.selectStudents.findIndex(
+            (select: any) => select.id == item.id
+          );
+          if(index >= 0) state.selectStudents.splice(index, 1)
+        }
+      });
+
+      let count = 0;
+      state.tableList.forEach((item: any) => {
+        const index = state.selectStudents.findIndex(
+          (select: any) => select.id === item.id
+        );
+        if(index >= 0) count++
+      })
+
+      if (count >= state.tableList.length) {
+        state.checkAllStatus = true;
+        state.indeterminate = false;
+      } else {
+        state.checkAllStatus = false;
+        state.indeterminate = count === 0 ? false : true;
+      }
+    };
+
+    // 删除用户
+    const onRemove = (item: any) => {
+      const index = state.checkboxIds.findIndex((id: any) => id == item.id);
+      if (index !== -1) {
+        state.checkboxIds.splice(index, 1);
+        const sIndex = state.selectStudents.findIndex(
+          (select: any) => select.id === item.id
+        );
+        if (sIndex !== -1) {
+          state.selectStudents.splice(sIndex, 1);
+        }
+        onCheckStudents();
+      }
+    };
+
+    const onSave = () => {
+      const studentInfo: any[] = [];
+      state.selectStudents.forEach((item: any) => {
+        studentInfo.push({
+          id: item.id,
+          name: item.nickname,
+          avatar: item.avatar
+        });
+      });
+
+      emit('confirm', studentInfo);
+    };
+
+    onMounted(async () => {
+      console.log(props.currentGradeNum, 'props.currentGradeNum-----')
+      if(Array.isArray(props.currentGradeNum)) {
+        state.searchFrom.currentGradeNums = props.currentGradeNum.join(',')
+      } else {
+        state.searchFrom.currentGradeNums = props.currentGradeNum
+      }
+
+      state.checkboxIds = props.selectIds || [];
+      state.loading = true;
+      await catchStore.getSubjects();
+      await getYearList();
+      await getStudentLists();
+      // onCheckStudents();
+      // 重置选择的学生
+      state.selectStudents = props.studentList?.map((item: any) => {
+        return {
+          ...item,
+          nickname: item.name
+        };
+      });
+    });
+    return () => (
+      <div class={[styles.assignStudent, 'assignStudent']}>
+        <div class={styles.studentListGroup}>
+          <div class={styles.searchSection}>
+            <div class={styles.searchSpace}>
+              <NSelect
+                placeholder="全部年级班级"
+                disabled={props.classGroupId ? true : false}
+                labelField="defaultLabel"
+                filterable
+                clearable
+                v-model:value={state.searchFrom.classGroupId}
+                onUpdate:value={() => onSearch()}
+                options={
+                  [{ defaultLabel: '全部年级班级', value: '' }, ...props.classList] as any
+                }
+              />
+              <NCascader
+                options={[
+                  { name: '全部乐器', id: '' },
+                  ...catchStore.getSubjectList
+                ]}
+                placeholder="全部乐器"
+                v-model:value={state.searchFrom.classInstrumentId}
+                onUpdate:value={() => onSearch()}
+                checkStrategy="child"
+                showPath={false}
+                childrenField="instruments"
+                expandTrigger="hover"
+                labelField="name"
+                valueField="id"
+                clearable
+                filterable
+              />
+            </div>
+            <SearchInput
+              {...{ placeholder: '请输入学生姓名/手机号' }}
+              class={styles.searchInput}
+              searchWord={state.searchFrom.keyword}
+              onChangeValue={(val: string) => {
+                state.searchFrom.keyword = val;
+              }}
+              onClear={() => {
+                state.searchFrom.keyword = '';
+                onSearch();
+              }}
+              onKeyup={(e: KeyboardEvent) => {
+                if (e.code === 'Enter') {
+                  onSearch();
+                }
+              }}></SearchInput>
+          </div>
+
+          <div class={styles.studentSection}>
+            <div class={styles.checkboxAll}>
+              <NCheckbox
+                v-model:checked={state.checkAllStatus}
+                indeterminate={state.indeterminate}
+                onUpdate:checked={(val: any) => {
+                  if (val) {
+                    const ids: any = [];
+                    state.tableList.forEach((item: any) => {
+                      ids.push(item.id);
+                    });
+                    ids.forEach((id: any) => {
+                      if(!state.checkboxIds.includes(id)) {
+                        state.checkboxIds.push(id)
+                      }
+                    })
+                  } else {
+                    const removeIds = state.tableList.map((item: any) => item.id)
+                    console.log(removeIds, 'removeIds', state.checkboxIds)
+                    removeIds.forEach((rid: any) => {
+                      const index = state.checkboxIds.findIndex((id: any) => id == rid);
+                      if(index !== -1) state.checkboxIds.splice(index, 1);
+                      const sindex = state.selectStudents.findIndex(
+                        (select: any) => select.id == rid
+                      );
+                      if(sindex !== -1) state.selectStudents.splice(sindex, 1) 
+                    })
+                    state.indeterminate = false;
+                  }
+                  onCheckStudents();
+                }}></NCheckbox>
+              <p>
+                全选 <span class={styles.nums}>({state.tableList.length})</span>{' '}
+                :
+              </p>
+            </div>
+          </div>
+          <NScrollbar
+            class={styles.student}
+            onScroll={(e: any) => {
+              const clientHeight = e.target?.clientHeight;
+              const scrollTop = e.target?.scrollTop;
+              const scrollHeight = e.target?.scrollHeight;
+              // 是否到底,是否加载完
+              if (
+                clientHeight + scrollTop + 20 >= scrollHeight &&
+                !state.finshed &&
+                !state.loading
+              ) {
+                throttledFn();
+              }
+            }}>
+            <NSpin show={state.loading} class={styles.loadingSection}>
+              <NCheckboxGroup
+                v-model:value={state.checkboxIds}
+                onUpdate:value={() => {
+                  // state.selectStudents = [];
+                  onCheckStudents();
+                }}>
+                {state.tableList.map((item: any) => (
+                  <NCheckbox value={item.id} class={[styles.studentItem]}>
+                    <div class={styles.studentInfo}>
+                      <NAvatar
+                        src={item.avatar || defultHeade}
+                        class={styles.studentImg}
+                      />
+                      <div class={styles.studentValue}>
+                        <div class={styles.userInfo}>
+                          <span class={styles.name}>{item.nickname}</span>
+                          {item.membership && <i class={styles.iconMember}></i>}
+                          {item.classGroupName && (
+                            <span class={styles.className}>
+                              {item.classGroupName}
+                            </span>
+                          )}
+                        </div>
+                        <div class={styles.phone}>{item.phone}</div>
+                      </div>
+                    </div>
+                  </NCheckbox>
+                ))}
+              </NCheckboxGroup>
+              {state.tableList.length <= 0 && !state.loading && <TheEmpty />}
+            </NSpin>
+          </NScrollbar>
+        </div>
+
+        <div class={styles.selectStudentGroup}>
+          <div class={styles.selectCount}>
+            当前选中 <span>({state.selectStudents.length}) </span>:
+          </div>
+          <div class={styles.searchSection}>
+            <SearchInput
+              {...{ placeholder: '请输入学生姓名' }}
+              class={styles.searchInput}
+              searchWord={state.selectKeyword}
+              onChangeValue={(val: string) => {
+                state.selectKeyword = val;
+
+                state.selectStudents.forEach((item: any) => {
+                  if (item.nickname?.indexOf(val) === -1) {
+                    item.hide = true;
+                  } else {
+                    item.hide = false;
+                  }
+                });
+              }}></SearchInput>
+          </div>
+          <NScrollbar class={styles.student}>
+            {state.selectStudents.map((student: any) => (
+              <div class={[styles.studentItem, student.hide && styles.hide]}>
+                <div class={styles.studentInfo}>
+                  <NAvatar
+                    src={student.avatar || defultHeade}
+                    class={styles.studentImg}
+                  />
+                  <span class={styles.name}>{student.nickname}</span>
+                </div>
+                <i
+                  class={styles.iconClose}
+                  onClick={() => onRemove(student)}></i>
+              </div>
+            ))}
+            {selectStudentEmpty.value && <TheEmpty />}
+          </NScrollbar>
+
+          <NSpace justify="end" class={styles.btnGroup}>
+            <NButton type="default" onClick={() => emit('close')}>
+              取消
+            </NButton>
+            <NButton type="primary" onClick={onSave}>
+              保存
+            </NButton>
+          </NSpace>
+        </div>
+      </div>
+    );
+  }
+});

+ 0 - 25
src/views/prepare-lessons/components/lesson-main/train/index.tsx

@@ -449,7 +449,6 @@ export default defineComponent({
               }}>
               {forms.trainList.length > 0 && (
                 <>
-                  {/* {forms.drag ? ( */}
                   <Draggable
                     v-model:modelValue={forms.trainList}
                     itemKey="id"
@@ -496,30 +495,6 @@ export default defineComponent({
                       }
                     }}
                   </Draggable>
-                  {/* ) : (
-                    <div class={styles.list}>
-                      {forms.trainList.map((item: any) => (
-                        <TrainType
-                          item={item}
-                          type="prepare"
-                          offShelf={item.removeFlag ? true : false}
-                          onOffShelf={() => onRemove(item)}
-                          onEdit={(child: any) => {
-                            // console.log('edit', child);
-                            const { trainingConfigJson, id, musicId, ...res } =
-                              child;
-                            forms.editItem = {
-                              ...res,
-                              id: musicId,
-                              trainId: id,
-                              ...trainingConfigJson
-                            };
-                            forms.editStatus = true;
-                          }}
-                        />
-                      ))}
-                    </div>
-                  )} */}
                 </>
               )}
 

+ 5 - 3
src/views/prepare-lessons/components/resource-main/components/resource-item/index.tsx

@@ -85,9 +85,9 @@ export default defineComponent({
         const tempRows = data.rows || [];
         const temp: any = [];
         tempRows.forEach((row: any) => {
-          const index = prepareStore.getCoursewareList.findIndex(
-            (course: any) => course.materialId === row.id
-          );
+          // const index = prepareStore.getCoursewareList.findIndex(
+          //   (course: any) => course.materialId === row.id
+          // );
           temp.push({
             id: row.id,
             coverImg: row.coverImg,
@@ -98,6 +98,7 @@ export default defineComponent({
             isSelected: row.sourceFrom === 'PLATFORM' ? true : false,
             containAccompaniment: row.containAccompaniment,
             content: row.content,
+            instrumentIds: row.instrumentIds,
             sourceForm: 'resource-item',
             audioPlayTypeArray: row.audioPlayTypes
               ? row.audioPlayTypes.split(',')
@@ -157,6 +158,7 @@ export default defineComponent({
           isCollect: item.isCollect,
           isSelected: item.isSelected,
           audioPlayTypeArray: item.audioPlayTypeArray,
+          instrumentIds: item.instrumentIds,
           content: item.content,
           removeFlag: false
         });

+ 0 - 1
src/views/prepare-lessons/components/resource-main/components/resource-item/resource-search-group/index.tsx

@@ -92,7 +92,6 @@ export default defineComponent({
         ...tags
       ];
 
-      console.log(tagSubjectList.value, 'tagSubjectList.value');
     };
     onMounted(async () => {
       // await catchStore.getMusicSheetCategory();

+ 2 - 0
src/views/prepare-lessons/model/add-other-source/index.tsx

@@ -320,6 +320,7 @@ export default defineComponent({
                 value.forEach((item: any) => {
                   temp.push({
                     materialId: item.materialId,
+                    instrumentIds: item.instrumentIds,
                     coverImg: item.coverImg,
                     dataJson: null,
                     title: item.title,
@@ -364,6 +365,7 @@ export default defineComponent({
                 const temp: any[] = [];
                 value.forEach((item: any) => {
                   temp.push({
+                    instrumentIds: item.instrumentIds,
                     materialId: item.id,
                     coverImg: item.coverImg,
                     dataJson: null,

+ 3 - 0
src/views/prepare-lessons/model/select-resources/select-item/index.tsx

@@ -111,6 +111,7 @@ export default defineComponent({
             isCollect: !!row.favoriteFlag,
             isSelected: row.sourceFrom === 'PLATFORM' ? true : false,
             refFlag: row.refFlag,
+            instrumentIds: row.instrumentIds,
             audioPlayTypeArray: row.audioPlayTypes
               ? row.audioPlayTypes.split(',')
               : [],
@@ -152,6 +153,8 @@ export default defineComponent({
           refFlag: item.refFlag,
           isCollect: item.isCollect,
           isSelected: item.isSelected,
+          audioPlayTypeArray: item.audioPlayTypeArray,
+          instrumentIds: item.instrumentIds,
           content: item.content,
           removeFlag: false
         });

+ 1 - 0
src/views/prepare-lessons/model/subject-sync/index.tsx

@@ -72,6 +72,7 @@ export default defineComponent({
           const tempCode = item.code ? item.code.split(',')[0] : '';
           subjectCode.push({
             materialId: item.id,
+            instrumentIds: item.id,
             coverImg: subjectImgs[tempCode] || subjectImgs.Panpipes,
             dataJson: null,
             title: item.name,

+ 9 - 0
src/views/studentList/api.ts

@@ -75,3 +75,12 @@ export const api_getCurrentGradeYear = (params: object) => {
     data: params
   });
 };
+
+/**
+ * 评测记录分页统计
+ */
+export const api_musicPracticeRecordPageStat = (params: object) => {
+  return request.post('/edu-app/musicPracticeRecord/pageStat', {
+    data: params
+  })
+}

+ 402 - 77
src/views/studentList/components/evaluationRecords.tsx

@@ -2,18 +2,22 @@ import {  defineComponent, onMounted, reactive, ref } from 'vue';
 import styles from '../index.module.less';
 import {
   NButton,
+  NCascader,
   NDataTable,
   NForm,
   NFormItem,
   NInput,
   NInputNumber,
   NModal,
+  NNumberAnimation,
   NSpace,
-  NTag
+  NTag,
+  NTooltip,
+  useMessage
 } from 'naive-ui';
 // import { useECharts } from '@/hooks/web/useECharts';
 import Pagination from '/src/components/pagination';
-import { getPracticeRecordList } from '../api';
+import { api_musicPracticeRecordPageStat, getPracticeRecordList } from '../api';
 import {
   getNowDateAndMonday,
   getNowDateAndSunday,
@@ -24,11 +28,20 @@ import CDatePicker from '/src/components/CDatePicker';
 import { useUserStore } from '/src/store/modules/users';
 import TheEmpty from '/src/components/TheEmpty';
 import { initCache, setCache } from '/src/hooks/use-async';
-import { iframeDislableKeyboard } from '/src/utils';
+import { checkUrlType, iframeDislableKeyboard } from '/src/utils';
 import { modalClickMask } from '/src/state';
 // import SearchInput from '/src/components/searchInput';
 import CSelect from '/src/components/CSelect';
 import { evaluateDifficultArray } from '/src/utils/searchArray';
+import { useCatchStore } from '/src/store/modules/catchData';
+import iconSortDefault from '@/common/images/icon-sort-default.png';
+import iconSortDesc from '@/common/images/icon-sort-desc.png';
+import iconSortAsc from '@/common/images/icon-sort-asc.png';
+import TheTooltip from '/src/components/TheTooltip';
+import CardPreview from '/src/components/card-preview';
+import { saveAs } from 'file-saver';
+import { useRoute } from 'vue-router';
+import dayjs from 'dayjs';
 export default defineComponent({
   name: 'student-practiceData',
   props: {
@@ -42,10 +55,10 @@ export default defineComponent({
     }
   },
   setup(props) {
+    const route = useRoute()
     const userStore = useUserStore();
-    const chartRef = ref<HTMLDivElement | null>(null);
-    // const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
-    // const practiceFlag = ref(true);
+    const catchData = useCatchStore()
+    const message = useMessage()
     const payForm = reactive({
       height: '360px',
       width: '100%',
@@ -64,14 +77,18 @@ export default defineComponent({
         rows: 10,
         pageTotal: 4
       },
+      asc: null as any,
+      sortType: null as any,
       searchForm: {
         musicSheetName: '',
+        instrumentId: null,
         heardLevel: null, //
         userMusicFlag: null, // 是否生成作品
         minScore: null,
         maxScore: null,
         musicStartTime: []
       },
+      stat: {} as any,
       tableList: [] as any,
       goCourseVisiable: false
     });
@@ -79,17 +96,134 @@ export default defineComponent({
       getNowDateAndMonday(new Date().getTime()),
       getNowDateAndSunday(new Date().getTime())
     ]);
+    const previewShow = ref(false);
+    const previewItem = ref({
+      type: '',
+      content: '',
+      title: ''
+    });
+
+
+    const toolTitleTips = (title: string, item: any) => {
+      return <NTooltip showArrow={false} placement="top-start">
+      {{
+        trigger: () => (
+          <div class={styles.cell}>
+            {title}
+            <img
+              class={styles.sortIcon}
+              src={
+                item.sortOrder === 'descend'
+                  ? iconSortDesc
+                  : item.sortOrder === 'ascend'
+                  ? iconSortAsc
+                  : iconSortDefault
+              }
+            />
+          </div>
+        ),
+        default:
+          item.sortOrder === 'descend'
+            ? '点击升序'
+            : item.sortOrder === 'ascend'
+            ? '取消排序'
+            : '点击降序'
+      }}
+    </NTooltip>
+    }
+
+    const createTimeRef = reactive({
+      title() {
+        return (
+          toolTitleTips('评测时间', createTimeRef)
+        );
+      },
+      key: 'createTime',
+      sorter: true,
+      sortOrder: false as any
+    });
+
+    const evaluationProgressRef = reactive({
+      title() {
+        return (
+          toolTitleTips('评测进度', evaluationProgressRef)
+        );
+      },
+      key: 'evaluationProgress',
+      sorter: true,
+      sortOrder: false as any
+    });
+
+    const scoreRef = reactive({
+      title() {
+        return (
+          toolTitleTips('评测分数', scoreRef)
+        );
+      },
+      key: 'score',
+      sorter: true,
+      sortOrder: false as any
+    });
+
+    const intonationRef = reactive({
+      title() {
+        return (
+          toolTitleTips('音准', intonationRef)
+        );
+      },
+      key: 'intonation',
+      sorter: true,
+      sortOrder: false as any,
+      render(row: any) {
+        return row.rhythmFlag ? '--' : row.intonation
+      }
+    });
+
+    const cadenceRef = reactive({
+      title() {
+        return (
+          toolTitleTips('节奏', cadenceRef)
+        );
+      },
+      key: 'cadence',
+      sorter: true,
+      sortOrder: false as any
+    });
+
+    const integrityRef = reactive({
+      title() {
+        return (
+          toolTitleTips('完整度', integrityRef)
+        );
+      },
+      key: 'integrity',
+      sorter: true,
+      sortOrder: false as any
+    });
+
+    const userMusicTimeRef = reactive({
+      title() {
+        return (
+          toolTitleTips('发布时间', userMusicTimeRef)
+        );
+      },
+      key: 'userMusicTime',
+      sorter: true,
+      sortOrder: false as any
+    });
+
     const columns = () => {
       return [
-        {
-          title: '时间',
-          key: 'createTime'
-        },
+        createTimeRef,
         {
           title: '评测曲目',
           key: 'musicSheetName',
           render(row: any) {
-            return <span>{row.musicSheetName}</span>;
+            return <TheTooltip
+            maxWidth={200}
+            showContentWidth={300}
+            content={row.musicSheetName}
+          />;
           }
         },
         // 入门:BEGINNER/进阶:ADVANCED/大师:PERFORMER"
@@ -114,83 +248,172 @@ export default defineComponent({
           }
         },
         {
-          title: '评测分数',
-          key: 'score',
-          render(row: any) {
-            return <span>{row.score}</span>;
-          }
-        },
-        {
-          title: '音准',
-          key: 'intonation',
-          render(row: any) {
-            return <span>{row.intonation}</span>;
-          }
-        },
-        {
-          title: '节奏',
-          key: 'cadence',
-          render(row: any) {
-            return <span>{row.cadence}</span>;
-          }
-        },
-        {
-          title: '完整度',
-          key: 'integrity',
-          render(row: any) {
-            return <span>{row.integrity}</span>;
-          }
+          title: '乐器',
+          key: 'instrumentName'
         },
+        evaluationProgressRef,
+        scoreRef,
+        intonationRef,
+        cadenceRef,
+        integrityRef,
         {
-          title: '生成作品',
+          title: '发布作品',
           key: 'integrity',
           render(row: any) {
             return <span>{row.userMusicFlag ? '是' : '否'}</span>;
           }
         },
-        {
-          title: '生成时间',
-          key: 'userMusicTime',
-          render(row: any) {
-            return <span>{row.userMusicTime || '--'}</span>;
-          }
-        },
+        userMusicTimeRef,
         {
           title: '操作',
           key: 'id',
           render(row: any) {
             return (
-              <NButton
-                text
-                type="primary"
-                onClick={() => {
-                  gotoRecode(row);
-                }}>
-                评测报告
-              </NButton>
+              <NSpace>
+                <NButton
+                  text
+                  type="primary"
+                  onClick={() => {
+                    gotoRecode(row);
+                  }}>
+                  评测报告
+                </NButton>
+                {row.videoFilePath || row.recordFilePath ? <><NButton
+                  text
+                  type="primary"
+                  onClick={() => {
+                    gotoPreview(row);
+                  }}>
+                  预览作品
+                </NButton>
+                <NButton
+                  text
+                  type="primary"
+                  onClick={() => {
+                    gotoDownload(row);
+                  }}>
+                  下载作品
+                </NButton></> : ''}
+                
+              </NSpace>
             );
           }
         }
       ];
     };
+
+    // 统计排序
+    const handleSorterChange = (sroter: any) => {
+      if (!sroter.order) {
+        state.asc = null
+        state.sortType = null
+        evaluationProgressRef.sortOrder = false
+        scoreRef.sortOrder = false
+        createTimeRef.sortOrder = false
+        intonationRef.sortOrder = false
+        cadenceRef.sortOrder = false
+        integrityRef.sortOrder = false
+        userMusicTimeRef.sortOrder = false
+      } else {
+        // 1:综合得分,2:音准,3:节奏:4:完整度,5:评测时间  6:生成时间,7:评测进度
+        const template = {
+          score: 1,
+          intonation: 2,
+          cadence: 3,
+          integrity: 4,
+          createTime: 5,
+          userMusicTime: 6,
+          evaluationProgress: 7
+        } as any
+
+        state.sortType = template[sroter.columnKey]
+
+        evaluationProgressRef.sortOrder = false
+        scoreRef.sortOrder = false
+        createTimeRef.sortOrder = false
+        intonationRef.sortOrder = false
+        cadenceRef.sortOrder = false
+        integrityRef.sortOrder = false
+        userMusicTimeRef.sortOrder = false
+
+        if (sroter.columnKey == 'score') {
+          scoreRef.sortOrder = sroter.order
+        }
+        if (sroter.columnKey == 'createTime') {
+          createTimeRef.sortOrder = sroter.order
+        }
+        if (sroter.columnKey == 'intonation') {
+          intonationRef.sortOrder = sroter.order
+        }
+        if (sroter.columnKey == 'cadence') {
+          cadenceRef.sortOrder = sroter.order
+        }
+        if (sroter.columnKey == 'userMusicTime') {
+          userMusicTimeRef.sortOrder = sroter.order
+        }
+        if (sroter.columnKey == 'evaluationProgress') {
+          evaluationProgressRef.sortOrder = sroter.order
+        }
+
+        state.asc = sroter.order == 'ascend' ? true : false
+      }
+      getList()
+    }
+
     const getList = async () => {
-      const { musicStartTime, ...temp } = state.searchForm;
-      const res = await getPracticeRecordList({
-        userId: props.studentId,
-        ...state.pagination,
-        ...temp,
-        ...getTimes(
-          musicStartTime,
-          ['userMusicStartTime', 'userMusicEndTime'],
-          'YYYY-MM-DD'
-        ),
-        classGroupId: props.classGroupId,
-        feature: 'EVALUATION',
-        ...getTimes(timer.value, ['startTime', 'endTime'], 'YYYY-MM-DD')
-      });
-      state.tableList = res.data.rows;
-      state.pagination.pageTotal = res.data.total;
+      state.loading = true;
+      try {
+        const { musicStartTime, ...temp } = state.searchForm;
+        const res = await getPracticeRecordList({
+          userId: props.studentId,
+          ...state.pagination,
+          sortType: state.sortType,
+          asc: state.asc,
+          ...temp,
+          ...getTimes(
+            musicStartTime,
+            ['userMusicStartTime', 'userMusicEndTime'],
+            'YYYY-MM-DD'
+          ),
+          ...getTimes(
+            timer.value,
+            ['startTime', 'endTime'],
+            'YYYY-MM-DD'
+          ),
+          classGroupId: props.classGroupId,
+          ...getTimes(timer.value, ['startTime', 'endTime'], 'YYYY-MM-DD')
+        });
+        state.tableList = res.data.rows;
+        state.pagination.pageTotal = res.data.total;
+      } catch {
+
+      }
+      state.loading = false
     };
+
+    const getTrainingStat = async () => {
+      state.loading = true;
+      try {
+        const { musicStartTime, ...more } = state.searchForm
+        const { data } = await api_musicPracticeRecordPageStat({
+          userId: props.studentId,
+          page: state.pagination.page,
+          rows: state.pagination.rows,
+          sortType: state.sortType,
+          asc: state.asc,
+          ...more,
+          ...getTimes(timer, ['startTime', 'endTime'])
+        })
+
+        state.stat = {
+          evaluateFrequency: data.evaluateFrequency || 0,
+          publishCount: data.publishCount || 0
+        }
+      } catch {}
+      state.loading = false
+    }
+
+
     const gotoRecode = (row: any) => {
       const token = userStore.getToken;
       reportSrc.value =
@@ -200,8 +423,43 @@ export default defineComponent({
         }&platform=webTeacher&Authorization=${token}`;
       payForm.detailVisiable = true;
     };
+    const gotoPreview = (row: any) => {
+      let lookTitle = '';
+      if (row.videoFilePath) {
+        lookTitle = checkUrlType(row.videoFilePath);
+      } else {
+        lookTitle = checkUrlType(row.recordFilePath);
+      }
+      const lookUrl = row.videoFilePath || row.recordFilePath;
+      previewItem.value.content = lookUrl;
+      previewItem.value.title = row.musicSheetName;
+      if (lookTitle === 'video') {
+        previewItem.value.type = 'VIDEO';
+      } else if (lookTitle === 'audio') {
+        previewItem.value.type = 'SONG';
+      }
+      previewShow.value = true;
+    }
+    const gotoDownload = (row: any) => {
+      // 下载资源
+      const fileUrl = row.videoFilePath || row.recordFilePath;
+      const filename =
+        row.musicSheetName + route.query.studentName + ( row.userMusicTime ? dayjs( row.userMusicTime) : '');
+      const sfixx = fileUrl.substring(fileUrl.lastIndexOf('.'));
+      // 发起Fetch请求
+      fetch(fileUrl)
+        .then(response => response.blob())
+        .then(blob => {
+          saveAs(blob, (filename || new Date().getTime()) + sfixx);
+        })
+        .catch(() => {
+          message.error('下载失败');
+        });
+    
+    }
     const search = () => {
       state.pagination.page = 1;
+      getTrainingStat()
       getList();
       setCache({
         current: { timer: timer.value },
@@ -209,17 +467,23 @@ export default defineComponent({
       });
     };
     const onReset = () => {
-      timer.value = [
-        getNowDateAndMonday(new Date().getTime()),
-        getNowDateAndSunday(new Date().getTime())
-      ];
+      if(route.query.times) {
+        timer.value = JSON.parse(route.query.times as any)
+      } else {
+        timer.value = [
+          getNowDateAndMonday(new Date().getTime()),
+          getNowDateAndSunday(new Date().getTime())
+        ];
+      }
+      
       state.searchForm = {
         musicSheetName: '',
         heardLevel: null, //
         userMusicFlag: null, // 是否生成作品
         minScore: null,
         maxScore: null,
-        musicStartTime: []
+        musicStartTime: [],
+        instrumentId: null
       };
       search();
       setCache({
@@ -235,8 +499,14 @@ export default defineComponent({
       }
     });
     const iframeRef = ref();
-    onMounted(() => {
-      getList();
+    onMounted(async () => {
+      state.loading = true
+      if(route.query.times) {
+        timer.value = JSON.parse(route.query.times as any)
+      }
+      await catchData.getSubjects();
+      await getTrainingStat()
+      await getList();
     });
     return () => (
       <>
@@ -299,6 +569,25 @@ export default defineComponent({
           </NFormItem>
 
           <NFormItem>
+            <NCascader
+              to="body"
+              placeholder="选择乐器"
+              options={[
+                { value: '', label: '全部乐器' },
+                ...catchData.getSubjectList
+              ]}
+              childrenField="instruments"
+              checkStrategy="child"
+              expandTrigger="hover"
+              showPath={false}
+              clearable
+              v-model:value={state.searchForm.instrumentId}
+              onUpdate:value={(val: any, option: any, pathValues: any) => {
+                console.log(val, option, pathValues);
+              }}
+            />
+          </NFormItem>
+          <NFormItem>
             <CSelect
               {...({
                 options: [
@@ -337,7 +626,35 @@ export default defineComponent({
             </NSpace>
           </NFormItem>
         </NForm>
-        <div class={styles.tableWrap}>
+        <div class={[styles.TrainDataTop, styles.TrainDataTopEvaluation]}>
+          <div class={styles.TrainDataTopLeft}>
+            <div class={styles.TrainDataItem}>
+              <p class={styles.TrainDataItemTitle}>
+                <div>
+                  <span>
+                    <NNumberAnimation
+                      from={0}
+                      to={state.stat.evaluateFrequency || 0}></NNumberAnimation>
+                  </span>
+                </div>
+              </p>
+              <p class={styles.TrainDataItemsubTitle}>评测次数</p>
+            </div>
+            <div class={styles.TrainDataItem}>
+              <p class={styles.TrainDataItemTitle}>
+                <div>
+                  <span>
+                    <NNumberAnimation
+                      from={0}
+                      to={state.stat.publishCount || 0}></NNumberAnimation>
+                  </span>
+                </div>
+              </p>
+              <p class={styles.TrainDataItemsubTitle}>作品数量</p>
+            </div>
+          </div>
+        </div>
+        <div class={[styles.tableWrap, styles.noSort]}>
           <NDataTable
             v-slots={{
               empty: () => <TheEmpty></TheEmpty>
@@ -345,6 +662,7 @@ export default defineComponent({
             class={styles.classTable}
             loading={state.loading}
             columns={columns()}
+            onUpdate:sorter={handleSorterChange}
             data={state.tableList}></NDataTable>
           <Pagination
             v-model:page={state.pagination.page}
@@ -352,6 +670,7 @@ export default defineComponent({
             v-model:pageTotal={state.pagination.pageTotal}
             onList={getList}
             sync
+            saveKey='studentDetail-evaluationRecords'
           />
         </div>
         <NModal
@@ -371,6 +690,12 @@ export default defineComponent({
               src={reportSrc.value}></iframe>
           </div>
         </NModal>
+
+        <CardPreview
+          v-model:show={previewShow.value}
+          item={previewItem.value}
+          isDownload={false}
+        />
       </>
     );
   }

+ 87 - 53
src/views/studentList/components/practiceData.tsx

@@ -18,11 +18,15 @@ import {
   getNowDateAndSunday,
   getTimes,
   getMinutes,
-  getSecend
+  getSecend,
+  getHours,
+  getLastMinutes,
+  formateSeconds
 } from '/src/utils/dateFormat';
 import CDatePicker from '/src/components/CDatePicker';
 import TheEmpty from '/src/components/TheEmpty';
 import { initCache, setCache } from '/src/hooks/use-async';
+import { useRoute } from 'vue-router';
 export default defineComponent({
   name: 'student-practiceData',
   props: {
@@ -36,6 +40,7 @@ export default defineComponent({
     }
   },
   setup(props) {
+    const route = useRoute()
     const chartRef = ref<HTMLDivElement | null>(null);
     const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
     const practiceFlag = ref(true);
@@ -51,6 +56,7 @@ export default defineComponent({
 
     const state = reactive({
       loading: false,
+      dayFlag: true,
       pagination: {
         page: 1,
         rows: 10,
@@ -78,12 +84,7 @@ export default defineComponent({
                 {' '}
                 <>
                   {row.practiceDuration
-                    ? getMinutes(row.practiceDuration) > 0
-                      ? getMinutes(row.practiceDuration) +
-                        '分' +
-                        getSecend(row.practiceDuration) +
-                        '秒'
-                      : getSecend(row.practiceDuration) + '秒'
+                    ? formateSeconds(row.practiceDuration, 1)
                     : 0 + '分钟'}
                 </>
               </>
@@ -97,6 +98,7 @@ export default defineComponent({
         const res = await getTrainingStatList({
           page: 1,
           rows: 999,
+          dayFlag: state.dayFlag,
           studentId: props.studentId,
           classGroupId: props.classGroupId,
           ...getTimes(timer.value, ['startTime', 'endTime'], 'YYYY-MM-DD')
@@ -163,18 +165,6 @@ export default defineComponent({
             type: 'bar',
             barWidth: '48px',
             stack: 'total',
-            // label: {
-            //   // 柱图头部显示值
-            //   formatter: (value: any) => {
-            //     console.log(value);
-            //     return getMinutes(value.value);
-            //   },
-            //   show: true,
-            //   position: 'top',
-            //   color: '#333',
-            //   fontSize: '12px',
-            //   fontWeight: 600
-            // },
 
             itemStyle: {
               normal: {
@@ -198,11 +188,7 @@ export default defineComponent({
             return [
               item[0].axisValueLabel,
               ...item.map((d: any) => {
-                let str;
-                getMinutes(d.value) > 0
-                  ? (str =
-                      getMinutes(d.value) + '分' + getSecend(d.value) + '秒')
-                  : (str = getSecend(d.value) + '秒');
+                let str = formateSeconds(d.value, 1)
                 return `<br/>${d.marker}<span style="margin-top:10px;margin-left:5px;font-size: 13px;font-weight: 500;
                   color: #131415;font-weight: 600;
                   margin-top:12px
@@ -228,6 +214,7 @@ export default defineComponent({
       try {
         const res = await getTrainingStat({
           studentId: props.studentId,
+          dayFlag: state.dayFlag,
           classGroupId: props.classGroupId,
           ...getTimes(timer.value, ['startTime', 'endTime'], 'YYYY-MM-DD')
         });
@@ -257,10 +244,15 @@ export default defineComponent({
       });
     };
     const onReset = () => {
-      timer.value = [
-        getNowDateAndMonday(new Date().getTime()),
-        getNowDateAndSunday(new Date().getTime())
-      ];
+      if(route.query.times) {
+        timer.value = JSON.parse(route.query.times as any)
+      } else {
+        timer.value = [
+          getNowDateAndMonday(new Date().getTime()),
+          getNowDateAndSunday(new Date().getTime())
+        ];
+      }
+      
       search();
       getList();
       setCache({
@@ -276,43 +268,71 @@ export default defineComponent({
       }
     });
     onMounted(() => {
-      console.log(props.studentId);
+      if(route.query.times) {
+        timer.value = JSON.parse(route.query.times as any)
+      }
       getChartDetail();
       getList();
     });
     return () => (
       <>
-        <NForm label-placement="left" inline>
-          <NFormItem>
-            <CDatePicker
-              v-model:value={timer.value}
-              separator={'至'}
-              type="daterange"
-              timerValue={timer.value}></CDatePicker>
-          </NFormItem>
+        <NSpace justify="space-between">
+          <NForm label-placement="left" inline>
+            <NFormItem>
+              <CDatePicker
+                v-model:value={timer.value}
+                separator={'至'}
+                type="daterange"
+                timerValue={timer.value}></CDatePicker>
+            </NFormItem>
 
-          <NFormItem>
-            <NSpace justify="end">
-              <NButton type="primary" class="searchBtn" onClick={search}>
-                搜索
-              </NButton>
-              <NButton type="primary" ghost class="resetBtn" onClick={onReset}>
-                重置
-              </NButton>
-            </NSpace>
-          </NFormItem>
-        </NForm>
-        <div class={styles.homeTrainData}>
+            <NFormItem>
+              <NSpace justify="end">
+                <NButton type="primary" class="searchBtn" onClick={search}>
+                  搜索
+                </NButton>
+                <NButton type="primary" ghost class="resetBtn" onClick={onReset}>
+                  重置
+                </NButton>
+              </NSpace>
+            </NFormItem>
+          </NForm>
+          <NSpace>
+            <NButton type="primary" class={state.dayFlag ? "searchDate" : "searchDateDefault"} onClick={() =>{
+              state.dayFlag = true;
+              search()
+            }}>按天</NButton>
+            <NButton type="primary" class={state.dayFlag ? "searchDateDefault" : "searchDate"} onClick={() =>{
+              state.dayFlag = false;
+              search()
+            }}>按月</NButton>
+          </NSpace>
+        </NSpace>
+        <div class={[styles.homeTrainData, styles.homeTrainDataPractice]}>
           <div class={styles.TrainDataTop}>
             <div class={styles.TrainDataTopLeft}>
               <div class={styles.TrainDataItem}>
                 <p class={styles.TrainDataItemTitle}>
-                  {getMinutes(payForm.practiceDurationTotal) > 0 ? (
+                {getHours(payForm.practiceDurationTotal) > 0 ? (
                     <div>
                       <span>
                         <NNumberAnimation
                           from={0}
-                          to={getMinutes(
+                          to={getHours(
+                            payForm.practiceDurationTotal
+                          )}></NNumberAnimation>
+                      </span>
+                      <i style={{ width: '4px', display: 'inline-block' }}></i>
+                      时
+                      <i style={{ width: '4px', display: 'inline-block' }}></i>
+                    </div>
+                  ) : null}
+                  {getHours(payForm.practiceDurationAvg) > 0 || getLastMinutes(payForm.practiceDurationTotal) > 0 ? (
+                    <div>
+                      <span>
+                        <NNumberAnimation
+                          from={0}
+                          to={getLastMinutes(
                             payForm.practiceDurationTotal
                           )}></NNumberAnimation>
                       </span>
@@ -336,12 +356,26 @@ export default defineComponent({
               </div>
               <div class={styles.TrainDataItem}>
                 <p class={styles.TrainDataItemTitle}>
-                  {getMinutes(payForm.practiceDurationAvg) > 0 ? (
+                {getHours(payForm.practiceDurationAvg) > 0 ? (
+                    <div>
+                      <span>
+                        <NNumberAnimation
+                          from={0}
+                          to={getHours(
+                            payForm.practiceDurationAvg
+                          )}></NNumberAnimation>
+                      </span>
+                      <i style={{ width: '4px', display: 'inline-block' }}></i>
+                      时
+                      <i style={{ width: '4px', display: 'inline-block' }}></i>
+                    </div>
+                  ) : null}
+                  {getHours(payForm.practiceDurationAvg) > 0 || getLastMinutes(payForm.practiceDurationAvg) > 0 ? (
                     <div>
                       <span>
                         <NNumberAnimation
                           from={0}
-                          to={getMinutes(
+                          to={getLastMinutes(
                             payForm.practiceDurationAvg
                           )}></NNumberAnimation>
                       </span>

+ 90 - 64
src/views/studentList/index.module.less

@@ -164,89 +164,96 @@
 .homeTrainData {
   margin-top: 40px;
 
-  .TrainDataTop {
-    margin-bottom: 40px;
+ &.homeTrainDataPractice {
+  margin-top: 20px;
+ } 
+}
+
+.TrainDataTop {
+  margin-bottom: 40px;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  &.TrainDataTopEvaluation {
+    margin-bottom: 24px;
+  }
+
+  .TrainDataTopLeft {
     display: flex;
     flex-direction: row;
     align-items: center;
-    justify-content: space-between;
 
-    .TrainDataTopLeft {
-      display: flex;
-      flex-direction: row;
-      align-items: center;
-
-      .TrainDataItem {
-        margin-right: 40px;
+    .TrainDataItem {
+      margin-right: 40px;
 
-        .TrainDataItemTitle {
-          display: flex;
-          flex-direction: row;
-          align-items: center;
-          text-align: center;
-          font-size: max(13px, 11Px);
-          font-weight: 400;
+      .TrainDataItemTitle {
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+        text-align: center;
+        font-size: max(13px, 11Px);
+        font-weight: 400;
+        color: #131415;
+        line-height: 18px;
+
+        span {
+          font-family: 'DINA';
+          font-size: max(26px, 18Px);
+          font-weight: 600;
           color: #131415;
-          line-height: 18px;
-
-          span {
-            font-family: 'DINA';
-            font-size: max(26px, 18Px);
-            font-weight: 600;
-            color: #131415;
-            line-height: 28px;
-          }
+          line-height: 28px;
         }
+      }
 
-        .TrainDataItemsubTitle {
-          margin-top: 4px;
-          text-align: center;
-          font-size: max(14px, 11Px);
-          font-family: PingFangSC-Regular, PingFang SC;
-          font-weight: 400;
-          color: #777777;
-          line-height: 18px;
-        }
+      .TrainDataItemsubTitle {
+        margin-top: 4px;
+        text-align: center;
+        font-size: max(14px, 11Px);
+        font-family: PingFangSC-Regular, PingFang SC;
+        font-weight: 400;
+        color: #777777;
+        line-height: 18px;
       }
     }
+  }
 
-    .TrainDataTopRight {
+  .TrainDataTopRight {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+
+    .DataTopRightItem {
+      cursor: pointer;
       display: flex;
       flex-direction: row;
       align-items: center;
+      margin-left: 30px;
 
-      .DataTopRightItem {
-        cursor: pointer;
-        display: flex;
-        flex-direction: row;
-        align-items: center;
-        margin-left: 30px;
-
-        &:hover {
-          opacity: 0.8;
-        }
+      &:hover {
+        opacity: 0.8;
+      }
 
-        .DataTopRightDot {
-          width: 16px;
-          height: 16px;
-          background: #3583fa;
-          border-radius: 4px;
-          margin-right: 6px;
-        }
+      .DataTopRightDot {
+        width: 16px;
+        height: 16px;
+        background: #3583fa;
+        border-radius: 4px;
+        margin-right: 6px;
+      }
 
-        .DataTopRightDot.DataTopRightDotBlue {
-          background: #d5e9ff;
-        }
+      .DataTopRightDot.DataTopRightDotBlue {
+        background: #d5e9ff;
+      }
 
-        .DataTopRightDot.red {
-          background: #ff7aa7;
-        }
+      .DataTopRightDot.red {
+        background: #ff7aa7;
       }
+    }
 
-      .DataTopRightItem.DataTopRightItemDis {
-        .DataTopRightDot {
-          background: #f5f6fa;
-        }
+    .DataTopRightItem.DataTopRightItemDis {
+      .DataTopRightDot {
+        background: #f5f6fa;
       }
     }
   }
@@ -640,4 +647,23 @@
 
 .updateStudent {
   width: 580px;
-}
+}
+
+.noSort {
+  :global {
+    .n-data-table-sorter {
+      display: none !important;
+    }
+  }
+}
+
+.cell {
+  display: flex;
+  align-items: center;
+
+  .sortIcon {
+    margin-left: 7px;
+    width: 13px;
+    height: 13px;
+  }
+}