Ver Fonte

差学生管理的前2个页面

1
mo há 1 ano atrás
pai
commit
718a86400f

+ 3 - 0
src/components/layout/index.module.less

@@ -366,3 +366,6 @@
     width: 698px;
   }
 }
+.changePwdModal {
+  border-radius: 16px;
+}

+ 6 - 1
src/components/layout/layoutSilder.tsx

@@ -39,7 +39,12 @@ export default defineComponent({
         isActive: false,
         id: 3,
         path: '/classList',
-        lightList: ['/classDetail', '/classStudentRecode', '/afterWorkDetail']
+        lightList: [
+          '/classDetail',
+          '/classStudentRecode',
+          '/afterWorkDetail',
+          '/classStudentDetail'
+        ]
       },
       {
         activeIcon: studentIcon,

+ 16 - 1
src/components/layout/layoutTop.tsx

@@ -1,6 +1,6 @@
 import { defineComponent, ref } from 'vue';
 import styles from './index.module.less';
-import { NImage, NBadge, NPopover, NIcon } from 'naive-ui';
+import { NImage, NBadge, NPopover, NIcon, NModal } from 'naive-ui';
 import schoolIcon from './images/schoolIcon.png';
 import teacherIcon from './images/teacherIcon.png';
 import messageIcon from './images/messageIcon.png';
@@ -11,12 +11,14 @@ import personIcon from './images/personIcon.png';
 import { useUserStore } from '@/store/modules/users';
 import { useRouter } from 'vue-router';
 import 'animate.css';
+import ForgotPassword from '/src/views/setting/modal/forgotPassword';
 export default defineComponent({
   name: 'layoutTop',
   setup() {
     const router = useRouter();
     const showHeadFlag = ref(false);
     const users = useUserStore();
+    const showWord = ref(false);
     const info = users.getUserInfo;
     const gotoPerson = () => {
       router.push({ path: '/setting', query: { activeTab: 'person' } });
@@ -26,6 +28,7 @@ export default defineComponent({
     };
 
     const resetPwd = () => {
+      showWord.value = true;
       console.log('resetPwd');
     };
     return () => (
@@ -143,6 +146,18 @@ export default defineComponent({
               </div>
             </NPopover>
           </div>
+          <NModal
+            class={styles.changePwdModal}
+            v-model:show={showWord.value}
+            preset="dialog"
+            showIcon={false}
+            title="修改密码">
+            <ForgotPassword
+              onClose={() => {
+                showWord.value = false;
+              }}
+            />
+          </NModal>
         </div>
       </>
     );

+ 37 - 0
src/hooks/use-async.ts

@@ -0,0 +1,37 @@
+import { isReactive, isRef, onMounted } from 'vue';
+import { RouterView, useRoute, useRouter } from 'vue-router';
+import { Searchs } from '@/utils/searchs';
+
+function setLoading(loading: any, val: any) {
+  if (loading != undefined && isRef(loading)) {
+    loading.value = val;
+  } else if (loading != undefined && isReactive(loading)) {
+    loading.loading = val;
+  }
+}
+
+export const useAsync = async (
+  func: Promise<any>,
+  loading: any
+): Promise<any> => {
+  setLoading(loading, true);
+
+  return await func.finally(() => setLoading(loading, false));
+};
+
+export const getTabsCache = (callBack: any) => {
+  const route = useRoute();
+  const searchs = new Searchs(route.path);
+
+  const active = searchs.get(route.path);
+  onMounted(() => {
+    callBack(active);
+  });
+};
+
+export const setTabsCaches = (current: any, key = 'current', routes: any) => {
+  const searchs = new Searchs(routes.path);
+  searchs.update({ [key]: current }, undefined, 'form');
+  const active = searchs.get(routes.path);
+  console.log(active, 'setTabsCaches');
+};

+ 21 - 0
src/router/routes/index.ts

@@ -58,6 +58,27 @@ export const constantRoutes: RouteRecordRaw[] = [
         }
       },
       {
+        path: '/classStudentDetail',
+        name: 'classStudentDetail',
+        component: () => import('@/views/studentList/studentDetail'),
+        meta: {
+          title: '学员详情',
+          singleLayout: 'blank',
+          isClass: true
+        }
+      },
+      {
+        path: '/studentDetail',
+        name: 'studentDetail',
+        component: () => import('@/views/studentList/studentDetail'),
+        meta: {
+          title: '学员详情',
+          singleLayout: 'blank',
+          isClass: false
+        }
+      },
+
+      {
         path: '/afterWorkDetail',
         name: 'afterWorkDetail',
         component: () => import('@/views/classList/components/afterWorkDetail'),

+ 1 - 1
src/utils/contants.ts

@@ -44,6 +44,6 @@ export const evaluateDifficult = {
  */
 export const trainingStatus = {
   UNSUBMITTED: '未提交',
-  SUBMITTED: '提交',
+  SUBMITTED: '不合格',
   TARGET: '合格'
 } as any;

+ 20 - 1
src/utils/dateFormat.ts

@@ -1,4 +1,5 @@
 import dayjs from 'dayjs';
+import { get } from 'http';
 
 export function getNowDateAndMonday(time: number) {
   let timestamp = time;
@@ -91,6 +92,24 @@ export function formatTime(time: number) {
   const m = minutes.toString().length === 1 ? `0${minutes}` : minutes;
 
   const s = seconds.toString().length === 1 ? `0${seconds}` : seconds;
+  let str = '';
+  if (hours > 0) {
+    str = `${h}小时${m}分钟${s}秒`;
+  } else if (minutes > 0) {
+    str = `${m}分钟${s}秒`;
+  } else {
+    str = `${s}秒`;
+  }
+  console.log();
+  return str;
+}
 
-  return `${h} 小时 ${m} 分钟 ${s} 秒`;
+export function getMinutes(time: number) {
+  const minutes = Math.floor(time / 60);
+  return minutes;
+}
+
+export function getSecend(time: number) {
+  const seconds = Math.floor(time % 60);
+  return seconds;
 }

+ 17 - 0
src/utils/urlUtils.ts

@@ -0,0 +1,17 @@
+export function vaildUrl() {
+  const url: string = window.location.href;
+  let returnUrl = '';
+  if (/192/.test(url)) {
+    //本地环境
+    returnUrl = 'https://test.lexiaoya.cn';
+  } else if (/test/.test(url)) {
+    // dev 环境
+    returnUrl = 'https://test.lexiaoya.cn';
+  } else if (/dev/.test(url)) {
+    returnUrl = 'https://dev.kt.colexiu.com';
+  } else {
+    // 默认dev环境
+    returnUrl = 'https://kt.colexiu.com';
+  }
+  return returnUrl;
+}

+ 2 - 3
src/views/attend-class/model/train-type/index.module.less

@@ -155,7 +155,7 @@
     .BProgress {
       color: #fff;
       h4 {
-        font-size: 28px;
+        font-size: 20px;
         font-family: 'DINA';
         font-weight: bold;
         text-align: center;
@@ -231,7 +231,6 @@
   }
 }
 
-
 .offShelfBg {
   position: absolute;
   top: 0;
@@ -249,7 +248,7 @@
   .offShelfTips {
     font-size: 22px;
     font-weight: 600;
-    color: #FFFFFF;
+    color: #ffffff;
     line-height: 30px;
     padding-bottom: 32px;
   }

+ 24 - 5
src/views/attend-class/model/train-type/index.tsx

@@ -136,7 +136,9 @@ export default defineComponent({
             <div class={styles.disPreview}>
               <NProgress
                 percentage={
-                  Number(props.item.trainingTimes) / Number(props.item.allTimes)
+                  (Number(props.item.trainingTimes) /
+                    Number(props.item.allTimes)) *
+                  100
                 }
                 offset-degree={180}
                 type="circle"
@@ -148,10 +150,27 @@ export default defineComponent({
                 }
                 style="width: 120px; margin: 0  0 10px;">
                 <div class={styles.BProgress}>
-                  <h4>
-                    {props.item.trainingTimes} <span>分钟</span>{' '}
-                  </h4>
-                  <p>实际练习 </p>
+                  {props.item.trainingType === 'EVALUATION' ? (
+                    <h4>
+                      {props.item.trainingTimes}
+
+                      <span>分</span>
+                    </h4>
+                  ) : (
+                    <h4>
+                      {props.item.trainingTimes
+                        ? (props.item.trainingTimes / 60).toFixed(0)
+                        : 0}
+                      <span>分钟</span>
+                    </h4>
+                  )}
+
+                  <p>
+                    {' '}
+                    {props.item.trainingType === 'EVALUATION'
+                      ? '学生分数'
+                      : '实际练习'}{' '}
+                  </p>
                 </div>
               </NProgress>
               <p class={styles.disPreviewDivide}>

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

@@ -140,3 +140,22 @@ export const getTrainingStudentDetail = (params: any) => {
     requestType: 'form'
   });
 };
+
+/**
+ * 获取练习统计
+ */
+export const getTrainingStat = (params: any) => {
+  return request.post(`/edu-app/musicPracticeRecordStat/trainingStat`, {
+    data: params
+  });
+};
+
+/**
+ * 获取班级训练详情
+ */
+export const getTrainingClassDetail = (params: any) => {
+  return request.get(`/edu-app/lessonTraining/trainingDetail`, {
+    params,
+    requestType: 'form'
+  });
+};

+ 10 - 0
src/views/classList/classDetail.tsx

@@ -7,6 +7,7 @@ import ClassStudent from './components/classStudent';
 import AfterWork from './components/afterWork';
 import ClassRecord from './components/classRecord';
 import TestRecode from './components/testRecode';
+import { getTabsCache, setTabsCaches } from '@/hooks/use-async';
 export default defineComponent({
   name: 'base-setting',
   setup() {
@@ -16,11 +17,20 @@ export default defineComponent({
       { name: '班级管理', path: '/classList' },
       { name: route.query.name, path: '/classDetail' }
     ] as any);
+    getTabsCache((val: any) => {
+      if (val.form.tabName) {
+        activeTab.value = val.form.tabName;
+      }
+    });
+    const setTabs = (val: any) => {
+      setTabsCaches(val, 'tabName', route);
+    };
     return () => (
       <div>
         <CBreadcrumb list={routerList.value}></CBreadcrumb>
         <div class={styles.listWrap}>
           <NTabs
+            onUpdate:value={(val: any) => setTabs(val)}
             class={styles.customTabs}
             v-model:value={activeTab.value}
             size="large"

+ 34 - 6
src/views/classList/components/afterWork.tsx

@@ -17,6 +17,7 @@ import { getTrainingList } from '../api';
 import add from './images/add.png';
 import { useRoute, useRouter } from 'vue-router';
 import CDatePicker from '/src/components/CDatePicker';
+import ClassTrainingDetails from '../modals/classTrainingDetails';
 import {
   getNowDateAndMonday,
   getNowDateAndSunday,
@@ -43,7 +44,9 @@ export default defineComponent({
         pageTotal: 4
       },
       tableList: [] as any,
-      addWorkVisible: false
+      addWorkVisible: false,
+      activeRow: null as any,
+      detailVisiable: false
     });
     const router = useRouter();
     const route = useRoute();
@@ -95,14 +98,14 @@ export default defineComponent({
           title: '布置时间',
           key: 'createTime',
           render(row: any) {
-            return <>{dayjs(row.createTime).format('YYYY-MM-DD')}</>;
+            return <>{row.createTime}</>;
           }
         },
         {
           title: '截止时间',
           key: 'expireDate',
           render(row: any) {
-            return <>{dayjs(row.expireDate).format('YYYY-MM-DD')}</>;
+            return <>{row.expireDate}</>;
           }
         },
         {
@@ -159,9 +162,20 @@ export default defineComponent({
           key: 'id',
           render(row: any) {
             return (
-              <NButton text type="primary" onClick={() => gotoWorkDetail(row)}>
-                详情
-              </NButton>
+              <NSpace>
+                <NButton
+                  text
+                  type="primary"
+                  onClick={() => gotoWorkDetail(row)}>
+                  详情
+                </NButton>
+                <NButton
+                  text
+                  type="primary"
+                  onClick={() => lookWorkDetail(row)}>
+                  作业详情
+                </NButton>
+              </NSpace>
             );
           }
         }
@@ -178,6 +192,11 @@ export default defineComponent({
         }
       });
     };
+
+    const lookWorkDetail = (row: any) => {
+      state.activeRow = row;
+      state.detailVisiable = true;
+    };
     return () => (
       <div>
         <div class={styles.searchList}>
@@ -264,6 +283,15 @@ export default defineComponent({
             }}
           />
         </NModal>
+        <NModal
+          v-model:show={state.detailVisiable}
+          preset="card"
+          class={['modalTitle background', styles.wordDetailModel]}
+          title={'作业详情'}>
+          <ClassTrainingDetails
+            onClose={() => (state.detailVisiable = false)}
+            activeRow={state.activeRow}></ClassTrainingDetails>
+        </NModal>
       </div>
     );
   }

+ 10 - 4
src/views/classList/components/afterWorkDetail.tsx

@@ -169,14 +169,20 @@ export default defineComponent({
     };
 
     const goToNext = () => {
-      state.index++;
+      ++state.index;
+
       state.activeRow = state.tableList[state.index - 1];
-      TrainingDetailsRef.value.getTrainingDetail();
+
+      TrainingDetailsRef.value.getTrainingDetail(
+        state.activeRow.studentLessonTrainingId
+      );
     };
     const gotoPre = () => {
-      state.index--;
+      --state.index;
       state.activeRow = state.tableList[state.index - 1];
-      TrainingDetailsRef.value.getTrainingDetail();
+      TrainingDetailsRef.value.getTrainingDetail(
+        state.activeRow.studentLessonTrainingId
+      );
     };
     return () => (
       <div>

+ 9 - 2
src/views/classList/components/classStudent.tsx

@@ -14,7 +14,7 @@ import CSelect from '@/components/CSelect';
 import Pagination from '@/components/pagination';
 import { getStudentList } from '../api';
 import add from './images/add.png';
-import { useRoute } from 'vue-router';
+import { useRoute, useRouter } from 'vue-router';
 export default defineComponent({
   name: 'student-studentList',
   setup(props, { emit }) {
@@ -29,6 +29,7 @@ export default defineComponent({
       tableList: [] as any
     });
     const route = useRoute();
+    const router = useRouter();
     const search = () => {
       state.pagination.page = 1;
       getList();
@@ -60,6 +61,12 @@ export default defineComponent({
     onMounted(() => {
       getList();
     });
+    const gotoDetail = (row: any) => {
+      router.push({
+        path: '/classStudentDetail',
+        query: { ...route.query, studentId: row.id, studentName: row.nickname }
+      });
+    };
     const columns = () => {
       return [
         {
@@ -98,7 +105,7 @@ export default defineComponent({
           key: 'id',
           render(row: any) {
             return (
-              <NButton text type="primary">
+              <NButton text type="primary" onClick={() => gotoDetail(row)}>
                 详情
               </NButton>
             );

+ 80 - 27
src/views/classList/components/testRecode.tsx

@@ -8,6 +8,7 @@ import {
   NGi,
   NGrid,
   NImage,
+  NModal,
   NNumberAnimation,
   NSelect,
   NSpace
@@ -20,12 +21,13 @@ import {
   getNowDateAndMonday,
   getNowDateAndSunday,
   getTimes,
-  formatTime
+  getMinutes,
+  getSecend
 } from '/src/utils/dateFormat';
-import { getTestList } from '../api';
+import { getTestList, getTrainingStat } from '../api';
 import CDatePicker from '/src/components/CDatePicker';
 import { useRoute, useRouter } from 'vue-router';
-import { get } from 'lodash';
+
 export default defineComponent({
   name: 'student-studentList',
   setup(props, { emit }) {
@@ -43,12 +45,14 @@ export default defineComponent({
         rows: 10,
         pageTotal: 4
       },
-      tableList: [{ studentId: '1000578', studentName: '一十四' }] as any,
+      tableList: [] as any,
       memberNumber: 0,
       testInfo: {
         practiceDurationAvg: 0,
-        memberCount: 0
-      }
+        vipUserCount: 0,
+        practiceUserCount: 0
+      },
+      activeRow: null
     });
     const route = useRoute();
     const router = useRouter();
@@ -78,7 +82,7 @@ export default defineComponent({
           ...getTimes(timer.value, ['startTime', 'endTime'], 'YYYY-MM-DD')
         });
 
-        // state.tableList = res.data.rows;
+        state.tableList = res.data.rows;
 
         state.pagination.pageTotal = res.data.total;
         state.loading = false;
@@ -87,7 +91,21 @@ export default defineComponent({
         console.log(e);
       }
     };
+
+    const getInfo = async () => {
+      try {
+        const res = await getTrainingStat({
+          classGroupId: route.query.id,
+          ...getTimes(timer.value, ['startTime', 'endTime'], 'YYYY-MM-DD')
+        });
+        state.testInfo.practiceDurationAvg = res.data.practiceDurationAvg;
+        state.testInfo.practiceUserCount = res.data.practiceUserCount;
+      } catch (e) {
+        console.log(e);
+      }
+    };
     onMounted(() => {
+      getInfo();
       getList();
     });
     const gotoStudentDetail = (row: any) => {
@@ -114,7 +132,15 @@ export default defineComponent({
           title: '性别',
           key: 'sex',
           render(row: any) {
-            return <>{row.sex == '0' ? '女' : '男'}</>;
+            return (
+              <>
+                {row.gender + '' != 'null'
+                  ? row.gender == '0'
+                    ? '女'
+                    : '男'
+                  : '--'}
+              </>
+            );
           }
         },
         {
@@ -136,7 +162,16 @@ export default defineComponent({
           key: 'studentType',
           render(row: any) {
             return (
-              <>{row.practiceDuration ? formatTime(row.practiceDuration) : 0}</>
+              <>
+                {row.practiceDuration
+                  ? getMinutes(row.practiceDuration) > 0
+                    ? getMinutes(row.practiceDuration) +
+                      '分' +
+                      getSecend(row.practiceDuration) +
+                      '秒'
+                    : getSecend(row.practiceDuration) + '秒'
+                  : 0}
+              </>
             );
           }
         },
@@ -222,14 +257,18 @@ export default defineComponent({
           <NGrid x-gap="12" cols={8}>
             <NGi>
               <div class={styles.TrainDataItem}>
-                <p class={styles.TrainDataItemTitle}>
-                  <span>
-                    <NNumberAnimation
-                      from={0}
-                      to={state.pagination.pageTotal}></NNumberAnimation>
-                  </span>
-                  人
-                </p>
+                <div>
+                  <p class={styles.TrainDataItemTitle}>
+                    <span>
+                      <NNumberAnimation
+                        from={0}
+                        to={
+                          state.testInfo.practiceUserCount
+                        }></NNumberAnimation>
+                    </span>{' '}
+                    人
+                  </p>
+                </div>
                 <p class={styles.TrainDataItemsubTitle}>练习人数</p>
               </div>
             </NGi>
@@ -239,8 +278,8 @@ export default defineComponent({
                   <span>
                     <NNumberAnimation
                       from={0}
-                      to={state.testInfo.memberCount}></NNumberAnimation>
-                  </span>
+                      to={state.testInfo.vipUserCount}></NNumberAnimation>
+                  </span>{' '}
                 </p>
                 <p class={styles.TrainDataItemsubTitle}>会员人数</p>
@@ -249,14 +288,28 @@ export default defineComponent({
             <NGi>
               <div class={styles.TrainDataItem}>
                 <p class={styles.TrainDataItemTitle}>
-                  <span>
-                    <NNumberAnimation
-                      from={0}
-                      to={
-                        state.testInfo.practiceDurationAvg
-                      }></NNumberAnimation>
-                  </span>
-                  分钟
+                  {getMinutes(state.testInfo.practiceDurationAvg) > 0 ? (
+                    <div>
+                      <span>
+                        <NNumberAnimation
+                          from={0}
+                          to={getMinutes(
+                            state.testInfo.practiceDurationAvg
+                          )}></NNumberAnimation>
+                      </span>{' '}
+                      分
+                    </div>
+                  ) : null}
+                  <div>
+                    <span>
+                      <NNumberAnimation
+                        from={0}
+                        to={getSecend(
+                          state.testInfo.practiceDurationAvg
+                        )}></NNumberAnimation>
+                    </span>{' '}
+                    秒
+                  </div>
                 </p>
                 <p class={styles.TrainDataItemsubTitle}>平均练习时长</p>
               </div>

+ 7 - 1
src/views/classList/index.module.less

@@ -440,7 +440,9 @@
     font-weight: 400;
     color: #777777;
     line-height: 18px;
-
+    display: flex;
+    flex-direction: row;
+    align-items: center;
     span {
       font-family: 'DINA';
       font-size: 26px;
@@ -606,6 +608,9 @@
           text-align: center;
           margin-left: 8px;
         }
+        .workafterInfoDot.workafterTeacherInfoDot {
+          background: #198cfe;
+        }
       }
       p {
         font-size: 16px;
@@ -622,6 +627,7 @@
     flex-direction: row;
     align-items: center;
     justify-content: space-between;
+    flex-wrap: wrap;
   }
   .allTotal {
     font-size: 16px;

+ 16 - 13
src/views/classList/modals/TrainingDetails.tsx

@@ -5,7 +5,8 @@ import {
   NForm,
   NFormItem,
   NSelect,
-  NImage
+  NImage,
+  NScrollbar
 } from 'naive-ui';
 import { defineComponent, onMounted, reactive, ref } from 'vue';
 import { getTrainingStudentDetail } from '../api';
@@ -69,10 +70,10 @@ export default defineComponent({
       }
       return tList;
     };
-    const getTrainingDetail = async () => {
+    const getTrainingDetail = async (id: any) => {
       try {
         const res = await getTrainingStudentDetail({
-          studentLessonTrainingId: props.activeRow.studentLessonTrainingId
+          studentLessonTrainingId: id
         });
         const arr = res.data.studentLessonTrainingDetails.map((item: any) => {
           const tList = typeFormat(
@@ -91,14 +92,13 @@ export default defineComponent({
 
           studentLessonTrainingDetails: arr
         };
-        console.log(studnetInfo.value.studentLessonTrainingDetails);
       } catch (e) {
         console.log(e);
       }
     };
     expose({ getTrainingDetail });
     onMounted(() => {
-      getTrainingDetail();
+      getTrainingDetail(props.activeRow.studentLessonTrainingId);
     });
 
     return () => (
@@ -152,14 +152,17 @@ export default defineComponent({
               src={qualified}></NImage>
           ) : null}
         </div>
-        <div class={styles.workList}>
-          {studnetInfo.value.studentLessonTrainingDetails.map((item: any) => (
-            <TrainType
-              isDisabled={true}
-              isDelete={false}
-              item={item}></TrainType>
-          ))}
-        </div>
+        <NScrollbar style="max-height:400px" trigger="none">
+          <div class={styles.workList}>
+            {studnetInfo.value.studentLessonTrainingDetails.map((item: any) => (
+              <TrainType
+                style={{ marginBottom: '20px' }}
+                isDisabled={true}
+                isDelete={false}
+                item={item}></TrainType>
+            ))}
+          </div>
+        </NScrollbar>
         <NSpace class={styles.btnGroups} justify="space-between">
           <div class={styles.allTotal}>
             {props.current}/{props.total}名学生

+ 161 - 0
src/views/classList/modals/classTrainingDetails.tsx

@@ -0,0 +1,161 @@
+import {
+  NButton,
+  NSpace,
+  useMessage,
+  NForm,
+  NFormItem,
+  NSelect,
+  NImage,
+  NScrollbar
+} from 'naive-ui';
+import { defineComponent, onMounted, reactive, ref } from 'vue';
+import { getTrainingClassDetail } from '../api';
+import styles from '../index.module.less';
+import TrainType from '@/views/attend-class/model/train-type';
+import defultHeade from '@/components/layout/images/teacherIcon.png';
+import noSub from '../images/nosub.png';
+import qualified from '../images/qualified.png';
+import unqualified from '../images/unqualified.png';
+import { evaluateDifficult } from '/src/utils/contants';
+import dayjs from 'dayjs';
+export default defineComponent({
+  props: {
+    activeRow: {
+      type: Object,
+      default: () => ({ id: '' })
+    },
+    total: {
+      type: Number,
+      default: 0
+    },
+    current: {
+      type: Number,
+      default: 0
+    }
+  },
+  name: 'classTrainingDetails',
+  emits: ['close'],
+
+  setup(props, { emit, expose }) {
+    const data = reactive({
+      uploading: false
+    });
+    const teacherInfo = ref({
+      teacherName: '',
+      createTime: '',
+      expireDate: '',
+      teacherAvatar: '',
+      studentLessonTrainingDetails: [] as any
+    } as any);
+    const message = useMessage();
+    const foemsRef = ref();
+    const typeFormat = (trainingType: string, configJson: any) => {
+      let tList: string[] = [];
+
+      if (trainingType === 'EVALUATION') {
+        tList = [
+          `${evaluateDifficult[configJson.evaluateDifficult]}`,
+          '全部小节',
+          `速度${configJson.evaluateSpeed}`,
+          `${configJson.trainingTimes}分合格`
+        ];
+        console.log('configJson.evaluateDifficult--', tList);
+      } else {
+        tList = [
+          `${configJson.practiceChapterBegin}-${configJson.practiceChapterEnd}小节`,
+          `速度${configJson.practiceSpeed}`,
+          `${configJson.trainingTimes}分钟`
+        ];
+        console.log('configJson.evaluateDifficult', tList);
+      }
+      return tList;
+    };
+    const getTrainingDetail = async (id: any) => {
+      try {
+        const res = await getTrainingClassDetail({
+          trainingId: id
+        });
+        const arr = res.data.studentLessonTrainingDetails.map((item: any) => {
+          const tList = typeFormat(
+            item.trainingType,
+            JSON.parse(item.trainingContent)
+          );
+          return {
+            ...item,
+            coverImg: item.titleImg,
+            allTimes: JSON.parse(item.trainingContent).trainingTimes,
+            typeList: tList || []
+          };
+        });
+        teacherInfo.value = {
+          ...res.data,
+
+          studentLessonTrainingDetails: arr
+        };
+      } catch (e) {
+        console.log(e);
+      }
+    };
+    expose({ getTrainingDetail });
+    onMounted(() => {
+      getTrainingDetail(props.activeRow.id);
+    });
+
+    return () => (
+      <div class={[styles.trainingDetails]}>
+        <div class={styles.studentList}>
+          <div class={styles.studentHeaderWrap}>
+            <div class={styles.studentHeader}>
+              <div class={styles.studentHeaderBorder}>
+                <NImage
+                  class={styles.studentHeaderImg}
+                  src={
+                    teacherInfo.value.teacherAvatar
+                      ? teacherInfo.value.teacherAvatar
+                      : defultHeade
+                  }
+                  previewDisabled></NImage>
+              </div>
+            </div>
+
+            <div class={styles.workafterInfo}>
+              <h4>
+                {teacherInfo.value.teacherName}{' '}
+                <div
+                  class={[
+                    styles.workafterInfoDot,
+                    styles.workafterTeacherInfoDot
+                  ]}>
+                  老师
+                </div>
+              </h4>
+              <p>
+                开始时间:
+                {teacherInfo.value.createTime
+                  ? dayjs(new Date(teacherInfo.value.createTime)).format(
+                      'YYYY-MM-DD'
+                    )
+                  : '--'}{' '}
+                | 结束时间:
+                {dayjs(new Date(teacherInfo.value.expireDate)).format(
+                  'YYYY-MM-DD'
+                )}
+              </p>
+            </div>
+          </div>
+        </div>
+        <NScrollbar style="max-height:400px" trigger="none">
+          <div class={styles.workList}>
+            {teacherInfo.value.studentLessonTrainingDetails.map((item: any) => (
+              <TrainType
+                style={{ marginBottom: '20px' }}
+                isDisabled={true}
+                isDelete={false}
+                item={item}></TrainType>
+            ))}
+          </div>
+        </NScrollbar>
+      </div>
+    );
+  }
+});

+ 55 - 26
src/views/setting/index.module.less

@@ -52,32 +52,32 @@
 
     .teacherHeadWrap {
       position: relative;
-      width: 236Px;
-      height: 132Px;
+      width: 236px;
+      height: 132px;
       .headerD {
         width: 100%;
         height: 100%;
       }
 
       .defultHeade {
-        width: 116Px;
-        height: 116Px;
+        width: 116px;
+        height: 116px;
         overflow: hidden;
         border-radius: 50%;
         position: absolute;
-        top: 8Px;
-        left: 60Px;
+        top: 8px;
+        left: 60px;
       }
 
       .changeHead {
-        background-color: rgba(0, 0, 0, .7);
+        background-color: rgba(0, 0, 0, 0.7);
         display: flex;
         justify-content: center;
         align-items: center;
         font-size: 16px;
         color: #fff;
         font-weight: 600;
-        transition: opacity .3s;
+        transition: opacity 0.3s;
         cursor: pointer;
       }
 
@@ -142,12 +142,12 @@
       }
 
       .n-input__input-el {
-        height: 55Px;
-        line-height: 55Px;
+        height: 55px;
+        line-height: 55px;
         border-radius: 8px;
       }
-      .n-base-selection .n-base-selection-label{
-        height: 55Px;
+      .n-base-selection .n-base-selection-label {
+        height: 55px;
       }
     }
   }
@@ -177,35 +177,36 @@
     background-color: #198cfe !important;
   }
 
-  .option.n-base-select-option.n-base-select-option--pending .n-base-select-option__content {
+  .option.n-base-select-option.n-base-select-option--pending
+    .n-base-select-option__content {
     color: #fff !important;
     text-align: center;
   }
 }
-.select{
-  :globla{
-    .n-base-selection{
-      height: 53Px;
+.select {
+  :globla {
+    .n-base-selection {
+      height: 53px;
     }
   }
 }
 .changePwdModal {
   border-radius: 16px;
-  .wrap{
+  .wrap {
     padding: 12px 0;
-    :global{
-      .n-input{
+    :global {
+      .n-input {
         border-radius: 8px;
       }
-      .n-input .n-input__input-el{
-        height: 53Px;
+      .n-input .n-input__input-el {
+        height: 53px;
       }
-      .n-button.n-button--disabled{
+      .n-button.n-button--disabled {
         background: #aaa;
       }
     }
   }
-  .sendMsg{
+  .sendMsg {
     height: 53px;
     min-width: 108px;
   }
@@ -214,8 +215,36 @@
     height: 24px;
     cursor: pointer;
   }
-  .submitBtm{
+  .submitBtm {
     width: 45%;
     height: 47px;
   }
-}
+}
+
+.wrap {
+  padding: 12px 0;
+  :global {
+    .n-input {
+      border-radius: 8px;
+    }
+    .n-input .n-input__input-el {
+      height: 53px;
+    }
+    .n-button.n-button--disabled {
+      background: #aaa;
+    }
+  }
+}
+.sendMsg {
+  height: 53px;
+  min-width: 108px;
+}
+.pwdIcon {
+  width: 24px;
+  height: 24px;
+  cursor: pointer;
+}
+.submitBtm {
+  width: 45%;
+  height: 47px;
+}

+ 7 - 2
src/views/setting/index.tsx

@@ -1,4 +1,4 @@
-import { defineComponent, ref } from 'vue';
+import { defineComponent, ref, watch } from 'vue';
 import styles from './index.module.less';
 import { NTabs, NTabPane } from 'naive-ui';
 import PersonInfo from './components/personInfo';
@@ -14,7 +14,12 @@ export default defineComponent({
     if (route.query.activeTab) {
       activeTab.value = route.query.activeTab;
     }
-
+    watch(
+      () => route.query.activeTab,
+      val => {
+        activeTab.value = val;
+      }
+    );
     return () => (
       <div class={styles.listWrap}>
         <NTabs

+ 1 - 2
src/views/studentList/api.ts

@@ -14,8 +14,7 @@ export const getTrainingStat = (params: any) => {
  */
 export const getPracticeRecordList = (params: any) => {
   return request.post('/edu-app/musicPracticeRecord/page', {
-    data: params,
-    params
+    data: params
     // requestType: 'form'
   });
 };

+ 43 - 6
src/views/studentList/components/evaluationRecords.tsx

@@ -5,8 +5,10 @@ import {
   NDataTable,
   NForm,
   NFormItem,
+  NModal,
   NNumberAnimation,
-  NSpace
+  NSpace,
+  NTag
 } from 'naive-ui';
 import numeral from 'numeral';
 import { useECharts } from '@/hooks/web/useECharts';
@@ -17,7 +19,9 @@ import {
   getNowDateAndSunday,
   getTimes
 } from '/src/utils/dateFormat';
+import { vaildUrl } from '@/utils/urlUtils';
 import CDatePicker from '/src/components/CDatePicker';
+import { useUserStore } from '/src/store/modules/users';
 export default defineComponent({
   name: 'student-practiceData',
   props: {
@@ -27,6 +31,7 @@ export default defineComponent({
     }
   },
   setup(props) {
+    const userStore = useUserStore();
     const chartRef = ref<HTMLDivElement | null>(null);
     const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
     const practiceFlag = ref(true);
@@ -37,9 +42,10 @@ export default defineComponent({
       practiceDays: 0,
       practiceDurationTotal: 0,
       dateList: [],
-      timeList: []
+      timeList: [],
+      detailVisiable: false
     });
-
+    const reportSrc = ref('');
     const state = reactive({
       loading: false,
       pagination: {
@@ -67,11 +73,25 @@ export default defineComponent({
             return <span>{row.musicSheetName}</span>;
           }
         },
+        // 入门:BEGINNER/进阶:ADVANCED/大师:PERFORMER"
         {
           title: '评测难度',
           key: 'heardLevel',
           render(row: any) {
-            return <span>{row.heardLevel}</span>;
+            return (
+              <>
+                {row.heardLevel == null ? <span>--</span> : null}
+                {row.heardLevel == 'BEGINNER' ? (
+                  <NTag type="info">入门级</NTag>
+                ) : null}
+                {row.heardLevel == 'ADVANCED' ? (
+                  <NTag type="warning">进阶级</NTag>
+                ) : null}
+                {row.heardLevel == 'PERFORMER' ? (
+                  <NTag type="error">大师级</NTag>
+                ) : null}
+              </>
+            );
           }
         },
         {
@@ -126,11 +146,17 @@ export default defineComponent({
         ...state.pagination,
         ...getTimes(timer.value, ['startTime', 'endTime'], 'YYYY-MM-DD')
       });
-      state.tableList = res.data.trainingStatDetailList;
+      state.tableList = res.data.rows;
+      console.log(state.tableList, 'state.tableList ');
       state.pagination.pageTotal = res.data.total;
     };
     const gotoRecode = (row: any) => {
-      console.log('gotoRecode');
+      console.log(row.id, 'gotoRecode');
+      const tockn = userStore.getToken;
+      reportSrc.value =
+        vaildUrl() +
+        `/instrument/#/evaluat-report?id=${row.id}&Authorization=${tockn}`;
+      payForm.detailVisiable = true;
     };
     const search = () => {
       getList();
@@ -183,6 +209,17 @@ export default defineComponent({
             saveKey="orchestraRegistration-key"
           />
         </div>
+        <NModal
+          v-model:show={payForm.detailVisiable}
+          preset="card"
+          class={['modalTitle background', styles.reportModel]}
+          title={'评测报告'}>
+          <iframe
+            width={'100%'}
+            height={'450px'}
+            frameborder="0"
+            src={reportSrc.value}></iframe>
+        </NModal>
       </>
     );
   }

+ 94 - 36
src/views/studentList/components/practiceData.tsx

@@ -15,7 +15,9 @@ import { getTrainingStat } from '../api';
 import {
   getNowDateAndMonday,
   getNowDateAndSunday,
-  getTimes
+  getTimes,
+  getMinutes,
+  getSecend
 } from '/src/utils/dateFormat';
 import CDatePicker from '/src/components/CDatePicker';
 export default defineComponent({
@@ -64,7 +66,21 @@ export default defineComponent({
           title: '练习时长(分钟)',
           key: 'practiceDuration',
           render(row: any) {
-            return <span>{row.practiceDuration}分钟</span>;
+            return (
+              <>
+                {' '}
+                <>
+                  {row.practiceDuration
+                    ? getMinutes(row.practiceDuration) > 0
+                      ? getMinutes(row.practiceDuration) +
+                        '分' +
+                        getSecend(row.practiceDuration) +
+                        '秒'
+                      : getSecend(row.practiceDuration) + '秒'
+                    : 0 + '分钟'}
+                </>
+              </>
+            );
           }
         }
       ];
@@ -100,7 +116,9 @@ export default defineComponent({
           {
             type: 'value',
             axisLabel: {
-              formatter: '{value} min'
+              formatter: (value: any) => {
+                return getMinutes(value) + 'min';
+              }
             },
             axisTick: {
               show: false
@@ -127,14 +145,18 @@ export default defineComponent({
             type: 'bar',
             barWidth: '48px',
             stack: 'total',
-            label: {
-              // 柱图头部显示值
-              show: true,
-              position: 'top',
-              color: '#333',
-              fontSize: '12px',
-              fontWeight: 600
-            },
+            // label: {
+            //   // 柱图头部显示值
+            //   formatter: (value: any) => {
+            //     console.log(value);
+            //     return getMinutes(value.value);
+            //   },
+            //   show: true,
+            //   position: 'top',
+            //   color: '#333',
+            //   fontSize: '12px',
+            //   fontWeight: 600
+            // },
 
             itemStyle: {
               normal: {
@@ -157,15 +179,17 @@ export default defineComponent({
           if (Array.isArray(item)) {
             return [
               item[0].axisValueLabel,
-              ...item.map(
-                (d: any) =>
-                  `<br/>${
-                    d.marker
-                  }<span style="margin-top:10px;margin-left:5px;font-size: 13px;font-weight: 500;
+              ...item.map((d: any) => {
+                let str;
+                getMinutes(d.value) > 0
+                  ? (str =
+                      getMinutes(d.value) + '分' + getSecend(d.value) + '秒')
+                  : (str = getSecend(d.value) + '秒');
+                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
-                  line-height: 18px;">练习时长: ${d.value}${'分钟'} </span>`
-              )
+                  line-height: 18px;">练习时长: ${str} </span>`;
+              })
             ].join('');
           } else {
             return item;
@@ -249,34 +273,68 @@ export default defineComponent({
             <div class={styles.TrainDataTopLeft}>
               <div class={styles.TrainDataItem}>
                 <p class={styles.TrainDataItemTitle}>
-                  <span>
-                    <NNumberAnimation
-                      from={0}
-                      to={payForm.practiceDurationTotal}></NNumberAnimation>
-                  </span>
-                  人
+                  {getMinutes(payForm.practiceDurationTotal) > 0 ? (
+                    <div>
+                      <span>
+                        <NNumberAnimation
+                          from={0}
+                          to={getMinutes(
+                            payForm.practiceDurationTotal
+                          )}></NNumberAnimation>
+                      </span>{' '}
+                      分
+                    </div>
+                  ) : null}
+                  <div>
+                    <span>
+                      <NNumberAnimation
+                        from={0}
+                        to={getSecend(
+                          payForm.practiceDurationTotal
+                        )}></NNumberAnimation>
+                    </span>{' '}
+                    秒
+                  </div>
                 </p>
                 <p class={styles.TrainDataItemsubTitle}>累计练习时长</p>
               </div>
               <div class={styles.TrainDataItem}>
                 <p class={styles.TrainDataItemTitle}>
-                  <span>
-                    <NNumberAnimation
-                      from={0}
-                      to={payForm.practiceDurationAvg}></NNumberAnimation>
-                  </span>
-                  分钟
+                  {getMinutes(payForm.practiceDurationAvg) > 0 ? (
+                    <div>
+                      <span>
+                        <NNumberAnimation
+                          from={0}
+                          to={getMinutes(
+                            payForm.practiceDurationAvg
+                          )}></NNumberAnimation>
+                      </span>{' '}
+                      分
+                    </div>
+                  ) : null}
+                  <div>
+                    <span>
+                      <NNumberAnimation
+                        from={0}
+                        to={getSecend(
+                          payForm.practiceDurationTotal
+                        )}></NNumberAnimation>
+                    </span>{' '}
+                    秒
+                  </div>
                 </p>
                 <p class={styles.TrainDataItemsubTitle}>平均练习时长</p>
               </div>
               <div class={styles.TrainDataItem}>
                 <p class={styles.TrainDataItemTitle}>
-                  <span>
-                    <NNumberAnimation
-                      from={0}
-                      to={payForm.practiceDays}></NNumberAnimation>
-                  </span>
-                  天
+                  <div>
+                    <span>
+                      <NNumberAnimation
+                        from={0}
+                        to={payForm.practiceDays}></NNumberAnimation>
+                    </span>{' '}
+                    天
+                  </div>
                 </p>
                 <p class={styles.TrainDataItemsubTitle}>练习天数</p>
               </div>

+ 7 - 0
src/views/studentList/index.module.less

@@ -133,6 +133,9 @@
         margin-right: 40px;
 
         .TrainDataItemTitle {
+          display: flex;
+          flex-direction: row;
+          align-items: center;
           text-align: center;
           font-size: 13px;
           font-weight: 400;
@@ -245,3 +248,7 @@
     }
   }
 }
+.reportModel {
+  width: 830px;
+  overflow: hidden;
+}

+ 14 - 3
src/views/studentList/index.tsx

@@ -13,6 +13,7 @@ import SearchInput from '@/components/searchInput';
 import CSelect from '@/components/CSelect';
 import Pagination from '@/components/pagination';
 import add from './images/add.png';
+import { useRoute, useRouter } from 'vue-router';
 export default defineComponent({
   name: 'student-studentList',
   setup(props, { emit }) {
@@ -31,12 +32,13 @@ export default defineComponent({
       },
       tableList: [
         {
-          studentName: '胡小小',
+          nickname: '汤科斯',
           phone: '17625367893',
           sex: '0',
           className: '一年级3班',
           classType: 'normal',
-          studentType: 'member'
+          studentType: 'member',
+          id: '1001415'
         },
         {
           studentName: '丁曼蓉',
@@ -64,6 +66,8 @@ export default defineComponent({
         }
       ] as any
     });
+    const route = useRoute();
+    const router = useRouter();
     const search = () => {
       console.log('search', state);
     };
@@ -124,7 +128,7 @@ export default defineComponent({
           key: 'id',
           render(row: any) {
             return (
-              <NButton text type="primary">
+              <NButton text type="primary" onClick={() => gotoDetail(row)}>
                 详情
               </NButton>
             );
@@ -132,6 +136,13 @@ export default defineComponent({
         }
       ];
     };
+
+    const gotoDetail = (row: any) => {
+      router.push({
+        path: '/studentDetail',
+        query: { ...route.query, studentId: row.id, studentName: row.nickname }
+      });
+    };
     return () => (
       <div class={styles.listWrap}>
         <div class={styles.searchList}>

+ 124 - 0
src/views/studentList/studentDetail.tsx

@@ -0,0 +1,124 @@
+import { defineComponent, onMounted, reactive, ref } from 'vue';
+import styles from '@/views/classList/index.module.less';
+import {
+  NButton,
+  NDataTable,
+  NForm,
+  NFormItem,
+  NImage,
+  NSelect,
+  NSpace,
+  NTabPane,
+  NTabs
+} from 'naive-ui';
+import {
+  getStudentDetail,
+  getTrainingStudentList
+} from '@/views/classList/api';
+import { useRoute } from 'vue-router';
+import CBreadcrumb from '/src/components/CBreadcrumb';
+import defultHeade from '@/components/layout/images/teacherIcon.png';
+import femaleIcon from '@/views/setting/images/femaleIcon.png';
+import maleIcon from '@/views/setting/images/maleIcon.png';
+import PracticeData from '@/views/studentList/components/practiceData';
+import EvaluationRecords from '@/views/studentList/components/evaluationRecords';
+
+import dayjs from 'dayjs';
+export default defineComponent({
+  name: 'studentDetail',
+  setup(props, { emit }) {
+    const state = reactive({
+      studentInfo: { avatar: '', nickname: '', gender: null, subjectNames: '' }
+    });
+    const activeStudentTab = ref('textRcode');
+    const route = useRoute();
+    console.log(route.meta.isClass);
+    const routerList = ref(
+      route.meta.isClass
+        ? [
+            { name: '班级管理', path: '/classList' },
+            { name: route.query.name, path: '/classDetail' },
+            { name: route.query.studentName, path: '/classStudentRecode' }
+          ]
+        : ([
+            { name: '学生管理', path: '/studentList' },
+
+            { name: route.query.studentName, path: '/classStudentRecode' }
+          ] as any)
+    );
+
+    const getWorkInfo = async () => {
+      console.log(route.query.studentId);
+      try {
+        const res = await getStudentDetail({
+          id: route.query.studentId
+        });
+        state.studentInfo = { ...res.data };
+      } catch (e) {
+        console.log(e);
+      }
+    };
+    onMounted(() => {
+      getWorkInfo();
+    });
+
+    return () => (
+      <div>
+        <CBreadcrumb list={routerList.value}></CBreadcrumb>
+        <div class={styles.listWrap}>
+          <div class={styles.teacherList}>
+            <div class={styles.teacherHeader}>
+              <div class={styles.teacherHeaderBorder}>
+                <NImage
+                  class={styles.teacherHeaderImg}
+                  src={
+                    state.studentInfo.avatar
+                      ? state.studentInfo.avatar
+                      : defultHeade
+                  }
+                  previewDisabled></NImage>
+              </div>
+            </div>
+            <div class={styles.workafterInfo}>
+              <h4 class={styles.studentGender}>
+                {state.studentInfo.nickname}{' '}
+                <NImage
+                  src={
+                    state.studentInfo.gender ? maleIcon : femaleIcon
+                  }></NImage>
+              </h4>
+              <p>
+                {route.query.name}{' '}
+                {state.studentInfo.subjectNames
+                  ? '|' + state.studentInfo.subjectNames
+                  : ''}
+              </p>
+            </div>
+          </div>
+          <NTabs
+            class={styles.customTabs}
+            v-model:value={activeStudentTab.value}
+            size="large"
+            animated
+            pane-wrapper-style="margin: 0 -4px"
+            pane-style="padding-left: 4px; padding-right: 4px; box-sizing: border-box;">
+            <NTabPane name="baseInfo" tab="基本信息">
+              基本信息
+            </NTabPane>
+            <NTabPane name="afterWork" tab="课后训练">
+              课后训练
+            </NTabPane>
+            <NTabPane name="textRcode" tab="练习记录">
+              <PracticeData
+                studentId={route.query.studentId as string}></PracticeData>
+            </NTabPane>
+            <NTabPane name="evaluatingRcode" tab="评测记录">
+              <EvaluationRecords
+                studentId={route.query.studentId as string}></EvaluationRecords>
+            </NTabPane>
+          </NTabs>
+        </div>
+      </div>
+    );
+  }
+});