Browse Source

添加统计

lex-xin 3 weeks ago
parent
commit
fe87176914
35 changed files with 1589 additions and 224 deletions
  1. 6 1
      src/components/m-search/index.module.less
  2. 150 0
      src/components/the-time-range/index.module.less
  3. 278 0
      src/components/the-time-range/index.tsx
  4. 1 1
      src/helpers/constant.ts
  5. 1 1
      src/router/routes-common.ts
  6. 95 76
      src/views/activation-code/index.module.less
  7. 51 12
      src/views/activation-code/record.tsx
  8. 3 1
      src/views/collection-record/api.ts
  9. 86 25
      src/views/collection-record/component/list.module.less
  10. 62 60
      src/views/collection-record/component/list.tsx
  11. BIN
      src/views/collection-record/image/icon-arrow-active.png
  12. BIN
      src/views/collection-record/image/icon-arrow.png
  13. 0 4
      src/views/courseware-list/index.tsx
  14. 1 4
      src/views/exercise-record/exercis-detail.tsx
  15. BIN
      src/views/exercise-record/images/bg.png
  16. BIN
      src/views/exercise-record/images/icon-1.png
  17. BIN
      src/views/exercise-record/images/icon-2.png
  18. BIN
      src/views/exercise-record/images/icon-album.png
  19. BIN
      src/views/exercise-record/images/icon-arrow-active.png
  20. BIN
      src/views/exercise-record/images/icon-arrow.png
  21. BIN
      src/views/exercise-record/images/icon-bird.png
  22. BIN
      src/views/exercise-record/images/icon-number.png
  23. BIN
      src/views/exercise-record/images/icon-p-p.png
  24. BIN
      src/views/exercise-record/images/l-c.png
  25. BIN
      src/views/exercise-record/images/l-l.png
  26. BIN
      src/views/exercise-record/images/l-r.png
  27. BIN
      src/views/exercise-record/images/p-c.png
  28. BIN
      src/views/exercise-record/images/p-l.png
  29. BIN
      src/views/exercise-record/images/p-r.png
  30. 324 0
      src/views/exercise-record/index.module.less
  31. 468 0
      src/views/exercise-record/index.tsx
  32. 22 9
      src/views/exercise-record/modals/detail-item.module.less
  33. 39 28
      src/views/exercise-record/modals/detail-item.tsx
  34. 1 1
      src/views/school-register/index.tsx
  35. 1 1
      vite.config.ts

+ 6 - 1
src/components/m-search/index.module.less

