瀏覽代碼

修改练习数据

lex-xin 6 月之前
父節點
當前提交
e4df6ccdfb

+ 1 - 1
dev-dist/sw.js

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

+ 1 - 1
public/version.json

@@ -1 +1 @@
-{"version":1727149225174}
+{"version":1727258406989}

+ 34 - 0
src/utils/dateFormat.ts

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

+ 34 - 0
src/utils/index.ts

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

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

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

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

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

+ 395 - 66
src/views/home/components/practiceData.tsx

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

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

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