Browse Source

修改逻辑

lex-xin 7 months ago
parent
commit
39d97bf303

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

+ 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;

+ 10 - 5
src/utils/dateFormat.ts

@@ -109,6 +109,11 @@ export function getHours(time: number) {
   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;
@@ -121,7 +126,7 @@ export function getSecend(time: number) {
 
 
 // 秒转时分秒
-export function formateSeconds(endTime: string) {
+export function formateSeconds(endTime: string, pad = 2) {
   let secondTime = parseInt(endTime) //将传入的秒的值转化为Number
   let min = 0 // 初始化分
   let h = 0 // 初始化小时
@@ -137,13 +142,13 @@ export function formateSeconds(endTime: string) {
     }
   }
   if (h) {
-    result = `${h.toString().padStart(2, '0')}时${min.toString().padStart(2, '0')}分${secondTime
+    result = `${h.toString().padStart(pad, '0')}时${min.toString().padStart(pad, '0')}分${secondTime
       .toString()
-      .padStart(2, '0')}秒`
+      .padStart(pad, '0')}秒`
   } else if (min) {
-    result = `${min.toString().padStart(2, '0')}分${secondTime.toString().padStart(2, '0')}秒`
+    result = `${min.toString().padStart(pad, '0')}分${secondTime.toString().padStart(pad, '0')}秒`
   } else {
-    result = `${secondTime.toString().padStart(2, '0')}秒`
+    result = `${secondTime.toString().padStart(pad, '0')}秒`
   }
   return result
 }

+ 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 {

+ 16 - 13
src/views/home/components/practiceData.tsx

@@ -4,13 +4,15 @@ import { NButton, NDataTable, NNumberAnimation, NTooltip, useMessage } from 'nai
 import { useECharts } from '@/hooks/web/useECharts';
 // import Pagination from '/src/components/pagination';
 import { getPracticePageStat, getTestStat } from '@/views/data-module/api';
-import { formateSeconds, getHours, getMinutes, getSecend, getTimes } from '/src/utils/dateFormat';
+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';
 export default defineComponent({
   name: 'home-practiceData',
   props: {
@@ -20,6 +22,7 @@ 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>);
@@ -239,14 +242,13 @@ export default defineComponent({
               type="primary"
               text
               onClick={() => {
-                // router.push({
-                //   path: '/studentManageDetail',
-                //   query: {
-                //     id: row.studentId,
-                //     name: row.studentName,
-                //     timer: (state.searchForm.times)
-                //   }
-                // })
+                setTabsCaches('evaluatingRcode', 'tabName', {
+                  path: '/studentDetail'
+                });
+                router.push({
+                  path: '/studentDetail',
+                  query: { studentId: row.studentId, studentName: row.studentName }
+                });
               }}
             >
               详情
@@ -512,12 +514,13 @@ export default defineComponent({
                       </div>
                     ) : null}
-                  {getMinutes(payForm.practiceDurationAvg) > 0 ? (
+                    
+                  {getHours(payForm.practiceDurationAvg) > 0 || getLastMinutes(payForm.practiceDurationAvg) > 0 ? (
                     <div>
                       <span>
                         <NNumberAnimation
                           from={0}
-                          to={getMinutes(
+                          to={getLastMinutes(
                             payForm.practiceDurationAvg
                           )}></NNumberAnimation>
                       </span>
@@ -552,12 +555,12 @@ export default defineComponent({
                       </div>
                     ) : null}
-                  {getMinutes(payForm.practiceDuration) > 0 ? (
+                  {getHours(payForm.practiceDuration) > 0 || getLastMinutes(payForm.practiceDuration) > 0 ? (
                     <div>
                       <span>
                         <NNumberAnimation
                           from={0}
-                          to={getMinutes(
+                          to={getLastMinutes(
                             payForm.practiceDuration
                           )}></NNumberAnimation>
                       </span>

+ 4 - 14
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';
@@ -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}
           </>
         );

+ 44 - 19
src/views/prepare-lessons/components/lesson-main/courseware/addCourseware.tsx

@@ -149,7 +149,7 @@ export default defineComponent({
                 title: sub.bizInfo.name,
                 dataJson: sub.dataJson,
                 instrumentIds: sub.instrumentIds, // 素材编号
-                isError: checkCurrentIsInstrument(sub.instruments), // 是否异常 当前素材是否在选中的乐器里面
+                isError: checkCurrentIsInstrument(sub.instruments, sub.type), // 是否异常 当前素材是否在选中的乐器里面
                 // isCollect: !!sub.favoriteFlag,
                 isSelected: sub.source === 'PLATFORM' ? true : false,
                 audioPlayTypeArray: sub.audioPlayTypes
@@ -192,12 +192,17 @@ export default defineComponent({
     };
 
     /** 检测当前素材是否包含所选声部 */
-    const checkCurrentIsInstrument = (instruments: string) => {
+    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
@@ -206,6 +211,15 @@ export default defineComponent({
       return isError
     }
 
+    /** 检测之后的提示 */
+    const checkCurrentInstrumentTip = (isError = false) => {
+      if(isError) {
+        message.error('您添加的资源与课件乐器不符')
+      } else {
+        message.success('添加成功');
+      }
+    }
+
     // 删除
     const onDelete = (j: number, index: number) => {
       const coursewareItem = forms.coursewareList[index];
@@ -397,13 +411,11 @@ export default defineComponent({
           materialList.forEach((m: any) => {
             forms.coursewareList[item.index || 0].list.push(m);
           });
-          console.log(item, '1212---------------')
-          if(item.isError) {
-            message.error('您添加的资源与课件乐器不符')
-          } else {
-            message.success('添加成功');
-          }
-          
+          // if(item.isError) {
+          //   message.error('您添加的资源与课件乐器不符')
+          // } else {
+          //   message.success('添加成功');
+          // }
         }
 
         timer = setTimeout(() => {
@@ -614,7 +626,7 @@ export default defineComponent({
         eventGlobal.emit('checkCoursewareForm', 'subject')
         return
       }
-      item.isError = checkCurrentIsInstrument(item.instrumentIds) // 是否异常
+      item.isError = checkCurrentIsInstrument(item.instrumentIds, item.type) // 是否异常
       if (forms.coursewareList.length <= 0) {
         // 添加到临时对象
         forms.addCoursewareItem = item;
@@ -634,6 +646,7 @@ export default defineComponent({
         forms.addCoursewareItem = item;
       } else {
         addCoursewareItem(item, point);
+        checkCurrentInstrumentTip(item.isError)
       }
     };
 
@@ -722,7 +735,7 @@ export default defineComponent({
       forms.coursewareList.forEach((item: any) => {
         const childList = item.list || []
         childList.forEach((child: any) => {
-          child.isError = checkCurrentIsInstrument(child.instrumentIds)
+          child.isError = checkCurrentIsInstrument(child.instrumentIds, child.type)
           if(child.isError) {
             isTips = true
           }
@@ -881,7 +894,7 @@ export default defineComponent({
                             refFlag: dropItem.refFlag,
                             isCollect: dropItem.isCollect,
                             isSelected: dropItem.isSelected,
-                            isError: checkCurrentIsInstrument(dropItem.instrumentIds), // 是否异常
+                            isError: checkCurrentIsInstrument(dropItem.instrumentIds, dropItem.type), // 是否异常
                             content: dropItem.content,
                             audioPlayTypeArray: dropItem.audioPlayTypeArray,
                             removeFlag: false,
@@ -889,6 +902,7 @@ export default defineComponent({
                           },
                           evt.newDraggableIndex
                         );
+                        // checkCurrentInstrumentTip(checkCurrentIsInstrument(dropItem.instrumentIds, dropItem.type))
                       }
                     }}
                     onDrag={(event: any) => {
@@ -969,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;
                               }}>
@@ -1033,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('请选择需要添加的知识点');
               }
@@ -1096,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)
               }
             }}
           />

+ 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,

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

+ 386 - 73
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,18 @@ 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';
 export default defineComponent({
   name: 'student-practiceData',
   props: {
@@ -43,7 +54,9 @@ export default defineComponent({
   },
   setup(props) {
     const userStore = useUserStore();
-    const chartRef = ref<HTMLDivElement | null>(null);
+    const catchData = useCatchStore()
+    const message = useMessage()
+    // const chartRef = ref<HTMLDivElement | null>(null);
     // const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
     // const practiceFlag = ref(true);
     const payForm = reactive({
@@ -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,168 @@ export default defineComponent({
           }
         },
         {
-          title: '评测分数',
-          key: 'score',
-          render(row: any) {
-            return <span>{row.score}</span>;
-          }
-        },
-        {
-          title: '音准',
-          key: 'intonation',
-          render(row: any) {
-            return <span>{row.rhythmFlag ? '--' : row.intonation}</span>;
-          }
-        },
-        {
-          title: '节奏',
-          key: 'cadence',
-          render(row: any) {
-            return <span>{row.rhythmFlag ? '--' : 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,
+            ['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 +419,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 + '-' + row.userId;
+      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 },
@@ -219,7 +473,8 @@ export default defineComponent({
         userMusicFlag: null, // 是否生成作品
         minScore: null,
         maxScore: null,
-        musicStartTime: []
+        musicStartTime: [],
+        instrumentId: null
       };
       search();
       setCache({
@@ -235,8 +490,11 @@ export default defineComponent({
       }
     });
     const iframeRef = ref();
-    onMounted(() => {
-      getList();
+    onMounted(async () => {
+      state.loading = true
+      await catchData.getSubjects();
+      await getTrainingStat()
+      await getList();
     });
     return () => (
       <>
@@ -299,6 +557,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: [
@@ -312,7 +589,7 @@ export default defineComponent({
               v-model:value={state.searchForm.userMusicFlag}></CSelect>
           </NFormItem>
 
-          <NFormItem>
+          {/* <NFormItem>
             <CDatePicker
               v-model:value={state.searchForm.musicStartTime}
               separator={'至'}
@@ -324,7 +601,7 @@ export default defineComponent({
                 clearable: true
               } as any)}
               timerValue={state.searchForm.musicStartTime}></CDatePicker>
-          </NFormItem>
+          </NFormItem> */}
 
           <NFormItem>
             <NSpace justify="end">
@@ -337,7 +614,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 +650,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 +658,7 @@ export default defineComponent({
             v-model:pageTotal={state.pagination.pageTotal}
             onList={getList}
             sync
+            saveKey='studentDetail-evaluationRecords'
           />
         </div>
         <NModal
@@ -371,6 +678,12 @@ export default defineComponent({
               src={reportSrc.value}></iframe>
           </div>
         </NModal>
+
+        <CardPreview
+          v-model:show={previewShow.value}
+          item={previewItem.value}
+          isDownload={false}
+        />
       </>
     );
   }

+ 73 - 48
src/views/studentList/components/practiceData.tsx

@@ -18,7 +18,10 @@ 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';
@@ -51,6 +54,7 @@ export default defineComponent({
 
     const state = reactive({
       loading: false,
+      dayFlag: true,
       pagination: {
         page: 1,
         rows: 10,
@@ -78,12 +82,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 +96,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 +163,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 +186,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 +212,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')
         });
@@ -282,37 +267,63 @@ export default defineComponent({
     });
     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 +347,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;
+  }
+}