@@ -1,12 +1,17 @@
 .m-search {
   --van-cell-background-color: transparent;
 
+
   input::placeholder {
     color: var(--k-gray-4);
     font-size: 13px;
   }
 
   :global {
+    .van-search__content {
+      padding-left: 10px !important;
+    }
+
     .van-field__control {
       -webkit-user-select: text !important;
       user-select: text !important;
@@ -84,7 +89,7 @@
   .leftIcon {
     width: 14px;
     height: 14px;
-    margin-top: -2px;
+    // margin-top: -2px;
 
     img {
       width: 100%;

+ 150 - 0
src/components/the-time-range/index.module.less

@@ -0,0 +1,150 @@
+.searchPopup {
+  :global {
+    .van-popup__close-icon {
+      top: 19px;
+    }
+  }
+}
+
+.popupContainer {
+  // max-height: 504px;
+  // overflow-x: hidden;
+  // overflow-y: auto;
+  .popupTitle {
+    position: sticky;
+    z-index: 1;
+    top: 0;
+    text-align: center;
+    font-weight: 600;
+    font-size: 18px;
+    color: #333333;
+    line-height: 24px;
+    padding: 18px 0 12px;
+  }
+
+  .popupSearchList {
+    // min-height: 30vh;
+    // max-height: 50vh;
+    overflow: hidden auto;
+  }
+
+  .popupSection {
+    padding: 0 16px 18px;
+    .title {
+      display: flex;
+      justify-content: space-between;
+      padding-bottom: 10px;
+      span {
+        display: flex;
+        align-items: center;
+        font-weight: 600;
+        font-size: 15px;
+        color: #333333;
+        line-height: 18px;
+        &::before {
+          content: '';
+          display: inline-block;
+          width: 4px;
+          height: 11px;
+          background: linear-gradient(180deg, #259cfe 0%, #44c9ff 100%);
+          border-radius: 3px;
+          margin-right: 4px;
+        }
+      }
+    }
+
+    .timeCount {
+      display: flex;
+      align-items: center;
+
+      p {
+        margin-left: 10px;
+        flex: 1;
+        background: #f6f6f6;
+        border: 1px solid #f6f6f6;
+        border-radius: 4px;
+        font-size: 13px;
+        color: #333;
+        line-height: 18px;
+        text-align: center;
+        padding: 6px 0;
+        &:first-child {
+          margin-left: 0;
+        }
+
+        &.active {
+          background: #ebf8ff;
+          border-radius: 4px;
+          border: 1px solid rgba(28, 172, 241, 0.7);
+          color: #1cacf1;
+        }
+      }
+    }
+
+    .timeSubject {
+      flex-wrap: wrap;
+      margin-left: -5px;
+      margin-right: -5px;
+      p {
+        width: calc(33.333% - 10px);
+        padding: 6px 3px;
+        margin: 0 5px;
+        flex: none;
+        overflow: hidden;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+        margin-bottom: 9px;
+        box-sizing: border-box;
+        &:first-child {
+          margin-left: 5px;
+        }
+      }
+    }
+
+    .timeRang {
+      margin-top: 10px;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+
+      .timeInput {
+        width: 159px;
+        line-height: 32px;
+        text-align: center;
+        background: #f8f8f8;
+        border-radius: 4px;
+        font-size: 13px;
+        color: #333333;
+        cursor: pointer;
+      }
+      .hasValue {
+        color: #333;
+      }
+
+      .timeUnit {
+        width: 12px;
+        height: 1px;
+        background: #d0d0d0;
+      }
+    }
+  }
+
+  .popupBottom {
+    position: sticky;
+    z-index: 1;
+    bottom: 0;
+    border-top: 1px solid #f2f2f2;
+    padding: 20px 13px 30px;
+    display: flex;
+    align-items: center;
+    :global {
+      .van-button {
+        font-size: 16px;
+        font-weight: 500 !important;
+      }
+      .van-button + .van-button {
+        margin-left: 15px;
+      }
+    }
+  }
+}

+ 278 - 0
src/components/the-time-range/index.tsx

@@ -0,0 +1,278 @@
+import { defineComponent, PropType, reactive, ref, toRef, watch } from 'vue';
+import styles from './index.module.less';
+import dayjs from 'dayjs';
+import { Button, DatePicker, Popup, showToast } from 'vant';
+import { formatterDatePicker } from '@/helpers/utils';
+
+export type TIME_TYPE = 'MONTH' | 'THREE_MONTH' | 'HALF_YEAR' | 'YEAR' | '';
+
+/** 获取时间范围 */
+export const getTimeRange = (type: TIME_TYPE) => {
+  if (type === 'MONTH') {
+    return {
+      startTime: dayjs().format('YYYY-MM') + '-01',
+      endTime: dayjs().format('YYYY-MM-DD')
+    };
+  } else if (type === 'THREE_MONTH') {
+    return {
+      startTime: dayjs().subtract(3, 'month').format('YYYY-MM-DD'),
+      endTime: dayjs().format('YYYY-MM-DD')
+    };
+  } else if (type === 'HALF_YEAR') {
+    return {
+      startTime: dayjs().subtract(6, 'month').format('YYYY-MM-DD'),
+      endTime: dayjs().format('YYYY-MM-DD')
+    };
+  } else if (type === 'YEAR') {
+    return {
+      startTime: dayjs().subtract(1, 'year').format('YYYY-MM-DD'),
+      endTime: dayjs().format('YYYY-MM-DD')
+    };
+  }
+};
+
+/** 获取时间,拆分成数组 */
+export const getTimeArray = (time?: string) => {
+  const currentTime = time ? dayjs(time) : dayjs();
+  return [
+    currentTime.format('YYYY'),
+    currentTime.format('MM'),
+    currentTime.format('DD')
+  ];
+};
+
+export default defineComponent({
+  name: 'the-time-range',
+  props: {
+    /** 是否显示 */
+    show: {
+      type: Boolean,
+      default: false
+    },
+    /** 默认时间范围 */
+    typeUnit: {
+      type: String as PropType<TIME_TYPE>,
+      default: ''
+    }
+  },
+  emits: ['close', 'confirm', 'update:show'],
+  setup(props, { emit }) {
+    const showPopoverTime = ref(toRef(props.show));
+    const searchObj = reactive({
+      type: ''
+    });
+    const timeRange = getTimeRange('');
+
+    const forms = reactive({
+      startTimeStatus: false,
+      startTimeClosedStatus: false,
+      endTimeMinDate: timeRange?.startTime
+        ? new Date(timeRange?.startTime)
+        : new Date(),
+      endTimeMaxDate: dayjs(
+        timeRange?.startTime ? new Date(timeRange?.startTime) : new Date()
+      )
+        .add(1, 'year')
+        .toDate(),
+      endTimeStatus: false,
+      endTimeClosedStatus: false,
+      startTime: getTimeArray(timeRange?.startTime),
+      startTimeStr: timeRange?.startTime || '',
+      endTime: getTimeArray(timeRange?.endTime),
+      endTimeStr: timeRange?.endTime || ''
+    });
+
+    const onChangeTime = (type: TIME_TYPE) => {
+      if (searchObj.type === type) return;
+      searchObj.type = type;
+
+      resetTime(type);
+    };
+    // 格式化
+    const resetTime = (type: TIME_TYPE) => {
+      const timeRang = getTimeRange(type);
+
+      forms.startTime = getTimeArray(timeRang?.startTime);
+      forms.startTimeStr = timeRang?.startTime || '';
+      forms.endTimeMinDate = dayjs(timeRang?.startTime || '').toDate();
+      forms.endTimeMaxDate = dayjs(timeRang?.startTime || '')
+        .add(1, 'year')
+        .toDate();
+      forms.endTime = getTimeArray(timeRang?.endTime);
+      forms.endTimeStr = timeRang?.endTime || '';
+    };
+
+    // 确认
+    const onConfirm = () => {
+      if (forms.startTimeStr && !forms.endTimeStr || !forms.startTimeStr && forms.endTimeStr) {
+        showToast('请选择时间范围');
+        return;
+      }
+
+      showPopoverTime.value = false
+      // 确认
+      emit('confirm', {
+        startTime: forms.startTimeStr,
+        endTime: forms.endTimeStr
+      });
+    };
+
+    watch(() => props.show, () => {
+      showPopoverTime.value = props.show
+    })
+
+    return () => (
+      <div>
+        <Popup
+          v-model:show={showPopoverTime.value}
+          closeable
+          round
+          position="bottom"
+          class={styles.searchPopup}
+          onClosed={() => {
+            emit("update:show", false)
+          }}>
+          <div class={styles.popupContainer}>
+            <div class={styles.popupTitle}>筛选</div>
+
+            <div class={styles.popupSearchList}>
+              <div class={styles.popupSection}>
+                <div class={styles.title}>
+                  <span>时间</span>
+                </div>
+
+                <div class={styles.timeCount}>
+                  <p
+                    onClick={() => onChangeTime('MONTH')}
+                    class={searchObj.type === 'MONTH' ? styles.active : ''}>
+                    本月
+                  </p>
+                  <p
+                    onClick={() => onChangeTime('THREE_MONTH')}
+                    class={
+                      searchObj.type === 'THREE_MONTH' ? styles.active : ''
+                    }>
+                    近三个月
+                  </p>
+                  <p
+                    onClick={() => onChangeTime('HALF_YEAR')}
+                    class={searchObj.type === 'HALF_YEAR' ? styles.active : ''}>
+                    近半年
+                  </p>
+                  <p
+                    onClick={() => onChangeTime('YEAR')}
+                    class={searchObj.type === 'YEAR' ? styles.active : ''}>
+                    近一年
+                  </p>
+                </div>
+
+                <div class={styles.timeRang}>
+                  <p
+                    class={[
+                      styles.timeInput,
+                      forms.startTimeStr && styles.hasValue
+                    ]}
+                    onClick={() => {
+                      forms.startTimeStatus = true;
+                      forms.startTimeClosedStatus = true;
+
+                      console.log(forms.startTime, 'startTime');
+                    }}>
+                    {forms.startTimeStr || '起始时间'}
+                  </p>
+                  <p class={styles.timeUnit}></p>
+                  <p
+                    class={[
+                      styles.timeInput,
+                      forms.endTimeStr && styles.hasValue
+                    ]}
+                    onClick={() => {
+                      forms.endTimeStatus = true;
+                      forms.endTimeClosedStatus = true;
+                    }}>
+                    {forms.endTimeStr || '终止时间'}
+                  </p>
+                </div>
+              </div>
+            </div>
+
+            <div class={styles.popupBottom}>
+              <Button
+                round
+                block
+                type="default"
+                onClick={() => {
+                  searchObj.type = '';
+                  resetTime('');
+                }}>
+                重置
+              </Button>
+              <Button round block type="primary" onClick={onConfirm}>
+                确认
+              </Button>
+            </div>
+          </div>
+        </Popup>
+
+        {/* 开始日期 */}
+        <Popup
+          v-model:show={forms.startTimeStatus}
+          position="bottom"
+          round
+          class={'popupBottomSearch'}
+          onClosed={() => {
+            forms.startTimeClosedStatus = false;
+          }}>
+          {forms.startTimeClosedStatus && (
+            <DatePicker
+              v-model={forms.startTime}
+              formatter={formatterDatePicker}
+              onCancel={() => (forms.startTimeStatus = false)}
+              onConfirm={(val: any) => {
+                console.log(val, 'val');
+                forms.startTime = val.selectedValues;
+                const valTime = val.selectedValues.join('-');
+                forms.startTimeStr = dayjs(valTime).format('YYYY-MM-DD');
+                forms.startTimeStatus = false;
+
+                forms.endTimeMinDate = dayjs(valTime || new Date()).toDate();
+                forms.endTimeMaxDate = dayjs(valTime || new Date())
+                  .add(1, 'year')
+                  .toDate();
+                forms.endTime = val.selectedValues;
+                forms.endTimeStr = '';
+                searchObj.type = '' as any;
+              }}
+            />
+          )}
+        </Popup>
+        {/* 结束日期 */}
+        <Popup
+          v-model:show={forms.endTimeStatus}
+          position="bottom"
+          round
+          class={'popupBottomSearch'}
+          onClosed={() => {
+            forms.endTimeClosedStatus = false;
+          }}>
+          {forms.endTimeClosedStatus && (
+            <DatePicker
+              v-model={forms.endTime}
+              minDate={forms.endTimeMinDate}
+              maxDate={forms.endTimeMaxDate}
+              formatter={formatterDatePicker}
+              onCancel={() => (forms.endTimeStatus = false)}
+              onConfirm={(val: any) => {
+                forms.endTime = val.selectedValues;
+                forms.endTimeStatus = false;
+                const valTime = val.selectedValues.join('-');
+                forms.endTimeStr = valTime;
+                searchObj.type = '' as any;
+              }}
+            />
+          )}
+        </Popup>
+      </div>
+    );
+  }
+});

+ 1 - 1
src/helpers/constant.ts

@@ -29,7 +29,7 @@ export const gradeYear = {
 export const schoolType = {
   PRIMARY: '小学',
   JUNIOR: '初中',
-  PRIMARY_JUNIOR: '小初一体'
+  PRIMARY_JUNIOR: '九年一贯制'
 };
 
 /**

+ 1 - 1
src/router/routes-common.ts

@@ -62,7 +62,7 @@ export default [
       {
         path: '/exercise-record',
         name: 'exercise-record',
-        component: () => import('@/views/exercise-record/exercis-detail'),
+        component: () => import('@/views/exercise-record/index'),
         meta: {
           title: '学练统计'
         }

+ 95 - 76
src/views/activation-code/index.module.less

@@ -53,7 +53,11 @@
       bottom: -2px;
       width: 48px;
       height: 6px;
-      background: linear-gradient(270deg, rgba(119, 255, 239, 0.59) 0%, #42CDFF 100%);
+      background: linear-gradient(
+        270deg,
+        rgba(119, 255, 239, 0.59) 0%,
+        #42cdff 100%
+      );
       opacity: 0.5;
       z-index: -1;
     }
@@ -69,11 +73,11 @@
   }
 
   .recordBtn {
-    background: #FFFFFF;
+    background: #ffffff;
     border-radius: 50px;
-    border: 1px solid #1CACF1;
+    border: 1px solid #1cacf1;
     font-size: 14px;
-    color: #0E71BC;
+    color: #0e71bc;
     line-height: 20px;
     padding: 4px 7px;
     display: flex;
@@ -117,13 +121,34 @@
 .section {
   border-radius: 20px 20px 0px 0px;
   padding: 13px 13px 16px;
-  background: #F8F9FC;
+  background: #f8f9fc;
   position: relative;
   z-index: 1;
 }
 
+.sectionSearch {
+  .searchDropDown {
+    display: flex;
+    align-items: center;
+    font-size: 14px;
+    color: #191919;
+    line-height: 20px;
+    padding-right: 12px;
+  }
+  .active {
+    color: #1CACF1;
+  }
+  span {
+    padding-right: 4px;
+  }
+  .iconArrow {
+    width: 9px;
+    height: 5px;
+  }
+}
+
 .activationContent {
-  background: #FFFFFF;
+  background: #ffffff;
   border-radius: 20px;
   // margin: 13px;
   overflow: hidden;
@@ -132,7 +157,7 @@
 .inputGroup {
   position: relative;
   padding: 26px 12px;
-  border-bottom: 1px dashed #EBEBEB;
+  border-bottom: 1px dashed #ebebeb;
 
   &::before,
   &::after {
@@ -142,7 +167,7 @@
     width: 16px;
     height: 16px;
     border: 8px solid #fff;
-    background-color: #F8F9FC;
+    background-color: #f8f9fc;
     border-radius: 50%;
   }
 
@@ -157,12 +182,12 @@
   }
 
   .input {
-    background: #F2F2F2;
+    background: #f2f2f2;
     border-radius: 29px;
     --van-cell-line-height: 24px;
     --van-field-icon-size: 24px;
     --van-padding-base: 10px;
-    padding: 8px
+    padding: 8px;
   }
 
   .btnGroup {
@@ -188,7 +213,7 @@
       margin-right: 8px;
       width: 4px;
       height: 14px;
-      background: linear-gradient(135deg, #31C7FF 0%, #007AFE 100%);
+      background: linear-gradient(135deg, #31c7ff 0%, #007afe 100%);
       border-radius: 10px;
     }
   }
@@ -234,65 +259,64 @@
   }
 }
 
-
-.prodSection {
-  padding-top: 16px;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-
-  .title {
-    font-weight: 500;
-    font-size: 16px;
-    color: #131415;
-    line-height: 22px;
-    display: flex;
-    align-items: center;
-
-    &::before {
-      display: inline-block;
-      content: '';
-      margin-right: 8px;
-      width: 4px;
-      height: 14px;
-      background: #1BA5FF;
-      border-radius: 10px;
-    }
-  }
-
-  .times {
-    font-size: 14px;
-    color: #333333;
-    line-height: 20px;
-    display: flex;
-    align-items: center;
-
-    &::after {
-      content: '';
-      margin-left: 4px;
-      display: inline-block;
-      width: 9px;
-      height: 5px;
-      background: url('./images/icon-arrow.png') no-repeat center;
-      background-size: contain;
-    }
-
-    &.active {
-      color: #1CACF1;
-
-      &::after {
-        background: url('./images/icon-arrow-active.png') no-repeat center;
-        background-size: contain;
-      }
-    }
-  }
-}
+// .prodSection {
+//   padding-top: 16px;
+//   display: flex;
+//   align-items: center;
+//   justify-content: space-between;
+
+//   .title {
+//     font-weight: 500;
+//     font-size: 16px;
+//     color: #131415;
+//     line-height: 22px;
+//     display: flex;
+//     align-items: center;
+
+//     &::before {
+//       display: inline-block;
+//       content: '';
+//       margin-right: 8px;
+//       width: 4px;
+//       height: 14px;
+//       background: #1BA5FF;
+//       border-radius: 10px;
+//     }
+//   }
+
+//   .times {
+//     font-size: 14px;
+//     color: #333333;
+//     line-height: 20px;
+//     display: flex;
+//     align-items: center;
+
+//     &::after {
+//       content: '';
+//       margin-left: 4px;
+//       display: inline-block;
+//       width: 9px;
+//       height: 5px;
+//       background: url('./images/icon-arrow.png') no-repeat center;
+//       background-size: contain;
+//     }
+
+//     &.active {
+//       color: #1CACF1;
+
+//       &::after {
+//         background: url('./images/icon-arrow-active.png') no-repeat center;
+//         background-size: contain;
+//       }
+//     }
+//   }
+// }
 
 .sectionList {
   height: calc(100vh - var(--header-height));
   overflow-x: hidden;
   overflow-y: auto;
-  background: #F8F9FC;
+  background: #f8f9fc;
   padding: 0 13px;
 }
 
@@ -303,12 +327,11 @@
   // min-height: 141px;
   // max-width: 349px;
 
-
   .itemTop {
     position: relative;
     z-index: 1;
-    background: linear-gradient(180deg, #EFEAFE 0%, #E1F4FF 10%, #FFFFFF 50%);
-    border: 1px solid #FFFFFF;
+    background: linear-gradient(180deg, #efeafe 0%, #e1f4ff 10%, #ffffff 50%);
+    border: 1px solid #ffffff;
     border-radius: 10px;
 
     &::after {
@@ -318,7 +341,7 @@
       right: 14px;
       content: '';
       height: 0px;
-      border-bottom: 2px dotted #EBEBEB;
+      border-bottom: 2px dotted #ebebeb;
     }
   }
 
@@ -353,18 +376,16 @@
         display: inline-block;
         width: 100%;
         height: 6px;
-        background: linear-gradient(270deg, #CCF1FA 0%, #8CDDFC 100%);
+        background: linear-gradient(270deg, #ccf1fa 0%, #8cddfc 100%);
       }
     }
-
-
   }
 
   .itemCode {
     position: relative;
     font-weight: 500;
     font-size: 20px;
-    color: #1CACF1;
+    color: #1cacf1;
     line-height: 28px;
     padding: 6px 12px 16px;
     // padding-top: 24px;
@@ -372,7 +393,7 @@
   }
 
   .itemTime {
-    border: 1px solid #FFFFFF;
+    border: 1px solid #ffffff;
     border-radius: 10px;
     background: #fff;
     position: relative;
@@ -383,8 +404,6 @@
     color: #777777;
     line-height: 20px;
 
-
-
     span {
       color: #333333;
     }

+ 51 - 12
src/views/activation-code/record.tsx

@@ -6,13 +6,16 @@ import MHeader from '@/components/m-header';
 import { useRouter } from 'vue-router';
 import { postMessage } from '@/helpers/native-message';
 import MSearch from '@/components/m-search';
-import { Calendar, List } from 'vant';
+import { List } from 'vant';
 import dayjs from 'dayjs';
 import isBetween from 'dayjs/plugin/isBetween';
 import request from '@/helpers/request';
 import MEmpty from '@/components/m-empty';
 dayjs.extend(isBetween);
 import recordItemBg from './images/record-item-bg.png';
+import TheTimeRange from '@/components/the-time-range';
+import iconArrowDown from './images/icon-arrow.png';
+import iconArrowDownActive from './images/icon-arrow-active.png';
 
 const vipGiftPeriodType = {
   DAY: '天',
@@ -37,8 +40,8 @@ export default defineComponent({
       rows: 20,
       status: 'ACTIVATED',
       code: null as any,
-      activateStartTime: dayjs().subtract(1, 'months').format('YYYY-MM-DD'),
-      activateEndTime: dayjs().format('YYYY-MM-DD')
+      activateStartTime: null as any,
+      activateEndTime: null as any
     });
 
     const getList = async () => {
@@ -109,10 +112,11 @@ export default defineComponent({
           </MHeader>
           <div class={[styles.activationContainer, styles.recordContainer]}>
             <i class={styles.iconBrid}></i>
-            <div class={styles.section}>
+            <div class={[styles.section, styles.sectionSearch]}>
               <MSearch
-                inputBackground="white"
                 shape="round"
+                inputBackground="white"
+                background="transparent"
                 placeholder="请输入激活码"
                 onSearch={(val: any) => {
                   forms.code = val;
@@ -120,10 +124,29 @@ export default defineComponent({
                   state.list = [];
                   state.finished = false;
                   getList();
+                }}>
+                {{
+                  left: () => (
+                    <div
+                      class={[styles.searchDropDown, state.showPopoverTime && styles.active]}
+                      onClick={() => {
+                        state.showPopoverTime = true;
+                      }}>
+                      <span>筛选</span>
+                      <img
+                        class={styles.iconArrow}
+                        src={
+                          state.showPopoverTime
+                            ? iconArrowDownActive
+                            : iconArrowDown
+                        }
+                      />
+                    </div>
+                  )
                 }}
-              />
+              </MSearch>
 
-              <div class={styles.prodSection}>
+              {/* <div class={styles.prodSection}>
                 <div class={styles.title}>周期选择</div>
                 <div
                   class={[
@@ -136,7 +159,7 @@ export default defineComponent({
                   {dayjs(forms.activateStartTime).format('YYYY-MM-DD')}至
                   {forms.activateEndTime}
                 </div>
-              </div>
+              </div> */}
             </div>
           </div>
         </MSticky>
@@ -152,10 +175,13 @@ export default defineComponent({
               state.list.map((item: any) => (
                 <div class={styles.sectionItem}>
                   <div class={styles.itemTop}>
-                    <div class={styles.itemTitle}>  
+                    <div class={styles.itemTitle}>
                       <img src={recordItemBg} class={styles.recordItemBg} />
                       <div class={styles.text}>
-                        乐器AI学练工具{item.times >= 99 && item.type ==='YEAR' ? '永久' : item.times + vipGiftPeriodType[item.type] }
+                        乐器AI学练工具
+                        {item.times >= 99 && item.type === 'YEAR'
+                          ? '永久'
+                          : item.times + vipGiftPeriodType[item.type]}
                       </div>
                     </div>
                     <div class={styles.itemCode}>{item.code}</div>
@@ -170,12 +196,12 @@ export default defineComponent({
 
           {!state.loading && state.list.length === 0 && (
             <div style={{ height: '100%' }}>
-              <MEmpty description="暂无激活记录~" />
+              <MEmpty description="暂无内容" />
             </div>
           )}
         </div>
 
-        <Calendar
+        {/* <Calendar
           v-model:show={state.showPopoverTime}
           firstDayOfWeek={1}
           safeAreaInsetBottom
@@ -199,6 +225,19 @@ export default defineComponent({
             state.finished = false;
             getList();
           }}
+        /> */}
+
+        <TheTimeRange
+          v-model:show={state.showPopoverTime}
+          onConfirm={(val: any) => {
+            forms.activateStartTime = val.startTime;
+            forms.activateEndTime = val.endTime;
+
+            forms.page = 1;
+            state.list = [];
+            state.finished = false;
+            getList();
+          }}
         />
       </div>
     );

+ 3 - 1
src/views/collection-record/api.ts

@@ -28,7 +28,9 @@ export interface IApiUserPaymentOrderPage {
   /** 当前页 */
   page: number;
   /** 订单年份, 类似: 2023-06 */
-  payMonth: string;
+  // payMonth: string;
+  startTime: string;
+  endTime: string;
 }
 /** 获取开通列表 */
 export const api_userPaymentOrderPage = (

+ 86 - 25
src/views/collection-record/component/list.module.less

@@ -1,31 +1,92 @@
-.downBtn {
-  margin-left: 13px;
-  margin-bottom: 12px;
-  padding: 3px 12px;
-  font-size: 14px;
-  line-height: 20px;
-  color: #333;
-  font-weight: 400;
-  min-width: 103px;
-  border: none;
-
-  :global {
-    .van-button__text {
-      display: flex;
-      align-items: center;
+// .downBtn {
+//   margin-left: 13px;
+//   margin-bottom: 12px;
+//   padding: 3px 12px;
+//   font-size: 14px;
+//   line-height: 20px;
+//   color: #333;
+//   font-weight: 400;
+//   min-width: 103px;
+//   border: none;
+
+//   :global {
+//     .van-button__text {
+//       display: flex;
+//       align-items: center;
+//     }
+//   }
+
+//   .downIcon {
+//     width: 9px;
+//     height: 5px;
+//     margin-left: 4px;
+//     transition: transform 0.2s ease-in;
+
+//     &.upIcon {
+//       transition: transform 0.2s ease-in;
+//       transform: rotate(180deg);
+//       color: #1cacf1;
+//     }
+//   }
+// }
+
+.prodSection {
+  // padding-top: 16px;
+  padding: 0 13px 15px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+
+  .title {
+    font-weight: 500;
+    font-size: 16px;
+    color: #131415;
+    line-height: 22px;
+    display: flex;
+    align-items: center;
+
+    &::before {
+      display: inline-block;
+      content: '';
+      margin-right: 5px;
+      width: 4px;
+      height: 11px;
+      background: linear-gradient(180deg, #259cfe 0%, #44c9ff 100%);
+      border-radius: 3px;
     }
   }
 
-  .downIcon {
-    width: 9px;
-    height: 5px;
-    margin-left: 4px;
-    transition: transform 0.2s ease-in;
+  .times {
+    display: flex;
+    align-items: center;
+
+    font-size: 14px;
+    color: #191919;
+    line-height: 20px;
+
+    .iconArrow {
+      width: 9px;
+      height: 5px;
+      margin-left: 4px;
+    }
+
+    // &::after {
+    //   content: '';
+    //   margin-left: 4px;
+    //   display: inline-block;
+    //   width: 9px;
+    //   height: 5px;
+    //   background: url('./images/icon-arrow.png') no-repeat center;
+    //   background-size: contain;
+    // }
+
+    &.active {
+      color: #1cacf1;
 
-    &.upIcon {
-      transition: transform 0.2s ease-in;
-      transform: rotate(180deg);
-      color: #1CACF1;
+      // &::after {
+      //   background: url('./images/icon-arrow-active.png') no-repeat center;
+      //   background-size: contain;
+      // }
     }
   }
-}
+}

+ 62 - 60
src/views/collection-record/component/list.tsx

@@ -1,5 +1,6 @@
 import {
   PropType,
+  Teleport,
   computed,
   defineComponent,
   onMounted,
@@ -17,10 +18,13 @@ import MFullRefresh from '@/components/m-full-refresh';
 import { Button, DatePicker, List, Popup, showToast } from 'vant';
 import MEmpty from '@/components/m-empty';
 import ItemSkelete from './item-skelete';
-import icon_arrow from '../image/icon_arrow.svg';
+// import icon_arrow from '../image/icon_arrow.svg';
 import styles from './list.module.less';
-import { usePageVisibility } from '@vant/use';
+// import { usePageVisibility } from '@vant/use';
+import iconArrowDown from '../image/icon-arrow.png';
+import iconArrowDownActive from '../image/icon-arrow-active.png';
 import { listenerMessage } from '@/helpers/native-message';
+import TheTimeRange from '@/components/the-time-range';
 
 export default defineComponent({
   name: 'collection-record-list',
@@ -39,10 +43,12 @@ export default defineComponent({
     const time = new Date();
     const data = reactive({
       page: 1,
-      dateTime: [
-        time.getFullYear().toString(),
-        (time.getMonth() + 1).toString().padStart(2, '0')
-      ],
+      // dateTime: [
+      //   time.getFullYear().toString(),
+      //   (time.getMonth() + 1).toString().padStart(2, '0')
+      // ],
+      startTime: null as any,
+      endTime: null as any,
       minDate: new Date(time.getFullYear(), 0, 1),
       maxDate: new Date(time.getFullYear() + 10, 5, 1),
 
@@ -81,11 +87,17 @@ export default defineComponent({
       data.loading = true;
       let res = {} as any;
       if (props.orderStatus === 'REFUNDED') {
-        res = await api_userPaymentOrderStudentRefundOrderPage({
+        const params = {
           page: data.page,
           rows: 10,
-          refundMonth: data.dateTime.join('-')
-        });
+          startTime: null as any,
+          endTime: null as any
+        };
+        if (data.startTime && data.endTime) {
+          params.startTime = data.startTime + ' 00:00:00';
+          params.endTime = data.endTime + ' 23:59:59';
+        }
+        res = await api_userPaymentOrderStudentRefundOrderPage(params);
         if (res?.code === 200 && Array.isArray(res?.data?.rows)) {
           data.list = res.data.rows.map((item: any) => {
             return {
@@ -102,12 +114,18 @@ export default defineComponent({
         }
       } else {
         try {
-          res = await api_userPaymentOrderPage({
+          const params = {
             page: data.page,
             rows: 10,
             orderStatus: props.orderStatus as any,
-            payMonth: data.dateTime.join('-')
-          });
+            startTime: null as any,
+            endTime: null as any
+          };
+          if (data.startTime && data.endTime) {
+            params.startTime = data.startTime + ' 00:00:00';
+            params.endTime = data.endTime + ' 23:59:59';
+          }
+          res = await api_userPaymentOrderPage(params);
         } catch (error) {}
 
         if (res?.code === 200 && Array.isArray(res?.data?.rows)) {
@@ -147,9 +165,9 @@ export default defineComponent({
       getList();
     };
 
-    const timeName = computed(() => {
-      return `${data.dateTime[0]}年${data.dateTime[1]}月`;
-    });
+    // const timeName = computed(() => {
+    //   return `${data.dateTime[0]}年${data.dateTime[1]}月`;
+    // });
     return () => (
       <>
         <MFullRefresh
@@ -159,49 +177,20 @@ export default defineComponent({
             console.log('下拉');
             handlStart();
           }}>
-          <Button
-            class={styles.downBtn}
-            size="small"
-            round
-            onClick={() => (data.dataOpen = true)}>
-            <div>{timeName.value}</div>
-            {/* <img
-              class={[styles.downIcon, data.dataOpen ? styles.upIcon : '']}
-              src={icon_arrow}
-            /> */}
-
-            <svg
-              class={[styles.downIcon, data.dataOpen ? styles.upIcon : '']}
-              width="9px"
-              height="5px"
-              viewBox="0 0 9 5"
-              version="1.1"
-              xmlns="http://www.w3.org/2000/svg">
-              <title>三角形</title>
-              <g
-                id="页面-1"
-                stroke="currentColor"
-                stroke-width="1"
-                fill="currentColor"
-                fill-rule="evenodd">
-                <g
-                  id="开通记录-待开通"
-                  transform="translate(-99.000000, -155.000000)"
-                  fill="currentColor">
-                  <g id="编组-2" transform="translate(13.000000, 144.000000)">
-                    <g
-                      id="筛选目录备份"
-                      transform="translate(12.000000, 3.000000)">
-                      <path
-                        d="M78.8716471,8.41294119 L82.2489659,12.1655176 C82.4336954,12.3707726 82.4170562,12.6869176 82.2118012,12.8716471 C82.1199888,12.9542782 82.0008397,13 81.8773188,13 L75.1226812,13 C74.8465388,13 74.6226812,12.7761424 74.6226812,12.5 C74.6226812,12.3764791 74.668403,12.25733 74.7510341,12.1655176 L78.1283529,8.41294119 C78.3130824,8.20768618 78.6292274,8.19104698 78.8344824,8.37577649 C78.8475136,8.38750459 78.859919,8.39990996 78.8716471,8.41294119 Z"
-                        id="三角形"
-                        transform="translate(78.500000, 10.500000) rotate(-180.000000) translate(-78.500000, -10.500000) "></path>
-                    </g>
-                  </g>
-                </g>
-              </g>
-            </svg>
-          </Button>
+          <div class={styles.prodSection}>
+            <div class={styles.title}>选择时间</div>
+            <div
+              class={[styles.times, data.dataOpen ? styles.active : '']}
+              onClick={() => {
+                data.dataOpen = true;
+              }}>
+              <span>筛选</span>
+              <img
+                class={styles.iconArrow}
+                src={data.dataOpen ? iconArrowDownActive : iconArrowDown}
+              />
+            </div>
+          </div>
           {data.skelete && new Array(5).fill(1).map(i => <ItemSkelete />)}
 
           {data.list.length !== 0 && (
@@ -225,7 +214,7 @@ export default defineComponent({
             <MEmpty description="暂无记录" />
           )}
         </MFullRefresh>
-        <Popup
+        {/* <Popup
           teleport="body"
           v-model:show={data.dataOpen}
           position="bottom"
@@ -242,7 +231,20 @@ export default defineComponent({
             }}
             onCancel={() => (data.dataOpen = false)}
           />
-        </Popup>
+        </Popup> */}
+        <Teleport to={'body'}>
+          <TheTimeRange
+            v-model:show={data.dataOpen}
+            onConfirm={(val: any) => {
+              console.log(val, 'val');
+
+              data.dataOpen = false;
+              data.startTime = val.startTime;
+              data.endTime = val.endTime;
+              handlStart();
+            }}
+          />
+        </Teleport>
       </>
     );
   }

BIN
src/views/collection-record/image/icon-arrow-active.png


BIN
src/views/collection-record/image/icon-arrow.png


+ 0 - 4
src/views/courseware-list/index.tsx

@@ -352,10 +352,6 @@ export default defineComponent({
                   })}
                 </List>
               ) : (
-                // <OEmpty
-                //   description="暂无学练统计"
-                //   style={{ height: `calc(100vh - ${topWrapHeight.value}px)` }}
-                // />
                 <MEmpty image="list" description="暂无内容" />
               )}
             </div>

+ 1 - 4
src/views/exercise-record/exercis-detail.tsx

@@ -168,7 +168,6 @@ export default defineComponent({
                   {infoDetail.value.practiceDays
                     ? infoDetail.value.practiceDays
                     : 0}
-                  {/* <span>天</span> */}
                 </p>
                 <p class={styles.infoDaysub}>
                   <img src={iconDays} />
@@ -180,8 +179,6 @@ export default defineComponent({
                   {infoDetail.value.practiceTimes
                     ? Math.floor(infoDetail.value.practiceTimes / 60)
                     : 0}
-
-                  {/* <span>分钟</span> */}
                 </p>
                 <p class={styles.infoDaysub}>
                   <img src={iconClock} />
@@ -221,7 +218,7 @@ export default defineComponent({
             </OFullRefresh>
           ) : (
             <OEmpty
-              description="暂无学练统计"
+              description="暂无内容"
               style={{ height: `calc(100vh - ${topWrapHeight.value}px)` }}
             />
           )}

BIN
src/views/exercise-record/images/bg.png


BIN
src/views/exercise-record/images/icon-1.png


BIN
src/views/exercise-record/images/icon-2.png


BIN
src/views/exercise-record/images/icon-album.png


BIN
src/views/exercise-record/images/icon-arrow-active.png


BIN
src/views/exercise-record/images/icon-arrow.png


BIN
src/views/exercise-record/images/icon-bird.png


BIN
src/views/exercise-record/images/icon-number.png


BIN
src/views/exercise-record/images/icon-p-p.png


BIN
src/views/exercise-record/images/l-c.png


BIN
src/views/exercise-record/images/l-l.png


BIN
src/views/exercise-record/images/l-r.png


BIN
src/views/exercise-record/images/p-c.png


BIN
src/views/exercise-record/images/p-l.png


BIN
src/views/exercise-record/images/p-r.png


+ 324 - 0
src/views/exercise-record/index.module.less

@@ -0,0 +1,324 @@
+.exerciseContainer {
+  min-height: 100vh;
+  background: url('./images/bg.png') no-repeat top center #d1ecf8;
+  background-size: contain;
+
+  :global {
+    .van-sticky--fixed {
+      background: url('./images/bg.png') no-repeat top center;
+      background-size: 100%;
+      position: relative;
+    }
+  }
+}
+
+.hideTabsHeader {
+  :global {
+    .van-tabs__wrap {
+      display: none;
+    }
+  }
+}
+
+.woringHeader {
+  display: flex;
+  align-items: center;
+  height: var(--van-nav-bar-height);
+
+  .leftArrow {
+    padding: 0 var(--k-padding-md);
+    margin-right: 0;
+    color: #fff;
+  }
+
+  .tabSection {
+    // padding: 0 32px;
+    margin-right: calc((var(--k-padding-md) * 2));
+    flex: 1;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    --van-tab-font-size: 16px;
+    --van-tabs-line-height: 28px;
+    --van-tab-text-color: #fff;
+    --van-tab-active-text-color: #fff;
+    --van-tabs-nav-background: transparent;
+    --van-padding-xs: 0;
+
+    .iconArrow {
+      width: 9px;
+      height: 5px;
+      position: absolute;
+      right: -13px;
+      margin-left: 4px;
+    }
+    .iconArrowActive {
+      transform: rotate(180deg);
+    }
+
+    :global {
+      .van-tabs__line {
+        display: inline-block;
+        bottom: 11px;
+        width: 20px;
+        height: 3px;
+        background-color: #fff;
+        border-radius: 3px;
+      }
+      .van-tabs__wrap {
+        overflow: inherit;
+      }
+      .van-tab {
+        font-size: 16px;
+      }
+      .van-tab__text {
+        line-height: normal;
+        font-weight: 600;
+      }
+
+      .van-tab--shrink {
+        padding: 0 32px;
+        z-index: 9;
+      }
+    }
+  }
+}
+
+.EVALUATION_CARD {
+  .scBox {
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+    display: flex;
+    align-items: center;
+
+    .l1 {
+      height: 125px;
+      object-fit: contain;
+    }
+    .l2 {
+      height: 125px;
+      flex: 1;
+    }
+    .l3 {
+      height: 125px;
+      object-fit: contain;
+    }
+  }
+}
+.cardSection {
+  position: relative;
+  margin: 24px 13px 12px;
+  padding: 47px 12px 12px;
+  height: 125px;
+
+  .iconBird {
+    position: absolute;
+    z-index: 2;
+    top: -12px;
+    left: 27px;
+    width: 104px;
+    height: 70px;
+  }
+
+  .iconBirdPP {
+    position: absolute;
+    z-index: 2;
+    top: -12px;
+    right: 27px;
+    width: 104px;
+    height: 59px;
+  }
+
+  .sCountSection {
+    position: relative;
+    z-index: 1;
+    display: flex;
+    align-items: center;
+    height: 100%;
+    padding: 0 12px;
+    background: #f8f8f8;
+    border-radius: 10px;
+
+    .iconNumber {
+      width: 18px;
+      height: 18px;
+      margin-right: 3px;
+    }
+
+    .item {
+      display: flex;
+      align-items: center;
+
+      .label {
+        font-size: 13px;
+        color: #333333;
+        line-height: 18px;
+      }
+      .value {
+        display: flex;
+        align-items: center;
+        font-weight: bold;
+        font-size: 20px;
+        color: #333333;
+        font-family: 'DIN';
+        padding-left: 4px;
+
+        i {
+          font-style: normal;
+          font-size: 13px;
+          color: #777777;
+          font-weight: 400;
+        }
+      }
+      .iconArrow {
+        margin-left: 6px;
+        width: 9px;
+        height: 5px;
+        transform: rotate(-90deg);
+      }
+    }
+
+    .line {
+      width: 1px;
+      height: 14px;
+      background-color: #b9cfde;
+      margin: 0 10px;
+    }
+  }
+}
+
+.searchGroup {
+  padding: 0 13px;
+
+  .sectionSearch {
+    border-radius: 16px 16px 0px 0px;
+    padding: 12px;
+    background: rgba(255, 255, 255, 0.73);
+    border: 1px solid #ffffff;
+    border-bottom: none;
+    position: relative;
+    z-index: 1;
+    :global {
+      .van-search {
+        padding: 0;
+      }
+    }
+    .searchDropDown {
+      display: flex;
+      align-items: center;
+      font-size: 14px;
+      color: #191919;
+      line-height: 20px;
+      padding-right: 12px;
+    }
+    .active {
+      color: #1cacf1;
+    }
+    span {
+      padding-right: 4px;
+    }
+    .iconArrow {
+      width: 9px;
+      height: 5px;
+    }
+  }
+
+  .practiceName {
+    display: flex;
+    align-items: center;
+    font-size: 14px;
+    color: #191919;
+    line-height: 20px;
+
+    .iconArrow {
+      margin-left: 4px;
+    }
+
+    &.active {
+      color: #1cacf1;
+    }
+  }
+}
+
+.listSection {
+  margin: 0 13px;
+  border-radius: 0px;
+  padding: 0 12px;
+  background: rgba(255, 255, 255, 0.73);
+  border: 1px solid #ffffff;
+  border-top: 0;
+  border-bottom: 0;
+  overflow-x: hidden;
+  overflow-y: auto;
+}
+
+.listParent {
+  margin: 0 13px;
+  overflow-x: hidden;
+  overflow-y: auto;
+
+}
+
+.listChild {
+  padding: 0 12px;
+  background: rgba(255, 255, 255, 0.73);
+  border: 1px solid #ffffff;
+  border-top: 0;
+  border-radius: 0px 0px 16px 16px;
+}
+
+.practiceItem {
+  display: flex;
+  align-items: center;
+  padding-bottom: 20px;
+  padding-right: 70px;
+
+  .time {
+    font-weight: bold;
+    font-size: 14px;
+    line-height: 18px;
+    color: #777777;
+    font-family: 'DIN';
+    width: 42px;
+  }
+
+  .lineBox {
+    display: flex;
+    align-items: center;
+    flex: 1;
+
+    .boxSection {
+      flex: 1;
+      position: relative;
+    }
+
+    .box {
+      background: linear-gradient(270deg, #28a0fe 0%, #98dbff 100%);
+      border-radius: 2px;
+      border: 1px solid #ffffff;
+      height: 20px;
+      // width: 100%;
+    }
+    .long {
+      position: absolute;
+      top: 0;
+      padding-left: 8px;
+      font-weight: 400;
+      font-size: 14px;
+      color: #777777;
+      flex-shrink: 0;
+      width: 76px;
+      box-sizing: content-box;
+      span {
+        padding-right: 2px;
+        font-weight: bold;
+        font-size: 18px;
+        color: #259cfe;
+        font-family: 'DIN';
+      }
+    }
+  }
+}

+ 468 - 0
src/views/exercise-record/index.tsx

@@ -0,0 +1,468 @@
+import { defineComponent, onMounted, onUnmounted, reactive, ref } from 'vue';
+import styles from './index.module.less';
+import MSticky from '@/components/m-sticky';
+import MHeader from '@/components/m-header';
+import { browser, formatterDatePicker } from '@/helpers/utils';
+import { useRouter } from 'vue-router';
+import { DatePicker, List, Popup, Tab, Tabs } from 'vant';
+import { listenerMessage } from '@/helpers/native-message';
+import OFullRefresh from '@/components/m-full-refresh';
+import iconBird from './images/icon-bird.png';
+import iconPP from './images/icon-p-p.png';
+import iconNumber from './images/icon-number.png';
+import iconAlbum from './images/icon-album.png';
+import icon1 from './images/icon-1.png';
+import icon2 from './images/icon-2.png';
+import iconArrow from './images/icon-arrow.png';
+import iconArrowActive from './images/icon-arrow-active.png';
+import iconLl from './images/l-l.png';
+import iconLc from './images/l-c.png';
+import iconLr from './images/l-r.png';
+import iconPl from './images/p-l.png';
+import iconPc from './images/p-c.png';
+import iconPr from './images/p-r.png';
+import MSearch from '@/components/m-search';
+import TheTimeRange from '@/components/the-time-range';
+import request from '@/helpers/request';
+import DetailItem from './modals/detail-item';
+import MEmpty from '@/components/m-empty';
+import dayjs from 'dayjs';
+
+export default defineComponent({
+  name: 'exercise-record-index',
+  setup() {
+    const router = useRouter();
+    const tabsRef = ref();
+    const state = reactive({
+      tabActive: 'EVALUATION' as 'EVALUATION' | 'PRACTICE',
+      isClick: false,
+      showPopoverTime: false
+    });
+    // <Tab name="EVALUATION" title="评测记录"></Tab>
+    // <Tab name="PRACTICE" title="练习记录"></Tab>
+    const formEvaluation = reactive({
+      startTime: '',
+      endTime: '',
+      musicSheetName: null,
+      page: 1,
+      rows: 20
+    });
+
+    const fromPractice = reactive({
+      showData: true,
+      showTime: false,
+      currentDate: [dayjs().format('YYYY'), dayjs().format('MM')],
+      practiceMonthName:
+        dayjs().format('YYYY') + '年' + dayjs().format('MM') + '月',
+      practiceDetail: {} as any,
+      practiceList: [] as any
+    });
+    const refreshing = ref(false);
+    const loading = ref(false);
+    const finished = ref(false);
+    const showContact = ref(true);
+    const topWrapHeight = ref(0);
+    const infoDetail = ref({
+      evaluationNum: 0,
+      userMusicNum: 0
+    });
+    const onRefresh = () => {
+      finished.value = false;
+      // 重新加载数据
+      // 将 loading 设置为 true,表示处于加载状态
+      loading.value = true;
+      getList();
+    };
+    const list = ref([]);
+    const getList = async () => {
+      if (state.isClick) {
+        return;
+      }
+      state.isClick = true;
+      if (refreshing.value) {
+        list.value = [];
+        formEvaluation.page = 1;
+        refreshing.value = false;
+      }
+      try {
+        const res = await request.post(`/edu-app/musicPracticeRecord/page`, {
+          data: { ...formEvaluation, feature: 'EVALUATION' }
+        });
+
+        if (list.value.length > 0 && res.data.current === 1) {
+          return;
+        }
+
+        list.value = list.value.concat(res.data.rows || []);
+        formEvaluation.page = res.data.current + 1;
+        showContact.value = list.value.length > 0;
+        loading.value = false;
+        finished.value = res.data.current >= res.data.pages;
+      } catch {
+        showContact.value = false;
+        finished.value = true;
+      }
+      state.isClick = false;
+    };
+
+    const getDetail = async () => {
+      try {
+        const { data } = await request.post(
+          `/edu-app/musicPracticeRecord/studentStat`,
+          {
+            data: {
+              startTime: formEvaluation.startTime,
+              endTime: formEvaluation.endTime,
+              musicSheetName: formEvaluation.musicSheetName
+            }
+          }
+        );
+        infoDetail.value = { ...data };
+      } catch (e: any) {}
+    };
+
+    const onEvaluation = () => {
+      getDetail();
+      getList();
+    };
+
+    const tabResize = () => {
+      tabsRef.value?.resize();
+    };
+    //
+    const getPractice = async () => {
+      try {
+        const currentDate = fromPractice.currentDate.join('-');
+        const { data } = await request.post(
+          `/edu-app/musicPracticeRecord/studentTrainStat`,
+          {
+            data: {
+              startTime: currentDate + '-01 00:00:00',
+              endTime:
+                dayjs(currentDate).endOf('month').format('YYYY-MM-DD') +
+                ' 23:59:59'
+            }
+          }
+        );
+
+        const { studentTrainStatList, ...more } = data;
+        fromPractice.showData = studentTrainStatList?.length > 0;
+        fromPractice.practiceDetail = { ...more };
+
+        const tempList: any = [];
+        let maxTime = 0;
+        studentTrainStatList?.forEach((item: any) => {
+          if (item.practiceTimes > maxTime) {
+            maxTime = item.practiceTimes;
+          }
+        });
+        studentTrainStatList?.forEach((item: any) => {
+          tempList.push({
+            date: dayjs(item.practiceDate).format('MM/DD'),
+            time: parseFloat((item.practiceTimes / 60).toFixed(2)),
+            rate: Math.floor((item.practiceTimes / maxTime) * 100)
+          });
+        });
+
+        fromPractice.practiceList = tempList || [];
+      } catch (e: any) {}
+    };
+
+    // 我的作品
+    const gotoMyWork = (pageTag = 'my_work') => {
+      postMessage({
+        api: 'open_app_page',
+        content: {
+          action: 'app',
+          pageTag: pageTag,
+          url: ''
+        }
+      });
+    };
+
+    onMounted(() => {
+      window.addEventListener('resize', tabResize);
+      listenerMessage('webViewOnResume', () => {
+        tabResize();
+      });
+      onEvaluation();
+
+      getPractice();
+    });
+
+    onUnmounted(() => {
+      window.removeEventListener('resize', tabResize);
+    });
+    return () => (
+      <div class={styles.exerciseContainer}>
+        <MSticky
+          position="top"
+          onBarHeight={(height: number) => {
+            topWrapHeight.value = height;
+          }}>
+          <MHeader border={false} background={'transparent'}>
+            {{
+              content: () => (
+                <div class={styles.woringHeader}>
+                  <i
+                    onClick={() => {
+                      if (browser().isApp) {
+                        postMessage({
+                          api: 'back'
+                        });
+                      } else {
+                        router.back();
+                      }
+                    }}
+                    class={[
+                      'van-badge__wrapper van-icon van-icon-arrow-left van-nav-bar__arrow',
+                      styles.leftArrow
+                    ]}></i>
+                  <Tabs
+                    ref={tabsRef}
+                    class={styles.tabSection}
+                    v-model:active={state.tabActive}
+                    shrink>
+                    <Tab name="EVALUATION" title="评测记录"></Tab>
+                    <Tab name="PRACTICE" title="练习记录"></Tab>
+                  </Tabs>
+                </div>
+              )
+            }}
+          </MHeader>
+        </MSticky>
+
+        <Tabs v-model:active={state.tabActive} class={styles.hideTabsHeader}>
+          <Tab name="EVALUATION" title="评测记录">
+            <div
+              style={{
+                overflow: 'hidden',
+                height: `calc(100vh - ${topWrapHeight.value}px)`,
+                display: 'flex',
+                flexDirection: 'column'
+              }}>
+              <div class={[styles.cardSection, styles.EVALUATION_CARD]}>
+                <img src={iconBird} class={styles.iconBird} />
+
+                <div class={styles.scBox}>
+                  <img src={iconLl} class={styles.l1} />
+                  <img src={iconLc} class={styles.l2} />
+                  <img src={iconLr} class={styles.l3} />
+                </div>
+
+                <div class={styles.sCountSection}>
+                  <div class={styles.item}>
+                    <img src={iconNumber} class={styles.iconNumber} />
+                    <span class={styles.label}>评测次数</span>
+                    <span class={styles.value}>
+                      {infoDetail.value.evaluationNum}
+                      <i>次</i>
+                    </span>
+                  </div>
+                  <span class={styles.line}></span>
+                  <div class={styles.item} onClick={() => gotoMyWork()}>
+                    <img src={iconAlbum} class={styles.iconNumber} />
+                    <span class={styles.label}>作品数量</span>
+                    <span class={styles.value}>
+                      {infoDetail.value.userMusicNum}
+                      <i>首</i>
+                    </span>
+                    <img src={iconArrow} class={styles.iconArrow} />
+                  </div>
+                </div>
+              </div>
+              <div class={styles.searchGroup}>
+                <div class={[styles.section, styles.sectionSearch]}>
+                  <MSearch
+                    shape="round"
+                    inputBackground="white"
+                    background="transparent"
+                    placeholder="请输入曲目名称"
+                    onSearch={(val: any) => {
+                      formEvaluation.musicSheetName = val;
+                      refreshing.value = true;
+                      loading.value = true;
+                      onEvaluation();
+                    }}>
+                    {{
+                      left: () => (
+                        <div
+                          class={[
+                            styles.searchDropDown,
+                            state.showPopoverTime && styles.active
+                          ]}
+                          onClick={() => {
+                            state.showPopoverTime = true;
+                          }}>
+                          <span>筛选</span>
+                          <img
+                            class={styles.iconArrow}
+                            src={
+                              state.showPopoverTime
+                                ? iconArrowActive
+                                : iconArrow
+                            }
+                          />
+                        </div>
+                      )
+                    }}
+                  </MSearch>
+                </div>
+              </div>
+              <div class={styles.listSection} style={{ flex: '1' }}>
+                {showContact.value ? (
+                  <OFullRefresh
+                    v-model:modelValue={refreshing.value}
+                    onRefresh={onRefresh}
+                    style={
+                      {
+                        // minHeight: `calc(100vh - ${topWrapHeight.value}px)`
+                      }
+                    }>
+                    <List
+                      loading-text=" "
+                      loading={loading.value}
+                      finished={finished.value}
+                      finished-text=" "
+                      onLoad={getList}>
+                      {list.value.map((item: any) => (
+                        <DetailItem item={item} />
+                      ))}
+                    </List>
+                  </OFullRefresh>
+                ) : (
+                  <MEmpty description="暂无内容" />
+                )}
+              </div>
+            </div>
+          </Tab>
+          <Tab name="PRACTICE" title="练习记录">
+            <div
+              style={{
+                overflow: 'hidden',
+                height: `calc(100vh - ${topWrapHeight.value}px)`,
+                display: 'flex',
+                flexDirection: 'column'
+              }}>
+              <div class={[styles.cardSection, styles.EVALUATION_CARD]}>
+                <img src={iconPP} class={styles.iconBirdPP} />
+
+                <div class={styles.scBox}>
+                  <img src={iconPl} class={styles.l1} />
+                  <img src={iconPc} class={styles.l2} />
+                  <img src={iconPr} class={styles.l3} />
+                </div>
+
+                <div class={styles.sCountSection}>
+                  <div class={styles.item}>
+                    <img src={icon1} class={styles.iconNumber} />
+                    <span class={styles.label}>练习天数</span>
+                    <span class={styles.value}>
+                      {fromPractice.practiceDetail.practiceDays || 0}
+                      <i>天</i>
+                    </span>
+                  </div>
+                  <span class={styles.line}></span>
+                  <div class={styles.item} onClick={() => gotoMyWork()}>
+                    <img src={icon2} class={styles.iconNumber} />
+                    <span class={styles.label}>练习时长</span>
+                    <span class={styles.value}>
+                      {fromPractice.practiceDetail.practiceTimes
+                        ? Math.floor(
+                            fromPractice.practiceDetail.practiceTimes / 60
+                          )
+                        : 0}
+                      <i>分钟</i>
+                    </span>
+                  </div>
+                </div>
+              </div>
+              <div class={styles.searchGroup}>
+                <div class={[styles.section, styles.sectionSearch]}>
+                  <div
+                    class={[
+                      styles.practiceName,
+                      fromPractice.showTime && styles.active
+                    ]}
+                    onClick={() => {
+                      fromPractice.showTime = true;
+                    }}>
+                    {fromPractice.practiceMonthName}
+                    <img
+                      class={styles.iconArrow}
+                      src={fromPractice.showTime ? iconArrowActive : iconArrow}
+                    />
+                  </div>
+                </div>
+              </div>
+
+              <div class={styles.listParent} style={{ flex: '1' }}>
+                <div class={styles.listChild}>
+                  {fromPractice.showData ? (
+                    <div class={styles.practiceList}>
+                      {fromPractice.practiceList?.map((item: any) => (
+                        <div class={styles.practiceItem}>
+                          <span class={styles.time}>{item.date}</span>
+                          <div class={styles.lineBox}>
+                            <div class={styles.boxSection}>
+                              <div
+                                class={styles.box}
+                                style={{ width: item.rate + '%' }}></div>
+                              <p
+                                class={styles.long}
+                                style={{ left: item.rate + '%' }}>
+                                <span>{item.time}</span>
+                                分钟
+                              </p>
+                            </div>
+                          </div>
+                        </div>
+                      ))}
+                    </div>
+                  ) : (
+                    <MEmpty description="暂无内容" />
+                  )}
+                </div>
+              </div>
+            </div>
+          </Tab>
+        </Tabs>
+
+        <TheTimeRange
+          v-model:show={state.showPopoverTime}
+          onConfirm={(val: any) => {
+            formEvaluation.startTime = val.startTime
+              ? val.startTime + ' 00:00:00'
+              : '';
+            formEvaluation.endTime = val.endTime
+              ? val.endTime + ' 23:59:59'
+              : '';
+            state.showPopoverTime = false;
+            refreshing.value = true;
+            loading.value = true;
+            onEvaluation();
+          }}
+        />
+
+        <Popup
+          v-model:show={fromPractice.showTime}
+          position="bottom"
+          round
+          class={'popupBottomSearch'}>
+          <DatePicker
+            onCancel={() => {
+              fromPractice.showTime = false;
+            }}
+            onConfirm={(val: any) => {
+              fromPractice.showTime = false;
+              getPractice();
+            }}
+            v-model={fromPractice.currentDate}
+            formatter={formatterDatePicker}
+            columnsType={['year', 'month']}
+          />
+        </Popup>
+      </div>
+    );
+  }
+});

+ 22 - 9
src/views/exercise-record/modals/detail-item.module.less

@@ -1,8 +1,8 @@
 .itemWrap {
   background: #ffffff;
   border-radius: 10px;
-  padding: 12px 15px 20px;
-  margin: 0 13px 13px;
+  padding: 12px;
+  margin: 0 0 12px;
 
   .itemTop {
     display: flex;
@@ -13,18 +13,31 @@
     padding-bottom: 12px;
 
     .itemTopLeft {
+      .nameSection {
+        display: flex;
+        align-items: center;
+        margin-bottom: 6px;
+      }
       .itemTopMain {
         height: 22px;
         font-size: 16px;
         font-weight: 500;
         color: #333333;
         line-height: 22px;
-        margin-bottom: 6px;
-        max-width: 160px;
+        max-width: 130px;
         overflow: hidden;
         text-overflow: ellipsis;
         white-space: nowrap;
       }
+      .tagWork {
+        font-weight: 500;
+        font-size: 12px;
+        color: #2eaafe;
+        border-radius: 3px;
+        border: 1px solid rgba(46, 170, 254, 0.37);
+        padding: 0 4px;
+        // margin-left: 4px;
+      }
 
       .itemTopSub {
         font-size: 12px !important;
@@ -40,8 +53,8 @@
       align-items: center;
 
       .imgWrap {
-        width: 100px;
-        height: 33px;
+        width: 98px;
+        height: 29px;
         background: #e9e3ff;
         border-radius: 19px;
 
@@ -71,11 +84,11 @@
       width: 25%;
 
       .dotMain {
-        font-size: 26px;
+        font-size: 22px;
         color: #333333;
         line-height: 30px;
         margin-bottom: 4px;
-        font-family: DINAlternate-Bold, DINAlternate;
+        font-family: 'DIN';
         font-weight: bold;
 
         span {
@@ -95,4 +108,4 @@
       }
     }
   }
-}
+}

+ 39 - 28
src/views/exercise-record/modals/detail-item.tsx

@@ -13,27 +13,27 @@ const scoreInfos: any = {
   1: {
     img: Image1,
     tips: '你的演奏不太好,音准和完整性还需加强,再练一练吧~',
-    mome: '敢于尝试'
+    memo: '敢于尝试'
   },
   2: {
     img: Image2,
     tips: '你的演奏还不熟练,音准和完整性还需加强,加紧训练才能有好成绩哦~',
-    mome: '还要加油哦~'
+    memo: '还要加油哦~'
   },
   3: {
     img: Image3,
     tips: '你的演奏还不流畅,音准和节奏还需加强,科学的练习才能更完美哦~',
-    mome: '突破自我'
+    memo: '突破自我'
   },
   4: {
     img: Image4,
     tips: '你的演奏还不错,继续加油吧,加强音准,离完美就差一步啦~',
-    mome: '崭露头角'
+    memo: '崭露头角'
   },
   5: {
     img: Image5,
     tips: '你的演奏非常不错,音准的把握和节奏稍有瑕疵,完整性把握的很好~',
-    mome: '你很棒'
+    memo: '你很棒'
   }
 };
 export default defineComponent({
@@ -41,21 +41,21 @@ export default defineComponent({
   name: 'detail-item',
 
   setup(props) {
-    const getLeveByScoreId = (score?: number) => {
+    const getLevelByScoreId = (score?: number) => {
       if (!score && typeof score !== 'number') {
         return {};
       }
-      let leve: any = 1;
+      let level: any = 1;
       if (score > 20 && score <= 40) {
-        leve = 2;
+        level = 2;
       } else if (score > 40 && score <= 60) {
-        leve = 3;
+        level = 3;
       } else if (score > 60 && score <= 80) {
-        leve = 4;
+        level = 4;
       } else if (score > 80) {
-        leve = 5;
+        level = 5;
       }
-      return leve;
+      return level;
     };
     const gotoDetail = () => {
       const url =
@@ -77,13 +77,16 @@ export default defineComponent({
       <div class={styles.itemWrap} onClick={gotoDetail}>
         <div class={styles.itemTop}>
           <div class={styles.itemTopLeft}>
-            <p class={styles.itemTopMain}>{props.item.musicSheetName}</p>
+            <div class={styles.nameSection}>
+              <p class={styles.itemTopMain}>{props.item.musicSheetName}</p>
+              {props.item.userMusicFlag && <span class={styles.tagWork}>作品</span>}
+            </div>
             <p class={styles.itemTopSub}>{props.item.createTime}</p>
           </div>
           <div class={styles.itemTopRight}>
             <div class={styles.imgWrap}>
               <img
-                src={scoreInfos[getLeveByScoreId(props.item.score || 0)].img}
+                src={scoreInfos[getLevelByScoreId(props.item.score || 0)].img}
                 alt=""
               />
             </div>
@@ -98,13 +101,17 @@ export default defineComponent({
             </p>
             <p class={styles.dotSub}> 综合得分</p>
           </div>
-          {props.item.rhythmFlag ? '' : <div class={styles.itemBottomDot}>
-            <p class={styles.dotMain}>
-              {props.item.intonation || 0}
-              <span>分</span>{' '}
-            </p>
-            <p class={styles.dotSub}>音准 </p>
-          </div>}
+          {props.item.rhythmFlag ? (
+            ''
+          ) : (
+            <div class={styles.itemBottomDot}>
+              <p class={styles.dotMain}>
+                {props.item.intonation || 0}
+                <span>分</span>{' '}
+              </p>
+              <p class={styles.dotSub}>音准 </p>
+            </div>
+          )}
           <div class={styles.itemBottomDot}>
             <p class={styles.dotMain}>
               {props.item.cadence || 0}
@@ -112,13 +119,17 @@ export default defineComponent({
             </p>
             <p class={styles.dotSub}>节奏 </p>
           </div>
-          {props.item.rhythmFlag ? '' : <div class={styles.itemBottomDot}>
-            <p class={styles.dotMain}>
-              {props.item.integrity || 0}
-              <span>分</span>{' '}
-            </p>
-            <p class={styles.dotSub}>完成度 </p>
-          </div>}
+          {props.item.rhythmFlag ? (
+            ''
+          ) : (
+            <div class={styles.itemBottomDot}>
+              <p class={styles.dotMain}>
+                {props.item.integrity || 0}
+                <span>分</span>{' '}
+              </p>
+              <p class={styles.dotSub}>完成度 </p>
+            </div>
+          )}
         </div>
       </div>
     );

+ 1 - 1
src/views/school-register/index.tsx

@@ -43,7 +43,7 @@ export default defineComponent({
       types: [
         { label: '小学', value: 'PRIMARY' },
         { label: '初中', value: 'JUNIOR' },
-        { label: '小初一体', value: 'PRIMARY_JUNIOR' }
+        { label: '九年一贯制', value: 'PRIMARY_JUNIOR' }
       ],
       grades: [
         { label: '六年制', value: 'SIX_YEAR_SYSTEM' },

+ 1 - 1
vite.config.ts

@@ -14,7 +14,7 @@ function resolve(dir: string) {
 // https://vitejs.dev/config/
 // https://github.com/vitejs/vite/issues/1930 .env
 // const proxyUrl = 'https://test.lexiaoya.cn/';
-const proxyUrl = 'https://test.kt.colexiu.com/';
+const proxyUrl = 'https://dev.kt.colexiu.com/';
 // const proxyUrl = 'http://192.168.3.143:7093/';
 // const proxyUrl = 'https://dev.kt.colexiu.com/';
 export default defineConfig({