lex 1 year ago
parent
commit
2604daf0ea
60 changed files with 1945 additions and 188 deletions
  1. 19 16
      package-lock.json
  2. 1 1
      package.json
  3. 3 1
      src/helpers/request.ts
  4. 26 0
      src/router/routes-common.ts
  5. 1 1
      src/store/mutation-types.ts
  6. BIN
      src/views/knowledge-library/images/icon-book.png
  7. BIN
      src/views/knowledge-library/images/icon-button-list.png
  8. BIN
      src/views/knowledge-library/images/icon-exam-question.png
  9. BIN
      src/views/knowledge-library/images/next_btn_bg.png
  10. BIN
      src/views/knowledge-library/images/prev_btn_bg.png
  11. 10 0
      src/views/knowledge-library/index.module.less
  12. 9 1
      src/views/knowledge-library/index.tsx
  13. 11 2
      src/views/knowledge-library/model/answer-analysis/index.module.less
  14. 8 8
      src/views/knowledge-library/model/answer-analysis/index.tsx
  15. 16 17
      src/views/knowledge-library/model/answer-list/index.tsx
  16. 13 4
      src/views/knowledge-library/model/choice-question/index.module.less
  17. 7 10
      src/views/knowledge-library/model/choice-question/index.tsx
  18. 19 6
      src/views/knowledge-library/model/drag-question/index.module.less
  19. 20 11
      src/views/knowledge-library/model/drag-question/index.tsx
  20. 5 2
      src/views/knowledge-library/model/error-mode/index.module.less
  21. 9 10
      src/views/knowledge-library/model/error-mode/index.tsx
  22. 10 4
      src/views/knowledge-library/model/keep-look-question/index.module.less
  23. 8 9
      src/views/knowledge-library/model/keep-look-question/index.tsx
  24. 8 3
      src/views/knowledge-library/model/play-question/index.module.less
  25. 9 10
      src/views/knowledge-library/model/play-question/index.tsx
  26. BIN
      src/views/knowledge-library/model/unit-audio/images/icon-audio.png
  27. BIN
      src/views/knowledge-library/model/unit-audio/images/icon-pause.png
  28. 7 5
      src/views/knowledge-library/model/unit-audio/index.module.less
  29. 149 0
      src/views/knowledge-library/practice-mode/index.module.less
  30. 653 0
      src/views/knowledge-library/practice-mode/index.tsx
  31. 19 22
      src/views/knowledge-library/unit-detail.tsx
  32. BIN
      src/views/knowledge-library/wroing-book/ai-exam/images/icon-book.png
  33. BIN
      src/views/knowledge-library/wroing-book/ai-exam/images/wroing-bg.png
  34. BIN
      src/views/knowledge-library/wroing-book/ai-exam/images/wroing-title.png
  35. 103 0
      src/views/knowledge-library/wroing-book/ai-exam/index.module.less
  36. 86 0
      src/views/knowledge-library/wroing-book/ai-exam/index.tsx
  37. BIN
      src/views/knowledge-library/wroing-book/images/ai-exam.png
  38. BIN
      src/views/knowledge-library/wroing-book/images/woring-practice.png
  39. BIN
      src/views/knowledge-library/wroing-book/images/woring-stat.png
  40. 12 2
      src/views/knowledge-library/wroing-book/index.tsx
  41. 103 0
      src/views/knowledge-library/wroing-book/woring-stat/echarts.ts
  42. BIN
      src/views/knowledge-library/wroing-book/woring-stat/images/icon-1.png
  43. BIN
      src/views/knowledge-library/wroing-book/woring-stat/images/icon-2.png
  44. BIN
      src/views/knowledge-library/wroing-book/woring-stat/images/icon-3.png
  45. BIN
      src/views/knowledge-library/wroing-book/woring-stat/images/icon-4.png
  46. BIN
      src/views/knowledge-library/wroing-book/woring-stat/images/icon-5.png
  47. BIN
      src/views/knowledge-library/wroing-book/woring-stat/images/section-bg1.png
  48. BIN
      src/views/knowledge-library/wroing-book/woring-stat/images/section-bg2.png
  49. BIN
      src/views/knowledge-library/wroing-book/woring-stat/images/section-bg3.png
  50. BIN
      src/views/knowledge-library/wroing-book/woring-stat/images/stat-1.png
  51. BIN
      src/views/knowledge-library/wroing-book/woring-stat/images/stat-2.png
  52. BIN
      src/views/knowledge-library/wroing-book/woring-stat/images/stat-3.png
  53. BIN
      src/views/knowledge-library/wroing-book/woring-stat/images/stat-top.png
  54. BIN
      src/views/knowledge-library/wroing-book/woring-stat/images/wroing-bg.png
  55. BIN
      src/views/knowledge-library/wroing-book/woring-stat/images/wroing-title.png
  56. 242 0
      src/views/knowledge-library/wroing-book/woring-stat/index.module.less
  57. 309 0
      src/views/knowledge-library/wroing-book/woring-stat/index.tsx
  58. 44 37
      src/views/layout/auth.tsx
  59. 1 1
      src/views/layout/code.tsx
  60. 5 5
      src/views/member-center/index.tsx

+ 19 - 16
package-lock.json

@@ -20,7 +20,7 @@
         "html2canvas": "^1.4.1",
         "naive-ui": "^2.34.4",
         "numeral": "^2.0.6",
-        "pinia": "^2.1.4",
+        "pinia": "^2.0.36",
         "plyr": "^3.7.8",
         "qrcode": "^1.5.3",
         "query-string": "^8.1.0",
@@ -34,7 +34,7 @@
         "vue-awesome-swiper": "^5.0.1",
         "vue-router": "^4.1.6",
         "vue3-lottie": "^2.7.0",
-        "vuedraggable": "^2.24.3"
+        "vuedraggable": "^4.1.0"
       },
       "devDependencies": {
         "@babel/core": "^7.21.4",
@@ -7489,9 +7489,9 @@
       }
     },
     "node_modules/sortablejs": {
-      "version": "1.10.2",
-      "resolved": "https://registry.npmmirror.com/sortablejs/-/sortablejs-1.10.2.tgz",
-      "integrity": "sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A=="
+      "version": "1.14.0",
+      "resolved": "https://registry.npmmirror.com/sortablejs/-/sortablejs-1.14.0.tgz",
+      "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w=="
     },
     "node_modules/source-map": {
       "version": "0.6.1",
@@ -8393,11 +8393,14 @@
       }
     },
     "node_modules/vuedraggable": {
-      "version": "2.24.3",
-      "resolved": "https://registry.npmmirror.com/vuedraggable/-/vuedraggable-2.24.3.tgz",
-      "integrity": "sha512-6/HDXi92GzB+Hcs9fC6PAAozK1RLt1ewPTLjK0anTYguXLAeySDmcnqE8IC0xa7shvSzRjQXq3/+dsZ7ETGF3g==",
+      "version": "4.1.0",
+      "resolved": "https://registry.npmmirror.com/vuedraggable/-/vuedraggable-4.1.0.tgz",
+      "integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==",
       "dependencies": {
-        "sortablejs": "1.10.2"
+        "sortablejs": "1.14.0"
+      },
+      "peerDependencies": {
+        "vue": "^3.0.1"
       }
     },
     "node_modules/vueuc": {
@@ -14431,9 +14434,9 @@
       }
     },
     "sortablejs": {
-      "version": "1.10.2",
-      "resolved": "https://registry.npmmirror.com/sortablejs/-/sortablejs-1.10.2.tgz",
-      "integrity": "sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A=="
+      "version": "1.14.0",
+      "resolved": "https://registry.npmmirror.com/sortablejs/-/sortablejs-1.14.0.tgz",
+      "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w=="
     },
     "source-map": {
       "version": "0.6.1",
@@ -15094,11 +15097,11 @@
       }
     },
     "vuedraggable": {
-      "version": "2.24.3",
-      "resolved": "https://registry.npmmirror.com/vuedraggable/-/vuedraggable-2.24.3.tgz",
-      "integrity": "sha512-6/HDXi92GzB+Hcs9fC6PAAozK1RLt1ewPTLjK0anTYguXLAeySDmcnqE8IC0xa7shvSzRjQXq3/+dsZ7ETGF3g==",
+      "version": "4.1.0",
+      "resolved": "https://registry.npmmirror.com/vuedraggable/-/vuedraggable-4.1.0.tgz",
+      "integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==",
       "requires": {
-        "sortablejs": "1.10.2"
+        "sortablejs": "1.14.0"
       }
     },
     "vueuc": {

+ 1 - 1
package.json

@@ -48,7 +48,7 @@
     "vue-awesome-swiper": "^5.0.1",
     "vue-router": "^4.1.6",
     "vue3-lottie": "^2.7.0",
-    "vuedraggable": "^2.24.3"
+    "vuedraggable": "^4.1.0"
   },
   "devDependencies": {
     "@babel/core": "^7.21.4",

+ 3 - 1
src/helpers/request.ts

@@ -91,7 +91,9 @@ request.interceptors.response.use(
       }
       if (!(data.code === 403 || data.code === 5000)) {
         clearTimeout(toast);
-        showToast(msg);
+        setTimeout(() => {
+          showToast(msg);
+        }, 60);
       }
       const browserInfo = browser();
       if (data.code === 5000 || data.code === 403) {

+ 26 - 0
src/router/routes-common.ts

@@ -76,6 +76,32 @@ export default [
         }
       },
       {
+        path: '/wroing-stat',
+        name: 'wroing-stat',
+        component: () =>
+          import('@/views/knowledge-library/wroing-book/woring-stat'),
+        meta: {
+          title: '错题统计'
+        }
+      },
+      {
+        path: '/ai-exam',
+        name: 'ai-exam',
+        component: () =>
+          import('@/views/knowledge-library/wroing-book/ai-exam'),
+        meta: {
+          title: '智能组卷'
+        }
+      },
+      {
+        path: '/practice-mode',
+        name: 'practice-mode',
+        component: () => import('@/views/knowledge-library/practice-mode'),
+        meta: {
+          title: '练习模式'
+        }
+      },
+      {
         path: '/courseware-list',
         name: 'courseware-list',
         component: () => import('@/views/courseware-list/index'),

+ 1 - 1
src/store/mutation-types.ts

@@ -1,4 +1,4 @@
-export const ACCESS_TOKEN = 'ACCESS-TOKEN'; // 用户token
+export const ACCESS_TOKEN = 'ACCESS-TOKEN-STUDENT'; // 用户token
 export const IM_TOKEN = 'IM-TOKEN'; //
 export const CURRENT_USER = 'CURRENT-USER'; // 当前用户信息
 export const TABS_ROUTES = 'TABS-ROUTES'; // 标签页

BIN
src/views/knowledge-library/images/icon-book.png


BIN
src/views/knowledge-library/images/icon-button-list.png


BIN
src/views/knowledge-library/images/icon-exam-question.png


BIN
src/views/knowledge-library/images/next_btn_bg.png


BIN
src/views/knowledge-library/images/prev_btn_bg.png


+ 10 - 0
src/views/knowledge-library/index.module.less

@@ -2,6 +2,7 @@
   min-height: 100vh;
   background: url('./images/bg.png') no-repeat top center;
   background-size: contain;
+  position: relative;
 
   .wroingBtn {
     display: flex;
@@ -21,6 +22,15 @@
       display: inline-block;
     }
   }
+
+  .iconExamQuestion {
+    position: absolute;
+    right: 0;
+    bottom: 20vh;
+    z-index: 99;
+    width: 66px;
+    height: 66px;
+  }
 }
 
 .btnGroup {

+ 9 - 1
src/views/knowledge-library/index.tsx

@@ -87,7 +87,15 @@ export default defineComponent({
         </MSticky>
 
         <div class={styles.btnGroup}>
-          <Button class={styles.btnPractice} round></Button>
+          <Button
+            class={styles.btnPractice}
+            round
+            onClick={() =>
+              router.push({
+                path: '/practice-mode',
+                query: { lessonCoursewareId: forms.cid }
+              })
+            }></Button>
           <Button class={styles.btnTest} round></Button>
         </div>
 

+ 11 - 2
src/views/knowledge-library/model/answer-analysis/index.module.less

@@ -1,8 +1,13 @@
 .answerAnalysis {
+  margin: 12px 12px;
+  background: #FFF6D7;
+  border-radius: 10px;
+  border: 7px solid #FFFAE5;
+  padding: 15px 13px;
+
   .analysisResult {
     display: flex;
     align-items: center;
-    // line-height: 22px;
     font-size: 16px;
     font-weight: 600;
     color: #333333;
@@ -13,15 +18,19 @@
       margin-right: 6px;
     }
   }
+
   .success {
     color: #3b8df3;
   }
+
   .error {
     color: #f44541;
   }
+
   .analysisTitle {
     padding-bottom: 10px;
   }
+
   .analysisMessage {
     font-size: 15px;
     color: #333333;
@@ -29,4 +38,4 @@
     text-align: justify;
     padding-bottom: 5px;
   }
-}
+}

+ 8 - 8
src/views/knowledge-library/model/answer-analysis/index.tsx

@@ -1,9 +1,9 @@
-import { defineComponent } from 'vue'
-import styles from './index.module.less'
-import iconError from '../../images/icon-error.png'
-import iconSuccess from '../../images/icon-success.png'
-import iconAnalysis from '../../images/icon-analysis.png'
-import { Icon } from 'vant'
+import { defineComponent } from 'vue';
+import styles from './index.module.less';
+import iconError from '../../images/icon-error.png';
+import iconSuccess from '../../images/icon-success.png';
+import iconAnalysis from '../../images/icon-analysis.png';
+import { Icon } from 'vant';
 
 // 答案解析
 export default defineComponent({
@@ -65,6 +65,6 @@ export default defineComponent({
           </>
         )}
       </div>
-    )
+    );
   }
-})
+});

+ 16 - 17
src/views/knowledge-library/model/answer-list/index.tsx

@@ -1,6 +1,6 @@
-import { Grid, GridItem } from 'vant'
-import { defineComponent, PropType } from 'vue'
-import styles from './index.module.less'
+import { Grid, GridItem } from 'vant';
+import { defineComponent, PropType } from 'vue';
+import styles from './index.module.less';
 
 export default defineComponent({
   name: 'answer-list',
@@ -22,7 +22,7 @@ export default defineComponent({
       default: () => [
         {
           text: '已答',
-          color: '#FF8057'
+          color: '#1CACF1'
         },
         {
           text: '未答',
@@ -38,20 +38,19 @@ export default defineComponent({
   },
   emits: ['select'],
   setup(props, { emit }) {
-    console.log(props.lookType, 'lookType')
     /**
      * @description 检查用户是否答对
      * @returns Boolean
      */
     const formatUserResult = (id: string) => {
-      let result = false
+      let result = false;
       props.answerResult.forEach((item: any) => {
         if (item.questionId === id) {
-          result = item.rightFlag
+          result = item.rightFlag;
         }
-      })
-      return result
-    }
+      });
+      return result;
+    };
 
     return () => (
       <div class={styles.anserList}>
@@ -71,8 +70,7 @@ export default defineComponent({
             maxHeight: '40vh',
             minHeight: '20vh',
             overflowX: 'auto'
-          }}
-        >
+          }}>
           <Grid class={styles.aList} columnNum={6} border={false}>
             {props.value.map((item: any, index: number) => (
               <GridItem onClick={() => emit('select', index)}>
@@ -85,14 +83,15 @@ export default defineComponent({
                       styles.answered,
                     props.lookType === 'RESULT' &&
                       (formatUserResult(item.id) ? styles.yes : styles.no),
-                    props.lookType === 'CLICK' && index === props.index && styles.answered,
+                    props.lookType === 'CLICK' &&
+                      index === props.index &&
+                      styles.answered,
                     props.lookType !== 'CLICK' && item.showAnalysis
                       ? item.analysis.userResult
                         ? styles.yes
                         : styles.no
                       : ''
-                  ]}
-                >
+                  ]}>
                   {index + 1}
                 </span>
               </GridItem>
@@ -100,6 +99,6 @@ export default defineComponent({
           </Grid>
         </div>
       </div>
-    )
+    );
   }
-})
+});

+ 13 - 4
src/views/knowledge-library/model/choice-question/index.module.less

@@ -4,12 +4,14 @@
   background-color: #fff;
   overflow: hidden;
   border-radius: 10px;
-  & + .unitSubject {
+
+  &+.unitSubject {
     margin-top: 12px;
   }
 }
 
 .unitAnswers {
+
   // padding-bottom: 30px;
   .unitAnswer {
     margin-top: 15px;
@@ -22,36 +24,43 @@
     font-size: 16px;
     font-weight: 500;
     color: #333333;
+
     .option {
       margin-right: 10px;
     }
+
     .value {
       word-break: break-all;
+
       :global {
         .van-image {
           height: 38px;
         }
+
         .van-image__img {
           width: auto;
         }
       }
     }
+
     .valueAudio {
       width: 160px;
     }
   }
+
   .active {
-    background-color: #ffebdd;
-    color: #f67146;
+    background: #ABE8FF;
+    color: #0F77C4;
   }
 
   .answerContent {
     display: flex;
     align-items: center;
   }
+
   .answerChoice {
     font-size: 16px;
     font-weight: 500;
     line-height: 26px;
   }
-}
+}

+ 7 - 10
src/views/knowledge-library/model/choice-question/index.tsx

@@ -50,9 +50,8 @@ export default defineComponent({
     }
   },
   emits: ['update:value'],
-  setup(props, { emit }) {
+  setup(props, { emit, slots }) {
     const onSelect = (item: any) => {
-      console.log(item, 'onSelect');
       if (props.readOnly) return;
       const value: any = props.value || [];
       const result = {
@@ -61,7 +60,6 @@ export default defineComponent({
         answerExtra: item.questionExtra
       };
 
-      console.log(result, 'onSelect');
       if (props.type === 'checkbox') {
         const tempIndex = value.findIndex(
           (c: any) => c.answerId === item.examinationQuestionAnswerId
@@ -92,6 +90,7 @@ export default defineComponent({
     return () => (
       <>
         <div class={styles.unitSubject}>
+          {slots.title && slots.title()}
           {/* 标题 */}
           <AnserTitle
             index={props.index}
@@ -149,13 +148,11 @@ export default defineComponent({
         </div>
 
         {props.showAnalysis && (
-          <div class={styles.unitSubject}>
-            <AnswerAnalysis
-              answerAnalysis={props.analysis.message}
-              topic={props.analysis.topic}
-              userResult={props.analysis.userResult}
-            />
-          </div>
+          <AnswerAnalysis
+            answerAnalysis={props.analysis.message}
+            topic={props.analysis.topic}
+            userResult={props.analysis.userResult}
+          />
         )}
       </>
     );

+ 19 - 6
src/views/knowledge-library/model/drag-question/index.module.less

@@ -4,7 +4,8 @@
   background-color: #fff;
   // overflow: hidden;
   border-radius: 10px;
-  & + .unitSubject {
+
+  &+.unitSubject {
     margin-top: 12px;
   }
 }
@@ -23,44 +24,53 @@
     font-size: 16px;
     font-weight: 500;
     color: #333333;
+
     .option {
       margin-right: 10px;
     }
+
     .value {
       word-break: break-all;
+
       :global {
         .van-image {
           height: 38px;
         }
+
         .van-image__img {
           width: auto;
         }
       }
     }
+
     .valueAudio {
       width: 170px;
     }
   }
+
   .active {
-    background-color: #ffebdd;
-    color: #f67146;
+    background: #ABE8FF;
+    color: #0F77C4;
   }
 
   .answerContent {
     display: flex;
     align-items: center;
   }
+
   .answerChoice {
     font-size: 16px;
     font-weight: 500;
     line-height: 26px;
   }
+
   :global {
     .sortable-ghost {
       opacity: 0.5;
       background: #c8ebfb;
     }
   }
+
   .sortReset {
     display: flex;
     align-items: center;
@@ -68,12 +78,14 @@
     margin-top: 25px;
     padding-top: 30px;
     padding-bottom: 30px;
+
     .tips {
       font-size: 16px;
       font-weight: 500;
       color: #333333;
       line-height: 26px;
     }
+
     :global {
       .van-button {
         min-width: 84px;
@@ -98,18 +110,19 @@
     font-size: 16px;
     font-weight: 500;
     border-radius: 4px;
-    background: #ffebdd;
-    color: #f67146;
+    background: #E3F6FF;
+    color: #1CACF1;
     justify-content: center;
   }
 
   .imgs {
     height: 56px;
     width: auto;
+
     :global {
       .van-image__img {
         width: auto;
       }
     }
   }
-}
+}

+ 20 - 11
src/views/knowledge-library/model/drag-question/index.tsx

@@ -56,7 +56,7 @@ export default defineComponent({
     }
   },
   emits: ['update:value'],
-  setup(props, { emit }) {
+  setup(props, { emit, slots }) {
     const state = reactive({
       domId: 'draggableContainer' + +new Date(),
       drag: false,
@@ -110,7 +110,6 @@ export default defineComponent({
         answerExtra: state.list.length + 1
       });
 
-      // console.log(state.list, '------', result, 'result lis4t')
       emit('update:value', result);
 
       nextTick(() => {
@@ -168,6 +167,8 @@ export default defineComponent({
           });
           state.options = tempList;
         }
+
+        console.log(props.data, 'data');
       });
     };
 
@@ -188,12 +189,14 @@ export default defineComponent({
     );
 
     onMounted(() => {
+      console.log(props.data, 'mounted', state.options);
       initOptions();
     });
 
     return () => (
       <>
         <div class={styles.unitSubject}>
+          {slots.title && slots.title()}
           {/* 标题 */}
           <AnserTitle
             index={props.index}
@@ -227,7 +230,7 @@ export default defineComponent({
               </div>
             ))}
             <div class={[styles.sortReset, 'van-hairline--top']}>
-              <span class={styles.tips}>我的回答(可拖拽)</span>
+              <span class={styles.tips}>请长按拖拽答案进行排序</span>
               <Button
                 type="primary"
                 round
@@ -261,7 +264,15 @@ export default defineComponent({
                 </div>
               ))
             ) : (
-              <Draggable v-model:modelValue={state.options} itemKey="itemIndex">
+              <Draggable
+                v-model:modelValue={state.options}
+                itemKey="itemIndex"
+                componentData={{
+                  itemKey: 'id',
+                  tag: 'div',
+                  animation: 200,
+                  group: 'description'
+                }}>
                 {{
                   item: (element: any) => {
                     const item = element.element;
@@ -289,13 +300,11 @@ export default defineComponent({
           </div>
         </div>
         {props.showAnalysis && (
-          <div class={styles.unitSubject}>
-            <AnswerAnalysis
-              answerAnalysis={props.analysis.message}
-              topic={props.analysis.topic}
-              userResult={props.analysis.userResult}
-            />
-          </div>
+          <AnswerAnalysis
+            answerAnalysis={props.analysis.message}
+            topic={props.analysis.topic}
+            userResult={props.analysis.userResult}
+          />
         )}
       </>
     );

+ 5 - 2
src/views/knowledge-library/model/error-mode/index.module.less

@@ -11,6 +11,7 @@
     font-size: 18px;
     font-weight: 600;
     color: #333333;
+
     .titleImg {
       width: 18px;
       height: 18px;
@@ -31,6 +32,7 @@
       color: #4593f4;
       margin-right: 20px;
     }
+
     .no {
       color: #f44541;
     }
@@ -40,10 +42,11 @@
     padding: 20px 0 30px;
     font-size: 15px;
     color: #333333;
-    text-align: justify;
+    // text-align: justify;
     line-height: 23px;
+
     span {
       font-weight: 600;
     }
   }
-}
+}

+ 9 - 10
src/views/knowledge-library/model/error-mode/index.tsx

@@ -1,7 +1,7 @@
-import { defineComponent } from 'vue'
-import { Image, Button } from 'vant'
-import iconError from '../../images/icon-error.png'
-import styles from './index.module.less'
+import { defineComponent } from 'vue';
+import { Image, Button } from 'vant';
+import iconError from '../../images/icon-error.png';
+import styles from './index.module.less';
 
 export default defineComponent({
   name: 'result-mode',
@@ -45,13 +45,12 @@ export default defineComponent({
           class={styles.btn}
           block
           onClick={() => {
-            emit('conform')
-            emit('close')
-          }}
-        >
+            emit('conform');
+            emit('close');
+          }}>
           {props.confirmButtonText}
         </Button>
       </div>
-    )
+    );
   }
-})
+});

+ 10 - 4
src/views/knowledge-library/model/keep-look-question/index.module.less

@@ -4,7 +4,8 @@
   background-color: #fff;
   // overflow: hidden;
   border-radius: 10px;
-  & + .unitSubject {
+
+  &+.unitSubject {
     margin-top: 12px;
   }
 }
@@ -21,13 +22,16 @@
     display: flex;
     align-items: center;
     justify-content: space-between;
+
     &:last-child {
       margin-bottom: 0;
     }
   }
+
   .img {
     width: 95px;
   }
+
   .unitItem {
     display: flex;
     align-items: center;
@@ -46,8 +50,9 @@
   }
 
   .active {
-    border: 2px solid #ff8057;
-    color: #f67146;
+    border: 2px solid #0F77C4;
+    color: #0F77C4;
+
   }
 }
 
@@ -60,6 +65,7 @@
 .resetBtnGroup {
   text-align: right;
   padding-bottom: 16px;
+
   :global {
     .van-button {
       min-width: 84px;
@@ -69,4 +75,4 @@
       font-weight: 500;
     }
   }
-}
+}

+ 8 - 9
src/views/knowledge-library/model/keep-look-question/index.tsx

@@ -47,7 +47,7 @@ export default defineComponent({
     }
   },
   emits: ['update:value'],
-  setup(props, { emit }) {
+  setup(props, { emit, slots }) {
     const canvasRef = ref();
     const state = reactive({
       answerDomId: 'answer' + +new Date(),
@@ -310,7 +310,7 @@ export default defineComponent({
         Math.ceil(endPoint.y) * state.dpr
       );
       ctx.lineWidth = 2 * state.dpr;
-      ctx.strokeStyle = '#FF8057';
+      ctx.strokeStyle = '#1CACF1';
 
       ctx.stroke();
     };
@@ -426,6 +426,7 @@ export default defineComponent({
     return () => (
       <>
         <div class={styles.unitSubject}>
+          {slots.title && slots.title()}
           {/* 标题 */}
           <AnserTitle
             index={props.index}
@@ -501,13 +502,11 @@ export default defineComponent({
         </div>
 
         {props.showAnalysis && (
-          <div class={styles.unitSubject}>
-            <AnswerAnalysis
-              answerAnalysis={props.analysis.message}
-              topic={props.analysis.topic}
-              userResult={props.analysis.userResult}
-            />
-          </div>
+          <AnswerAnalysis
+            answerAnalysis={props.analysis.message}
+            topic={props.analysis.topic}
+            userResult={props.analysis.userResult}
+          />
         )}
       </>
     );

+ 8 - 3
src/views/knowledge-library/model/play-question/index.module.less

@@ -4,7 +4,8 @@
   background-color: #fff;
   // overflow: hidden;
   border-radius: 10px;
-  & + .unitSubject {
+
+  &+.unitSubject {
     margin-top: 12px;
   }
 }
@@ -18,6 +19,7 @@
   padding: 15px 12px;
   background: #ffebdd;
   border-radius: 10px;
+
   .img {
     width: 45px;
     height: 45px;
@@ -29,6 +31,7 @@
     font-weight: 500;
     color: #333333;
   }
+
   .playBtn {
     flex-shrink: 0;
     font-size: 15px;
@@ -43,11 +46,13 @@
   margin-top: 25px;
   padding-top: 20px;
   text-align: center;
+
   .score {
     font-size: 32px;
     font-weight: bold;
-    color: #f67146;
+    color: #0F77C4;
   }
+
   .scoreTitle {
     font-size: 16px;
     font-weight: 500;
@@ -59,4 +64,4 @@
     font-size: 12px;
     color: #aaaaaa;
   }
-}
+}

+ 9 - 10
src/views/knowledge-library/model/play-question/index.tsx

@@ -65,7 +65,7 @@ export default defineComponent({
     }
   },
   emits: ['update:value'],
-  setup(props, { emit }) {
+  setup(props, { emit, slots }) {
     console.log(props, 'props');
     const state = reactive({
       list: [] as any,
@@ -200,6 +200,7 @@ export default defineComponent({
     return () => (
       <>
         <div class={styles.unitSubject}>
+          {slots.title && slots.title()}
           {/* 标题 */}
           <AnserTitle
             index={props.index}
@@ -252,15 +253,13 @@ export default defineComponent({
           </div>
         </div>
         {props.showAnalysis && (
-          <div class={styles.unitSubject}>
-            <AnswerAnalysis
-              answerAnalysis={props.analysis.message}
-              topic={props.analysis.topic}
-              userResult={props.analysis.userResult}
-              rightFlagText={'合格'}
-              errorFlagText={'不合格'}
-            />
-          </div>
+          <AnswerAnalysis
+            answerAnalysis={props.analysis.message}
+            topic={props.analysis.topic}
+            userResult={props.analysis.userResult}
+            rightFlagText={'合格'}
+            errorFlagText={'不合格'}
+          />
         )}
       </>
     );

BIN
src/views/knowledge-library/model/unit-audio/images/icon-audio.png


BIN
src/views/knowledge-library/model/unit-audio/images/icon-pause.png


+ 7 - 5
src/views/knowledge-library/model/unit-audio/index.module.less

@@ -13,23 +13,25 @@
   width: 100%;
   height: 40px;
   box-sizing: border-box;
-  background: #ff8057;
+  background: rgba(28, 172, 241, 0.11);
+  border: 1px solid #1CACF1;
   border-radius: 20px;
   padding: 0 12px;
   z-index: 9;
   display: flex;
   align-items: center;
   justify-content: space-between;
-  color: #fff;
+  color: #1CACF1;
+
   .htmlTimes {
     display: flex;
     align-items: center;
-    color: #fff;
+    color: #1CACF1;
     font-size: 16px;
     font-weight: 500;
-    color: #ffffff;
     line-height: 22px;
   }
+
   .iconAudio {
     margin-right: 6px;
     font-size: 18px;
@@ -38,4 +40,4 @@
   .audioStatus {
     font-size: 14px;
   }
-}
+}

+ 149 - 0
src/views/knowledge-library/practice-mode/index.module.less

@@ -0,0 +1,149 @@
+.unitDetail {
+  min-height: 100vh;
+  overflow: hidden;
+  background: url('../images/bg.png') no-repeat top center;
+  background-size: contain;
+  position: relative;
+  background-color: #ABE8FF;
+}
+
+.unitSwipe {
+  margin-top: 75px;
+
+  .questionTitle {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    font-size: 14px;
+    font-weight: bold;
+    color: #333;
+    padding-bottom: 12px;
+
+    .questionNum {
+      span {
+        color: #FF5A56;
+      }
+    }
+
+    .questionType {
+      display: flex;
+      align-items: center;
+
+      i {
+        margin-right: 6px;
+        display: inline-block;
+        width: 20px;
+        height: 20px;
+        background: url('../images/icon-book.png') no-repeat center;
+        background-size: contain;
+      }
+    }
+  }
+}
+
+.wapList {
+  width: 44px;
+  height: 49px;
+  flex-shrink: 0;
+  margin-left: 18px;
+}
+
+.practiceResult {
+  .practiceTitle {
+    font-size: 20px;
+    font-weight: 600;
+    color: #191919;
+    line-height: 28px;
+  }
+
+  .practiceRate {
+    padding-top: 15px;
+    font-size: 60px;
+    // font-family: DINA;
+    font-weight: bold;
+    color: #f67146;
+    line-height: 70px;
+  }
+
+  :global {
+    .van-grid {
+      padding-top: 10px;
+      padding-bottom: 10px;
+      margin-left: 5%;
+      width: 90%;
+    }
+
+    .van-grid-item__content {
+      background-color: transparent;
+    }
+  }
+
+  .title {
+    font-size: 24px;
+    // font-family: DINA;
+    font-weight: bold;
+    color: #333333;
+    line-height: 28px;
+  }
+
+  .name {
+    padding-top: 3px;
+    font-size: 14px;
+    color: #777777;
+    line-height: 20px;
+  }
+
+  .practiceTips {
+    margin-left: 10%;
+    width: 80%;
+    background: linear-gradient(135deg, #fff4da 0%, #ffe6c4 100%);
+    border-radius: 10px;
+    padding: 14px 0;
+    text-align: center;
+    font-size: 15px;
+
+    font-weight: 500;
+    color: #703a17;
+    line-height: 21px;
+    letter-spacing: 1px;
+  }
+}
+
+.btnSection {
+  background-color: #fff;
+  padding-top: 20px;
+  --van-button-default-height: 49px;
+
+  .prevBtn,
+  .nextBtn,
+  .activePrevBtn {
+    width: 124px !important;
+    border: none;
+    color: #fff;
+  }
+
+  .prevBtn {
+    background: url('../images/prev_btn_bg.png') no-repeat center;
+    background-size: contain;
+  }
+
+  .activePrevBtn {
+    background: url('../images/next_btn_bg.png') no-repeat center;
+    background-size: contain;
+  }
+
+  .nextBtn {
+    background: url('../images/next_btn_bg.png') no-repeat center;
+    background-size: contain;
+  }
+
+  :global {
+    .van-button--disabled {
+      color: #587C98;
+
+      &:before {
+        opacity: 0.2 !important;
+      }
+    }
+  }
+}

+ 653 - 0
src/views/knowledge-library/practice-mode/index.tsx

@@ -0,0 +1,653 @@
+import {
+  ActionSheet,
+  Button,
+  Cell,
+  Grid,
+  GridItem,
+  Image,
+  Popup,
+  Swipe,
+  SwipeItem
+} from 'vant';
+import {
+  computed,
+  defineComponent,
+  nextTick,
+  onMounted,
+  onUnmounted,
+  reactive,
+  ref
+} from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import styles from './index.module.less';
+import iconButtonList from '../images/icon-button-list.png';
+import { state as baseState } from '@/state';
+import MSticky from '@/components/m-sticky';
+import ChoiceQuestion from '../model/choice-question';
+import AnswerList from '../model/answer-list';
+import ODialog from '@/components/m-dialog';
+import DragQuestion from '../model/drag-question';
+import KeepLookQuestion from '../model/keep-look-question';
+import PlayQuestion from '../model/play-question';
+import ErrorMode from '../model/error-mode';
+import ResultFinish from '../model/result-finish';
+import { eventUnit, QuestionType } from '../unit';
+import request from '@/helpers/request';
+import { useRect } from '@vant/use';
+import OHeader from '@/components/m-header';
+import { useEventListener, useInterval, useWindowScroll } from '@vueuse/core';
+
+export default defineComponent({
+  name: 'unit-detail',
+  setup() {
+    const route = useRoute();
+    const router = useRouter();
+    const swipeRef = ref();
+    const state = reactive({
+      lessonCoursewareId: route.query.lessonCoursewareId,
+      background: 'transparent',
+      color: '#fff',
+      visiableError: false,
+      visiableAnswer: false,
+      visiableResult: false,
+      id: route.query.id,
+      currentIndex: 0,
+      questionList: [],
+      visiableSure: false,
+      resultInfo: {} as any,
+      resultStatusType: 'SUCCESS', // 'SUCCESS' | 'FAIL'
+      visiableExam: false, // 考试已结束
+      nextStatus: false,
+      swipeHeight: 'auto' as any,
+      answerAnalysis: '',
+      questionTypeCode: '',
+      overResult: {
+        time: '00:00', // 时长
+        questionLength: 0, // 答题数
+        errorLength: 0, // 错题数
+        rate: 0 // 正确率
+      },
+      quitStatus: false,
+      dialogMessage: '',
+      dialogStatus: false
+    });
+
+    // 计时
+    const { counter, resume, pause } = useInterval(1000, { controls: true });
+
+    const getExamDetails = async () => {
+      try {
+        const { data } = await request.post(
+          '/edu-app/studentUnitExamination/queryPracticeModeExam',
+          {
+            data: {
+              lessonCoursewareId: state.lessonCoursewareId
+            }
+          }
+        );
+        const temp = data || [];
+        temp.examinationQuestionAdds.forEach((item: any) => {
+          item.showAnalysis = false; // 默认不显示解析
+          item.analysis = {
+            message: item.answerAnalysis,
+            topic: true, // 是否显示结果
+            userResult: false // 用户答题对错
+          };
+          item.userAnswer = []; // 用户答题
+        });
+        state.questionList = temp.examinationQuestionAdds || [];
+
+        console.log(state.questionList);
+      } catch {
+        //
+      }
+    };
+
+    /**
+     * @description 下一题 | 测试完成
+     */
+    const onNextQuestion = async () => {
+      try {
+        const questionList = state.questionList || [];
+
+        let result: any = {};
+        questionList.forEach((question: any, index: number) => {
+          // 格式化所有题目的答案
+          if (index === state.currentIndex) {
+            result = {
+              questionId: question.id,
+              details: question.userAnswer || []
+            };
+          }
+        });
+
+        const { data } = await request.post(
+          '/edu-app/studentUnitExamination/submitTrainingAnswer',
+          {
+            hideLoading: true,
+            data: result
+          }
+        );
+        // 初始化是否显示解析
+        questionList.forEach((question: any, index: number) => {
+          // 格式化所有题目的答案
+          if (index === state.currentIndex) {
+            state.answerAnalysis = question.answerAnalysis;
+            state.questionTypeCode = question.questionTypeCode;
+            question.showAnalysis = true;
+            question.analysis.userResult = data;
+          }
+        });
+
+        // 判断是否是最后一题
+        if (state.questionList.length === state.currentIndex + 1) {
+          eventUnit.emit('unitAudioStop');
+          state.visiableSure = true;
+          return;
+        }
+
+        if (data) {
+          swipeRef.value?.next();
+        } else {
+          state.visiableError = true;
+        }
+      } catch {
+        //
+      }
+    };
+
+    //
+    const getAnswerResult = computed(() => {
+      const questionList = state.questionList || [];
+      let count = 0;
+      let passCount = 0;
+      let noPassCount = 0;
+      questionList.forEach((item: any) => {
+        if (item.showAnalysis) {
+          count += 1;
+          if (item.analysis.userResult) {
+            passCount += 1;
+          } else {
+            noPassCount += 1;
+          }
+        }
+      });
+
+      return {
+        count,
+        passCount,
+        noPassCount
+      };
+    });
+
+    /**
+     * @description 重置当前的题目高度
+     * @param {any} scroll 是否滚动到顶部
+     */
+    let size = 0;
+    const resizeSwipeItemHeight = (scroll = true) => {
+      nextTick(() => {
+        scroll && window.scrollTo(0, 0);
+        setTimeout(() => {
+          const currentItemDom: any = document
+            .querySelectorAll('.van-swipe-item')
+            [state.currentIndex]?.querySelector('.swipe-item-question');
+
+          const allImg = currentItemDom?.querySelectorAll(
+            '.answerTitleImg img'
+          );
+          let status = true;
+          // console.log(allImg)
+          allImg?.forEach((img: any) => {
+            console.log(img.complete);
+            if (!img.complete) {
+              status = false;
+            }
+          });
+          // 判断图片是否加载完了
+          if (!status && size < 3) {
+            setTimeout(() => {
+              size += 1;
+              resizeSwipeItemHeight(scroll);
+            }, 300);
+          }
+          if (status) {
+            size = 0;
+          }
+          const rect = useRect(currentItemDom);
+          state.swipeHeight = rect.height;
+        }, 100);
+      });
+    };
+
+    const onConfirmExam = () => {
+      const answerResult = getAnswerResult.value;
+      let rate = 0;
+
+      if (answerResult.count > 0) {
+        rate = Math.floor((answerResult.passCount / answerResult.count) * 100);
+      }
+
+      const times = counter.value;
+      const minute =
+        Math.floor(times / 60) >= 10
+          ? Math.floor(times / 60)
+          : '0' + Math.floor(times / 60);
+      const seconds = times % 60 >= 10 ? times % 60 : '0' + (times % 60);
+      state.overResult = {
+        time: minute + ':' + seconds, // 时长
+        questionLength: answerResult.count, // 答题数
+        errorLength: answerResult.noPassCount, // 错题数
+        rate // 正确率
+      };
+      // 重置计时
+      pause();
+      counter.value = 0;
+      state.visiableResult = true;
+    };
+
+    // 重新练习
+    const onCloseResult = async () => {
+      state.questionList = [];
+      await getExamDetails();
+      setTimeout(async () => {
+        swipeRef.value?.swipeTo(0, {
+          immediate: true
+        });
+        state.swipeHeight = 'auto';
+        state.answerAnalysis = '';
+        state.overResult = {
+          time: '00:00', // 时长
+          questionLength: 0, // 答题数
+          errorLength: 0, // 错题数
+          rate: 0 // 正确率
+        };
+        state.visiableResult = false;
+        // 恢复计时
+        resume();
+        resizeSwipeItemHeight();
+      }, 100);
+    };
+
+    // 下一个考点
+    const onConfirmResult = () => {
+      // const knowledgelist = state.knowledgelist || [];
+      // // console.log('🚀 ~ file: index.tsx:246 ~ onConfirmResult ~ knowledgelist', knowledgelist)
+      // // 当前正在考试的节点
+      // const knownleIndex = knowledgelist.findIndex(
+      //   (item: any) => item.id === state.id
+      // );
+      // console.log(
+      //   '🚀 ~ file: index.tsx:249 ~ onConfirmResult ~ knownleIndex',
+      //   knownleIndex
+      // );
+      // let currentKnowle: any = {};
+      // if (knownleIndex + 1 >= knowledgelist.length || knownleIndex < 0) {
+      //   currentKnowle = knowledgelist[0];
+      // } else {
+      //   currentKnowle = knowledgelist[knownleIndex + 1];
+      // }
+      // state.id = currentKnowle.id;
+      // state.visiableResult = false;
+      // state.currentIndex = 0;
+      // // 重置
+      // onCloseResult();
+    };
+
+    // 拦截
+    const onBack = () => {
+      state.quitStatus = true;
+      eventUnit.emit('unitAudioStop');
+    };
+
+    const onAfter = () => {
+      window.removeEventListener('popstate', onBack, false);
+      router.back();
+    };
+
+    onMounted(async () => {
+      useEventListener(document, 'scroll', () => {
+        const { y } = useWindowScroll();
+        if (y.value > 52) {
+          state.background = '#fff';
+          state.color = '#323333';
+        } else {
+          state.background = 'transparent';
+          state.color = '#fff';
+        }
+      });
+      await getExamDetails();
+
+      resizeSwipeItemHeight();
+
+      if (!baseState.user.data.vipMember) {
+        pause();
+        state.dialogStatus = true;
+        state.dialogMessage = '您暂未开通团练宝,请开通后使用';
+        return;
+      }
+
+      // window.history.pushState(null, '', document.URL);
+      // window.addEventListener('popstate', onBack, false);
+    });
+
+    onUnmounted(() => {
+      // 关闭所有音频
+      eventUnit.emit('unitAudioStop');
+    });
+    return () => (
+      <div class={styles.unitDetail}>
+        <MSticky position="top">
+          <OHeader
+            border={false}
+            background={state.background}
+            color={state.color}
+          />
+        </MSticky>
+        <Swipe
+          loop={false}
+          showIndicators={false}
+          ref={swipeRef}
+          duration={300}
+          touchable={false}
+          class={styles.unitSwipe}
+          style={{ paddingBottom: '12px' }}
+          lazyRender
+          height={state.swipeHeight}
+          onChange={(index: number) => {
+            eventUnit.emit('unitAudioStop');
+            state.currentIndex = index;
+            resizeSwipeItemHeight();
+          }}>
+          {state.questionList.map((item: any, index: number) => (
+            <SwipeItem>
+              <div class="swipe-item-question">
+                {item.questionTypeCode === QuestionType.RADIO && (
+                  <ChoiceQuestion
+                    v-model:value={item.userAnswer}
+                    index={index + 1}
+                    data={item}
+                    type="radio"
+                    showAnalysis={item.showAnalysis}
+                    analysis={item.analysis}>
+                    {{
+                      title: () => (
+                        <div class={styles.questionTitle}>
+                          <div class={styles.questionNum}>
+                            <span>{state.currentIndex + 1}</span>/
+                            {state.questionList.length}
+                          </div>
+                          <div class={styles.questionType}>
+                            <i></i>音及音高
+                          </div>
+                        </div>
+                      )
+                    }}
+                  </ChoiceQuestion>
+                )}
+                {item.questionTypeCode === QuestionType.CHECKBOX && (
+                  <ChoiceQuestion
+                    v-model:value={item.userAnswer}
+                    index={index + 1}
+                    data={item}
+                    type="checkbox"
+                    showAnalysis={item.showAnalysis}
+                    analysis={item.analysis}>
+                    {{
+                      title: () => (
+                        <div class={styles.questionTitle}>
+                          <div class={styles.questionNum}>
+                            <span>{state.currentIndex + 1}</span>/
+                            {state.questionList.length}
+                          </div>
+                          <div class={styles.questionType}>
+                            <i></i>音及音高
+                          </div>
+                        </div>
+                      )
+                    }}
+                  </ChoiceQuestion>
+                )}
+                {item.questionTypeCode === QuestionType.SORT && (
+                  <DragQuestion
+                    v-model:value={item.userAnswer}
+                    onUpdate:value={() => {
+                      // 如果是空则滑动到顶部
+                      const status =
+                        item.userAnswer && item.userAnswer.length > 0
+                          ? false
+                          : true;
+                      resizeSwipeItemHeight(status);
+                    }}
+                    data={item}
+                    index={index + 1}
+                    showAnalysis={item.showAnalysis}
+                    analysis={item.analysis}>
+                    {{
+                      title: () => (
+                        <div class={styles.questionTitle}>
+                          <div class={styles.questionNum}>
+                            <span>{state.currentIndex + 1}</span>/
+                            {state.questionList.length}
+                          </div>
+                          <div class={styles.questionType}>
+                            <i></i>音及音高
+                          </div>
+                        </div>
+                      )
+                    }}
+                  </DragQuestion>
+                )}
+                {item.questionTypeCode === QuestionType.LINK && (
+                  <KeepLookQuestion
+                    v-model:value={item.userAnswer}
+                    data={item}
+                    index={index + 1}
+                    showAnalysis={item.showAnalysis}
+                    analysis={item.analysis}>
+                    {{
+                      title: () => (
+                        <div class={styles.questionTitle}>
+                          <div class={styles.questionNum}>
+                            <span>{state.currentIndex + 1}</span>/
+                            {state.questionList.length}
+                          </div>
+                          <div class={styles.questionType}>
+                            <i></i>音及音高
+                          </div>
+                        </div>
+                      )
+                    }}
+                  </KeepLookQuestion>
+                )}
+                {item.questionTypeCode === QuestionType.PLAY && (
+                  <PlayQuestion
+                    v-model:value={item.userAnswer}
+                    data={item}
+                    index={index + 1}
+                    unitId={state.id as any}
+                    showAnalysis={item.showAnalysis}
+                    analysis={item.analysis}>
+                    {{
+                      title: () => (
+                        <div class={styles.questionTitle}>
+                          <div class={styles.questionNum}>
+                            <span>{state.currentIndex + 1}</span>/
+                            {state.questionList.length}
+                          </div>
+                          <div class={styles.questionType}>
+                            <i></i>音及音高
+                          </div>
+                        </div>
+                      )
+                    }}
+                  </PlayQuestion>
+                )}
+              </div>
+            </SwipeItem>
+          ))}
+        </Swipe>
+
+        <MSticky position="bottom">
+          <div class={['btnGroup btnMore', styles.btnSection]}>
+            <Button
+              round
+              block
+              class={
+                state.currentIndex > 0 ? styles.activePrevBtn : styles.prevBtn
+              }
+              disabled={state.currentIndex > 0 ? false : true}
+              onClick={() => {
+                swipeRef.value?.prev();
+              }}>
+              上一题
+            </Button>
+            <Button
+              block
+              round
+              class={styles.nextBtn}
+              onClick={onNextQuestion}
+              loading={state.nextStatus}
+              disabled={state.nextStatus}>
+              {state.questionList.length === state.currentIndex + 1
+                ? '提交'
+                : '下一题'}
+            </Button>
+            <Image
+              src={iconButtonList}
+              class={[styles.wapList, 'van-haptics-feedback']}
+              onClick={() => (state.visiableAnswer = true)}
+            />
+          </div>
+        </MSticky>
+
+        {/* 题目集合 */}
+        <ActionSheet
+          v-model:show={state.visiableAnswer}
+          title="题目列表"
+          safeAreaInsetBottom>
+          <AnswerList
+            value={state.questionList}
+            lookType={'PRACTICE'}
+            onSelect={(item: any) => {
+              // 跳转,并且跳过动画
+              swipeRef.value?.swipeTo(item, {
+                immediate: true
+              });
+              state.visiableAnswer = false;
+            }}
+          />
+        </ActionSheet>
+
+        <Popup
+          v-model:show={state.visiableError}
+          style={{ width: '90%' }}
+          round
+          closeOnClickOverlay={false}>
+          <ErrorMode
+            onClose={() => (state.visiableError = false)}
+            answerAnalysis={state.answerAnalysis}
+            questionTypeCode={state.questionTypeCode}
+            onConform={() => {
+              swipeRef.value?.next();
+              state.answerAnalysis = '';
+            }}
+          />
+        </Popup>
+
+        <Popup
+          v-model:show={state.visiableResult}
+          closeOnClickOverlay={false}
+          style={{ background: 'transparent', width: '96%' }}>
+          <ResultFinish
+            status="PRACTICE"
+            confirmButtonText="下一个考点"
+            cancelButtonText="继续练习本考点"
+            onClose={onCloseResult}
+            onConform={onConfirmResult}
+            v-slots={{
+              content: () => (
+                <div class={styles.practiceResult}>
+                  <div class={styles.practiceTitle}>本次练习正确率</div>
+                  <div class={styles.practiceRate}>
+                    {state.overResult.rate}%
+                  </div>
+                  <Grid border={false} columnNum={3}>
+                    <GridItem>
+                      <p class={styles.title}>{state.overResult.time}</p>
+                      <p class={styles.name}>练习时长</p>
+                    </GridItem>
+                    <GridItem>
+                      <p class={[styles.title]}>
+                        {state.overResult.questionLength | 0}
+                      </p>
+                      <p class={styles.name}>答题数</p>
+                    </GridItem>
+                    <GridItem>
+                      <p class={styles.title}>
+                        {state.overResult.errorLength | 0}
+                      </p>
+                      <p class={styles.name}>错题数</p>
+                    </GridItem>
+                  </Grid>
+                  {state.overResult.rate >= 100 ? (
+                    <div class={styles.practiceTips}>
+                      你真棒!
+                      <br />
+                      本知识点你已经完全掌握啦!
+                    </div>
+                  ) : (
+                    <div class={styles.practiceTips}>
+                      继续努力!
+                      <br />
+                      争取在测验中获得高分!
+                    </div>
+                  )}
+                </div>
+              )
+            }}
+          />
+        </Popup>
+
+        <ODialog
+          v-model:show={state.visiableSure}
+          title="练习完成"
+          message="确认本次练习的题目都完成了吗?"
+          messageAlign="left"
+          showCancelButton
+          cancelButtonText="再等等"
+          confirmButtonText="确认完成"
+          onConfirm={onConfirmExam}
+        />
+
+        <ODialog
+          v-model:show={state.quitStatus}
+          title="提示"
+          message="您是否退出本次练习?"
+          showCancelButton
+          cancelButtonText="取消"
+          onCancel={() => {
+            window.history.pushState(null, '', document.URL);
+            window.addEventListener('popstate', onBack, false);
+          }}
+          confirmButtonText="确认退出"
+          onConfirm={onAfter}
+        />
+
+        <ODialog
+          message={state.dialogMessage}
+          v-model:show={state.dialogStatus}
+          showCancelButton
+          cancelButtonText="返回"
+          onCancel={() => {
+            router.back();
+          }}
+          confirmButtonText="立即开通"
+          onConfirm={() => {
+            router.push('/memberCenter');
+          }}
+        />
+      </div>
+    );
+  }
+});

+ 19 - 22
src/views/knowledge-library/unit-detail.tsx

@@ -1,14 +1,15 @@
 import MHeader from '@/components/m-header';
 import MSticky from '@/components/m-sticky';
-import { defineComponent, onMounted, reactive } from 'vue';
+import { defineComponent, onMounted, reactive, ref } from 'vue';
 import styles from './index.module.less';
 import { useRoute, useRouter } from 'vue-router';
-import { Image } from 'vant';
 import request from '@/helpers/request';
 import { useEventListener, useWindowScroll } from '@vueuse/core';
+import iconExamQuestion from './images/icon-exam-question.png';
+import MEmpty from '@/components/m-empty';
 
 export default defineComponent({
-  name: 'wroing-book',
+  name: 'unit-detail',
   setup() {
     const router = useRouter();
     const route = useRoute();
@@ -16,20 +17,16 @@ export default defineComponent({
     const forms = reactive({
       detailId: route.query.detailId,
       background: 'transparent',
-      color: '#fff'
+      color: '#fff',
+      dataInfo: '' as any
     });
 
     const getList = async () => {
       try {
-        const { data } = await request.post(
-          '/edu-app/lessonCourseware/queryStudentLessonDetail',
-          {
-            requestType: 'form',
-            data: {
-              lessonCoursewareId: forms.detailId
-            }
-          }
+        const { data } = await request.get(
+          '/edu-app/lessonCoursewareKnowledgeDetail/detail/' + forms.detailId
         );
+        forms.dataInfo = data;
       } catch {
         //
       }
@@ -46,6 +43,7 @@ export default defineComponent({
           forms.color = '#fff';
         }
       });
+
       getList();
     });
     return () => (
@@ -68,17 +66,16 @@ export default defineComponent({
         </MSticky>
 
         <div class={[styles.containerSection, styles.woringSection]}>
-          <div class={styles.woringContent}>
-            <Image
-              class={styles.unitImg}
-              lazyLoad
-              src={
-                'https://lanhu-dds-backend.oss-cn-beijing.aliyuncs.com/merge_image/imgs/a0c89db386a44702acf8ae61fe74c201_mergeImage.png'
-              }
-            />
-            这里是内容
-          </div>
+          {forms.dataInfo ? (
+            <div class={styles.woringContent} v-html={forms.dataInfo}></div>
+          ) : (
+            <div class={styles.woringContent}>
+              <MEmpty description="暂无内容" style={{ paddingTop: '40px' }} />
+            </div>
+          )}
         </div>
+
+        <img src={iconExamQuestion} class={styles.iconExamQuestion} />
       </div>
     );
   }

BIN
src/views/knowledge-library/wroing-book/ai-exam/images/icon-book.png


BIN
src/views/knowledge-library/wroing-book/ai-exam/images/wroing-bg.png


BIN
src/views/knowledge-library/wroing-book/ai-exam/images/wroing-title.png


+ 103 - 0
src/views/knowledge-library/wroing-book/ai-exam/index.module.less

@@ -0,0 +1,103 @@
+.woringBook {
+  min-height: 100vh;
+  background: url('./images/wroing-bg.png') no-repeat top center;
+  background-size: contain;
+  overflow: hidden;
+  background-color: #fff;
+}
+
+.woringHeader {
+  display: flex;
+  align-items: center;
+  height: var(--van-nav-bar-height);
+
+  .leftArrow {
+    padding: 0 var(--k-padding-md);
+  }
+
+  .title {
+    position: relative;
+    z-index: 1;
+
+    i {
+      width: 79px;
+      height: 20px;
+      display: inline-block;
+      background: url('./images/wroing-title.png') no-repeat center;
+      background-size: contain;
+    }
+
+    &::after {
+      content: ' ';
+      display: inline-block;
+      position: absolute;
+      left: 0;
+      bottom: -2px;
+      width: 48px;
+      height: 6px;
+      background: linear-gradient(270deg, rgba(119, 255, 239, 0.59) 0%, #42CDFF 100%);
+      z-index: -1;
+    }
+  }
+}
+
+.woringSecgtion {
+  min-height: calc(100vh - var(--header-height) - 36px);
+  margin-top: 36px;
+  background: #FFFFFF;
+  border-radius: 16px 16px 0 0;
+  padding: 12px 15px;
+
+  .title {
+    display: flex;
+    align-items: center;
+    font-size: 17px;
+    font-weight: 600;
+    color: #333333;
+    line-height: 24px;
+
+    &::before {
+      content: ' ';
+      display: inline-block;
+      margin-right: 6px;
+      width: 4px;
+      height: 14px;
+      border-radius: 4px;
+      background: linear-gradient(128deg, #259CFE 0%, #5BECFF 100%);
+    }
+  }
+
+  .cell {
+    padding: 18px 0;
+
+    .iconImg {
+      width: 39px;
+      height: 39px;
+      margin-right: 14px;
+    }
+
+    .cellTitle {
+      font-size: 17px;
+      font-weight: 600;
+      color: #333333;
+      line-height: 24px;
+    }
+
+    .cellContent {
+      font-size: 14px;
+      color: #777777;
+      line-height: 20px;
+
+      span {
+        color: #FF5A56;
+        padding-right: 5px;
+      }
+    }
+
+    :global {
+      .van-cell__label {
+        margin-top: 0;
+      }
+    }
+  }
+}

+ 86 - 0
src/views/knowledge-library/wroing-book/ai-exam/index.tsx

@@ -0,0 +1,86 @@
+import MHeader from '@/components/m-header';
+import MSticky from '@/components/m-sticky';
+import { defineComponent, reactive, ref } from 'vue';
+import styles from './index.module.less';
+import { useRouter } from 'vue-router';
+import { Button, Cell, CellGroup, Checkbox, CheckboxGroup, Image } from 'vant';
+import iconBook from './images/icon-book.png';
+
+export default defineComponent({
+  name: 'wroing-book',
+  setup() {
+    const router = useRouter();
+    const checkboxRefs = ref([] as any);
+    const forms = reactive({
+      list: [1, 2, 3, 4],
+      checked: [] as any
+    });
+
+    const checkboxToggle = (index: number) => {
+      checkboxRefs.value[index].toggle();
+    };
+    return () => (
+      <div class={styles.woringBook}>
+        <MSticky position="top">
+          <MHeader border={false} background="transparent">
+            {{
+              content: () => (
+                <div class={styles.woringHeader}>
+                  <i
+                    onClick={() => router.back()}
+                    class={[
+                      'van-badge__wrapper van-icon van-icon-arrow-left van-nav-bar__arrow',
+                      styles.leftArrow
+                    ]}></i>
+                  <span class={styles.title}>
+                    <i></i>
+                  </span>
+                </div>
+              )
+            }}
+          </MHeader>
+        </MSticky>
+
+        <div class={styles.woringSecgtion}>
+          <div class={styles.title}>请选择练习知识点</div>
+
+          <CellGroup border={false}>
+            <CheckboxGroup v-model={forms.checked}>
+              {forms.list.map((item: any, index: number) => (
+                <Cell
+                  center
+                  onClick={() => checkboxToggle(index)}
+                  class={styles.cell}>
+                  {{
+                    icon: () => <Image src={iconBook} class={styles.iconImg} />,
+                    title: () => <div class={styles.cellTitle}>音乐符号</div>,
+                    label: () => (
+                      <p class={styles.cellContent}>
+                        <span>200</span>道错题
+                      </p>
+                    ),
+                    'right-icon': () => (
+                      <Checkbox
+                        name={index}
+                        ref={el => (checkboxRefs.value[index] = el)}
+                        onClick={(e: MouseEvent) => e.stopPropagation()}
+                      />
+                    )
+                  }}
+                </Cell>
+              ))}
+            </CheckboxGroup>
+          </CellGroup>
+
+          <MSticky position="bottom">
+            <div class={'btnGroup'}>
+              <Button round block type="primary">
+                确认
+              </Button>
+            </div>
+          </MSticky>
+        </div>
+      </div>
+    );
+  }
+});

BIN
src/views/knowledge-library/wroing-book/images/ai-exam.png


BIN
src/views/knowledge-library/wroing-book/images/woring-practice.png


BIN
src/views/knowledge-library/wroing-book/images/woring-stat.png


+ 12 - 2
src/views/knowledge-library/wroing-book/index.tsx

@@ -35,9 +35,19 @@ export default defineComponent({
         </MSticky>
 
         <div class={styles.woringSecgtion}>
-          <Image lazyLoad src={WoringStat} class={styles.woringImg} />
+          <Image
+            lazyLoad
+            src={WoringStat}
+            class={styles.woringImg}
+            onClick={() => router.push('wroing-stat')}
+          />
           <Image lazyLoad src={WoringPractice} class={styles.woringImg} />
-          <Image lazyLoad src={AiExam} class={styles.woringImg} />
+          <Image
+            lazyLoad
+            src={AiExam}
+            class={styles.woringImg}
+            onClick={() => router.push('ai-exam')}
+          />
         </div>
       </div>
     );

+ 103 - 0
src/views/knowledge-library/wroing-book/woring-stat/echarts.ts

@@ -0,0 +1,103 @@
+import * as echarts from 'echarts/core';
+
+export const lineChartOption = {
+  legend: { show: false },
+  emphasis: { lineStyle: { width: 2 } },
+  xAxis: {
+    boundaryGap: false,
+    type: 'value',
+    axisLine: { lineStyle: { color: '#8C8C8C' } }
+  },
+  series: [
+    {
+      // name: '2011',
+      type: 'bar',
+      data: [0, 0, 0, 0],
+      label: {
+        show: true,
+        position: 'right',
+        fontSize: 10,
+        color: '#777'
+      },
+      itemStyle: {
+        normal: {
+          color: (params: any) => {
+            // 给出颜色组
+            var colorList = ['#86BAFF', '#FFDB91', '#85DFCF', '#4E9BFF'];
+            return colorList[params.dataIndex];
+          }
+        }
+      }
+      // itemStyle: {
+      //   normal: {
+      //     color: (params: any) => {
+      //       // 给出颜色组
+      //       let colorList = [
+      //         new echarts.graphic.LinearGradient(0, 1, 0, 1, [
+      //           { offset: 0, color: '#A1E8FF' },
+      //           { offset: 1, color: '#86BAFF' }
+      //         ]),
+      //         new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+      //           { offset: 0, color: '#69DCE8' },
+      //           { offset: 1, color: '#58A2FF' }
+      //         ]),
+      //         new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+      //           { offset: 0, color: '#69DCE8' },
+      //           { offset: 1, color: '#58A2FF' }
+      //         ]),
+      //         new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+      //           { offset: 0, color: '#69DCE8' },
+      //           { offset: 1, color: '#58A2FF' }
+      //         ])
+      //       ];
+      //       return colorList[params.dataIndex];
+      //     }
+      //   }
+      // }
+      // emphasis: {
+      //   itemStyle: {
+      //     color: (params: any) => {
+      //       // 给出颜色组
+      //       let colorList = [
+      //         new echarts.graphic.LinearGradient(0, 0, 0, 0, [
+      //           { offset: 0, color: '#A1E8FF' },
+      //           { offset: 1, color: '#86BAFF' }
+      //         ]),
+      //         new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+      //           { offset: 0, color: '#69DCE8' },
+      //           { offset: 1, color: '#58A2FF' }
+      //         ]),
+      //         new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+      //           { offset: 0, color: '#69DCE8' },
+      //           { offset: 1, color: '#58A2FF' }
+      //         ]),
+      //         new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+      //           { offset: 0, color: '#69DCE8' },
+      //           { offset: 1, color: '#58A2FF' }
+      //         ])
+      //       ];
+      //       return colorList[params.dataIndex];
+      //     }
+      //   }
+      // }
+    }
+  ],
+  title: { show: false },
+  grid: {
+    bottom: '3%',
+    containLabel: true,
+    left: '3%',
+    right: '5%',
+    top: '7%'
+  },
+  tooltip: {
+    trigger: 'axis',
+    confine: true
+  },
+  yAxis: {
+    type: 'category',
+    data: ['多选题', '排序题', '单选题', '连连看']
+  },
+  dataZoom: [{ type: 'inside', throttle: 100 }],
+  toolbox: { feature: { saveAsImage: { show: false } } }
+};

BIN
src/views/knowledge-library/wroing-book/woring-stat/images/icon-1.png


BIN
src/views/knowledge-library/wroing-book/woring-stat/images/icon-2.png


BIN
src/views/knowledge-library/wroing-book/woring-stat/images/icon-3.png


BIN
src/views/knowledge-library/wroing-book/woring-stat/images/icon-4.png


BIN
src/views/knowledge-library/wroing-book/woring-stat/images/icon-5.png


BIN
src/views/knowledge-library/wroing-book/woring-stat/images/section-bg1.png


BIN
src/views/knowledge-library/wroing-book/woring-stat/images/section-bg2.png


BIN
src/views/knowledge-library/wroing-book/woring-stat/images/section-bg3.png


BIN
src/views/knowledge-library/wroing-book/woring-stat/images/stat-1.png


BIN
src/views/knowledge-library/wroing-book/woring-stat/images/stat-2.png


BIN
src/views/knowledge-library/wroing-book/woring-stat/images/stat-3.png


BIN
src/views/knowledge-library/wroing-book/woring-stat/images/stat-top.png


BIN
src/views/knowledge-library/wroing-book/woring-stat/images/wroing-bg.png


BIN
src/views/knowledge-library/wroing-book/woring-stat/images/wroing-title.png


+ 242 - 0
src/views/knowledge-library/wroing-book/woring-stat/index.module.less

@@ -0,0 +1,242 @@
+.woringStat {
+  min-height: 100vh;
+  background: url('./images/wroing-bg.png') no-repeat top center;
+  background-size: contain;
+  background-color: #f8f8f8;
+}
+
+.woringHeader {
+  display: flex;
+  align-items: center;
+  height: var(--van-nav-bar-height);
+
+  .leftArrow {
+    padding: 0 var(--k-padding-md);
+  }
+
+  .title {
+    position: relative;
+    z-index: 1;
+
+    i {
+      width: 80px;
+      height: 20px;
+      display: inline-block;
+      background: url('./images/wroing-title.png') no-repeat center;
+      background-size: contain;
+    }
+
+    &::after {
+      content: ' ';
+      display: inline-block;
+      position: absolute;
+      left: 0;
+      bottom: -2px;
+      width: 48px;
+      height: 6px;
+      background: linear-gradient(270deg, rgba(119, 255, 239, 0.59) 0%, #42CDFF 100%);
+      // opacity: 0.5;
+      z-index: -1;
+    }
+  }
+}
+
+
+.preFix {
+  padding: 13px 12px;
+  display: flex;
+  align-items: center;
+  font-weight: 600;
+  font-size: 16px;
+
+  &::before {
+    content: ' ';
+    display: inline-block;
+    background: #fff;
+    border-radius: 3px;
+    width: 4px;
+    height: 14px;
+    margin-right: 6px;
+  }
+}
+
+.sectionImitate {
+  position: relative;
+  margin: 50px 13px 12px;
+  background: linear-gradient(128deg, #0079FF 0%, #68B0FF 100%);
+  border-radius: 6px;
+
+  .imitateTop {
+    position: absolute;
+    top: -36px;
+    right: 26px;
+    z-index: 9;
+    width: 75px;
+    height: 65px;
+  }
+}
+
+.imitateContainer {
+  --van-grid-item-content-background: transparent;
+  background: url('./images/section-bg3.png') no-repeat left center;
+  background-size: contain;
+
+  .imitateTitle {
+    padding-left: 0;
+    padding-right: 0;
+    margin: 0 12px;
+    color: #fff;
+    border-bottom: 1px solid rgba(255, 255, 255, 0.21);
+  }
+
+  .imitateGrid {
+    color: #FFFFFF;
+
+    .statImg {
+      width: 37px;
+      height: 37px;
+    }
+
+    .tip {
+      padding: 8px 0 3px;
+      font-size: 12px;
+      line-height: 17px;
+    }
+
+    .result {
+      font-size: 20px;
+      font-family: DINAlternate-Bold, DINAlternate;
+      font-weight: bold;
+      line-height: 24px;
+    }
+  }
+}
+
+.sectionConstQestion {
+  margin: 0 13px 12px;
+  background: linear-gradient(360deg, #FFFFFF 0%, #DFE8FF 100%);
+  border-radius: 10px;
+  border: 1px solid #FFFFFF;
+
+  .constTitle {
+    color: #333333;
+
+    &::before {
+      background: linear-gradient(59deg, #7590DB 0%, #A7C5FC 100%);
+    }
+  }
+}
+
+.constSection {
+  margin: 0 12px 12px;
+  background: #FFFFFF;
+  border-radius: 8px;
+
+  height: 200px;
+}
+
+
+.sectionImitateData {
+  margin: 0 13px 12px;
+  background: linear-gradient(360deg, #FFFFFF 0%, #FFECDF 100%);
+  border-radius: 6px;
+  border: 1px solid #FFFFFF;
+
+  .dataTitle {
+    color: #333333;
+
+    &::before {
+      background: linear-gradient(135deg, #FFAC94 0%, #FF9348 100%);
+    }
+  }
+}
+
+.dataImitate {
+  background: url('./images/section-bg1.png') no-repeat right top;
+  background-size: 50%;
+  padding-bottom: 16px;
+
+  .dataList::-webkit-scrollbar {
+    display: none;
+  }
+
+  .dataList {
+    width: 100%;
+    overflow-x: auto;
+    overflow-y: hidden;
+    display: flex;
+    position: relative;
+    user-select: none;
+    box-sizing: content-box;
+  }
+
+  .dataItem {
+    background: #FFFFFF;
+    border-radius: 8px;
+    padding: 12px;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    flex: 1 0 auto;
+    box-sizing: border-box;
+    background: #ffffff;
+    margin-left: 10px;
+
+    &:last-child {
+      margin-right: 10px;
+    }
+
+    .memo {
+      display: flex;
+      align-items: center;
+      font-size: 12px;
+      color: #777777;
+      line-height: 17px;
+    }
+
+    .iconMemo {
+      margin-right: 3px;
+      width: 18px;
+      height: 18px;
+    }
+
+    .memoResult {
+      padding: 6px 0 0 4px;
+      display: flex;
+      width: 100%;
+      font-size: 20px;
+      font-family: DINAlternate-Bold, DINAlternate;
+      font-weight: bold;
+      color: #333333;
+      line-height: 24px;
+    }
+  }
+}
+
+.knowledgeList {
+  margin: 0 12px 0;
+  background: #FFFFFF;
+  border-radius: 8px;
+  padding: 20px 12px;
+
+  .memo {
+    font-size: 12px;
+    color: #777777;
+    line-height: 17px;
+    padding-bottom: 10px;
+
+    span {
+      margin-left: 10px;
+      font-size: 16px;
+    }
+  }
+
+  .knowledgeItem {
+    padding-top: 20px;
+
+    &:first-child {
+      padding-top: 0;
+    }
+  }
+}

+ 309 - 0
src/views/knowledge-library/wroing-book/woring-stat/index.tsx

@@ -0,0 +1,309 @@
+import MHeader from '@/components/m-header';
+import MSticky from '@/components/m-sticky';
+import { defineComponent, markRaw, onMounted, reactive } from 'vue';
+import styles from './index.module.less';
+import { useRouter } from 'vue-router';
+import { Grid, GridItem, Image, Progress } from 'vant';
+import statTop from './images/stat-top.png';
+import stat1 from './images/stat-1.png';
+import stat2 from './images/stat-2.png';
+import stat3 from './images/stat-3.png';
+import icon1 from './images/icon-1.png';
+import icon2 from './images/icon-2.png';
+import icon3 from './images/icon-3.png';
+import icon4 from './images/icon-4.png';
+import icon5 from './images/icon-5.png';
+import { useEventListener, useWindowScroll } from '@vueuse/core';
+
+import * as echarts from 'echarts/core';
+import { BarChart, LineChart } from 'echarts/charts';
+import { PieChart } from 'echarts/charts';
+import {
+  TitleComponent,
+  // 组件类型的定义后缀都为 ComponentOption
+  TooltipComponent,
+  GridComponent,
+  // 数据集组件
+  DatasetComponent,
+  // 内置数据转换器组件 (filter, sort)
+  TransformComponent,
+  LegendComponent,
+  ToolboxComponent,
+  DataZoomComponent
+} from 'echarts/components';
+import { LabelLayout, UniversalTransition } from 'echarts/features';
+import { CanvasRenderer } from 'echarts/renderers';
+
+// 注册必须的组件
+echarts.use([
+  TitleComponent,
+  TooltipComponent,
+  GridComponent,
+  DatasetComponent,
+  TransformComponent,
+  BarChart,
+  LabelLayout,
+  UniversalTransition,
+  CanvasRenderer,
+  PieChart,
+  ToolboxComponent,
+  LegendComponent,
+  DataZoomComponent,
+  LineChart
+]);
+
+import { lineChartOption } from './echarts';
+import request from '@/helpers/request';
+
+const pointColor = [
+  {
+    textColor: '#5BD6A0',
+    backgroundColor: 'linear-gradient(270deg, #76E5BB 0%, #41C685 100%)'
+  },
+  {
+    textColor: '#92CD3A',
+    backgroundColor: 'linear-gradient(270deg, #B2E051 0%, #7ABE28 100%)'
+  },
+  {
+    textColor: '#81B3E9',
+    backgroundColor: 'linear-gradient(270deg, #8DBEEE 0%, #548AD8 100%)'
+  },
+  {
+    textColor: '#F29C71',
+    backgroundColor: 'linear-gradient(270deg, #F7B58A 0%, #EB7E52 100%)'
+  }
+];
+
+export default defineComponent({
+  name: 'wroing-book',
+  setup() {
+    const router = useRouter();
+    const forms = reactive({
+      myChart: null as any,
+      background: 'transparent',
+      color: '#fff',
+      PRACTICE_MODE: {} as any, // 练习数据统计
+      MOCK_TEST: {} as any, // 模拟测试数据统计
+      OFTEN_ERROR_QUESTION: [] as any,
+      OFTEN_ERROR_POINT: [] as any
+    });
+
+    // this.myChart.clear();
+    // this.myChart.setOption(lineChartOption);
+    const getDetails = async () => {
+      try {
+        const { data } = await request.get(
+          '/edu-app/studentExaminationErrorEdition/stat'
+        );
+
+        const tempResult = data || [];
+        tempResult.forEach((item: any) => {
+          if (item.errorEditionStatEnum === 'PRACTICE_MODE') {
+            forms.PRACTICE_MODE = item.data;
+          } else if (item.errorEditionStatEnum === 'MOCK_TEST') {
+            forms.MOCK_TEST = item.data;
+          } else if (item.errorEditionStatEnum === 'OFTEN_ERROR_QUESTION') {
+            forms.OFTEN_ERROR_QUESTION = item.data || [];
+          } else if (item.errorEditionStatEnum === 'OFTEN_ERROR_POINT') {
+            forms.OFTEN_ERROR_POINT = item.data || [];
+          }
+        });
+
+        // RADIO('单选题'),
+        // CHECKBOX('多选题'),
+        // PLAY('演奏题'),
+        // SORT('排序题'),
+        // LINK('连连看');
+        forms.OFTEN_ERROR_QUESTION.forEach((item: any) => {
+          if (item.questionType === 'LINK') {
+            lineChartOption.series[0].data = item.num;
+          } else if (item.questionType === 'RADIO') {
+            lineChartOption.series[1].data = item.num;
+          } else if (item.questionType === 'SORT') {
+            lineChartOption.series[2].data = item.num;
+          } else if (item.questionType === 'CHECKBOX') {
+            lineChartOption.series[3].data = item.num;
+          }
+        });
+        forms.myChart.clear();
+        forms.myChart.setOption(lineChartOption);
+      } catch {
+        //
+      }
+    };
+
+    onMounted(() => {
+      useEventListener(document, 'scroll', () => {
+        const { y } = useWindowScroll();
+        if (y.value > 52) {
+          forms.background = '#fff';
+          forms.color = '#323333';
+        } else {
+          forms.background = 'transparent';
+          forms.color = '#fff';
+        }
+      });
+      forms.myChart = markRaw(
+        echarts.init(document.getElementById('incomeClass') as HTMLDivElement)
+      );
+
+      getDetails();
+    });
+    return () => (
+      <div class={styles.woringStat}>
+        <MSticky position="top">
+          <MHeader
+            border={false}
+            background={forms.background}
+            color={forms.color}>
+            {{
+              content: () => (
+                <div class={styles.woringHeader}>
+                  <i
+                    onClick={() => router.back()}
+                    class={[
+                      'van-badge__wrapper van-icon van-icon-arrow-left van-nav-bar__arrow',
+                      styles.leftArrow
+                    ]}></i>
+                  <span class={styles.title}>
+                    <i></i>
+                  </span>
+                </div>
+              )
+            }}
+          </MHeader>
+        </MSticky>
+
+        <div class={styles.sectionImitate}>
+          <img class={styles.imitateTop} src={statTop} />
+          <div class={styles.imitateContainer}>
+            <div class={[styles.imitateTitle, styles.preFix]}>
+              <i></i>
+              练习模式数据统计
+            </div>
+            <Grid columnNum={3} class={styles.imitateGrid} border={false}>
+              <GridItem>
+                <Image src={stat1} class={styles.statImg} />
+                <p class={styles.tip}>已做题数</p>
+                <p class={styles.result}>{forms.PRACTICE_MODE.totalNum || 0}</p>
+              </GridItem>
+              <GridItem>
+                <Image src={stat2} class={styles.statImg} />
+                <p class={styles.tip}>总错题数</p>
+                <p class={styles.result}>{forms.PRACTICE_MODE.errorNum || 0}</p>
+              </GridItem>
+              <GridItem>
+                <Image src={stat3} class={styles.statImg} />
+                <p class={styles.tip}>正确率</p>
+                <p class={styles.result}>
+                  {forms.PRACTICE_MODE.rightRate || 0}%
+                </p>
+              </GridItem>
+            </Grid>
+          </div>
+        </div>
+
+        <div class={styles.sectionImitateData}>
+          <div class={styles.dataImitate}>
+            <div class={[styles.dataTitle, styles.preFix]}>
+              <i></i>
+              模拟测试数据统计
+            </div>
+            <div class={styles.dataList}>
+              <div class={styles.dataItem}>
+                <div class={styles.memo}>
+                  <img src={icon1} class={styles.iconMemo} />
+                  <span>答卷次数</span>
+                </div>
+                <div class={styles.memoResult}>
+                  {forms.MOCK_TEST.totalNum || 0}
+                </div>
+              </div>
+              <div class={styles.dataItem}>
+                <div class={styles.memo}>
+                  <img src={icon2} class={styles.iconMemo} />
+                  <span>及格次数</span>
+                </div>
+                <div class={styles.memoResult}>
+                  {forms.MOCK_TEST.passNum || 0}
+                </div>
+              </div>
+              <div class={styles.dataItem}>
+                <div class={styles.memo}>
+                  <img src={icon3} class={styles.iconMemo} />
+                  <span>及格率</span>
+                </div>
+                <div class={styles.memoResult}>
+                  {forms.MOCK_TEST.passRate || 0}
+                </div>
+              </div>
+              <div class={styles.dataItem}>
+                <div class={styles.memo}>
+                  <img src={icon4} class={styles.iconMemo} />
+                  <span>总错题数</span>
+                </div>
+                <div class={styles.memoResult}>
+                  {forms.MOCK_TEST.errorNum || 0}
+                </div>
+              </div>
+              <div class={styles.dataItem}>
+                <div class={styles.memo}>
+                  <img src={icon5} class={styles.iconMemo} />
+                  <span>正确率</span>
+                </div>
+                <div class={styles.memoResult}>
+                  {forms.MOCK_TEST.rightRate || 0}%
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <div class={styles.sectionConstQestion}>
+          <div class={styles.constQestion}>
+            <div class={[styles.constTitle, styles.preFix]}>
+              <i></i>
+              常错题型统计
+            </div>
+
+            <div id="incomeClass" class={styles.constSection}></div>
+          </div>
+        </div>
+
+        {forms.OFTEN_ERROR_POINT.length > 0 && (
+          <div class={styles.sectionImitateData}>
+            <div class={styles.dataImitate}>
+              <div class={[styles.dataTitle, styles.preFix]}>
+                <i></i>
+                常错知识点分析
+              </div>
+
+              <div class={styles.knowledgeList}>
+                {forms.OFTEN_ERROR_POINT.forEach((item: any, index: number) => (
+                  <div class={styles.knowledgeItem}>
+                    <div class={styles.memo}>
+                      {item.pointName}
+                      <span style={{ color: pointColor[index % 4].textColor }}>
+                        {item.num}
+                      </span>
+                    </div>
+                    <Progress
+                      color={pointColor[index % 4].backgroundColor}
+                      trackColor="#ECECEC"
+                      showPivot={false}
+                      style={{
+                        borderRadius: '10px'
+                      }}
+                      percentage={20}
+                      strokeWidth={8}
+                    />
+                  </div>
+                ))}
+              </div>
+            </div>
+          </div>
+        )}
+      </div>
+    );
+  }
+});

+ 44 - 37
src/views/layout/auth.tsx

@@ -1,59 +1,55 @@
-import { defineComponent } from 'vue';
+import { computed, defineComponent, onMounted, reactive, watch } from 'vue';
 import styles from './auth.module.less';
 import { state, setLogin, setLogout, setLoginError } from '@/state';
 import { browser } from '@/helpers/utils';
 import { storage } from '@/helpers/storage';
 import { ACCESS_TOKEN } from '@/store/mutation-types';
 import { postMessage } from '@/helpers/native-message';
-import { RouterView } from 'vue-router';
+import { RouterView, useRoute, useRouter } from 'vue-router';
 import request from '@/helpers/request';
 import MHeader from '@/components/m-header';
 import MEmpty from '@/components/m-empty';
 
 export default defineComponent({
   name: 'Auth-loayout',
-  data() {
-    return {
-      loading: false as boolean
-    };
-  },
-  computed: {
-    isExternal() {
-      // 该路由在外部连接打开是否需要登录
+  setup() {
+    const route = useRoute();
+    const router = useRouter();
+    const data = reactive({
+      loading: false
+    });
+    const isExternal = computed(() => {
+      //  该路由在外部连接打开是否需要登录
       // 只判断是否在学员端打开
-      return this.$route.meta.isExternal || false;
-    },
-    isNeedView() {
+      return route.meta.isExternal || false;
+    });
+    const isNeedView = computed(() => {
       return (
         state.user.status === 'login' ||
-        this.$route.path === '/login' ||
-        (this as any).isExternal
+        route.path === '/login' ||
+        isExternal.value
       );
-    }
-  },
-  mounted() {
-    !this.isExternal && this.setAuth();
-  },
-  methods: {
-    async setAuth() {
-      const { query } = this.$route;
+    });
+
+    const setAuth = async () => {
+      const { query } = route;
       const token = query.userInfo || query.Authorization;
 
       if (token) {
         storage.set(ACCESS_TOKEN, token);
       }
-      if (this.loading) {
+      if (data.loading) {
         return;
       }
       if (state.user.status === 'init' || state.user.status === 'error') {
-        this.loading = true;
+        data.loading = true;
         try {
           const res = await request.get('/edu-app/user/getUserInfo', {
             initRequest: true, // 初始化接口
             requestType: 'form',
             hideLoading: true
           });
-          if (res?.code === 200){
+          if (res?.code === 200) {
             setLogin(res.data);
           }
         } catch (e: any) {
@@ -67,22 +63,23 @@ export default defineComponent({
             setLogout();
           }
         }
-        this.loading = false;
+        data.loading = false;
       }
+    };
+    const checkAuthStatus = () => {
       if (state.user.status === 'logout') {
         if (browser().isApp) {
           postMessage({ api: 'login' });
         } else {
           try {
-            const route = this.$route;
             const query = {
-              returnUrl: this.$route.path,
-              ...this.$route.query
+              returnUrl: route.path,
+              ...route.query
             } as any;
             if (route.meta.isRegister) {
               query.isRegister = route.meta.isRegister;
             }
-            this.$router.replace({
+            router.replace({
               path: '/login',
               query: query
             });
@@ -91,10 +88,20 @@ export default defineComponent({
           }
         }
       }
-    }
-  },
-  render() {
-    return (
+    };
+    watch(
+      () => state.user.status,
+      () => {
+        if (state.user.status === 'logout') {
+          checkAuthStatus();
+        }
+      }
+    );
+
+    onMounted(() => {
+      !isExternal.value && setAuth();
+    });
+    return () => (
       <>
         {state.user.status === 'error' ? (
           <div class={styles.error}>
@@ -104,10 +111,10 @@ export default defineComponent({
               description="加载失败,请稍后重试"
               buttonText="重新加载"
               showButton
-              onClick={this.setAuth}
+              onClick={setAuth}
             />
           </div>
-        ) : this.isNeedView ? (
+        ) : isNeedView.value ? (
           <RouterView></RouterView>
         ) : null}
       </>

+ 1 - 1
src/views/layout/code.tsx

@@ -91,7 +91,6 @@ export default defineComponent({
           initRequest: true // 初始化接口
         });
         setLogin(userCash.data);
-        // router.back();
 
         emit('close', true);
       } catch {
@@ -111,6 +110,7 @@ export default defineComponent({
     onMounted(() => {
       nextTick(async () => {
         await onSendSms();
+        forms.showKeyboard = true;
       });
     });
     return () => (

+ 5 - 5
src/views/member-center/index.tsx

@@ -15,7 +15,6 @@ import { useEventListener, useWindowScroll } from '@vueuse/core';
 export default defineComponent({
   name: 'MemberCenter',
   data() {
-    const query = this.$route.query;
     return {
       functionList: [] as any,
       selectMember: {} as any,
@@ -34,6 +33,7 @@ export default defineComponent({
         avatar: users?.avatar,
         id: users?.id,
         isVip: users?.vipMember,
+        membershipGiftDays: users?.membershipGiftDays,
         membershipDays: users?.membershipDays,
         membershipEndTime: users?.membershipEndTime
       };
@@ -213,7 +213,7 @@ export default defineComponent({
                       <div>
                         使用有效期剩余
                         <span class={styles.remaining}>
-                          {this.userInfo.membershipDays}
+                          {this.userInfo.membershipGiftDays}
                         </span>
                       </div>
@@ -230,7 +230,7 @@ export default defineComponent({
           <div
             class={[
               styles.memberItem,
-              this.selectMember.membershipDays > 0 ? styles.memberGift : ''
+              this.selectMember.membershipGiftDays > 0 ? styles.memberGift : ''
             ]}>
             <p class={[styles.title]}>
               <strong>数字化</strong>器乐学练工具
@@ -245,14 +245,14 @@ export default defineComponent({
                 ¥{moneyFormat(this.selectMember.originalPrice)}
               </del>
             </div>
-            {this.selectMember.membershipDays > 0 && (
+            {this.selectMember.membershipGiftDays > 0 && (
               <Cell border={false} class={styles.giftCell}>
                 {{
                   title: () => (
                     <div class={styles.gift}>
                       <img src={iconGift} class={styles.iconGift} />
                       现在购买赠送{' '}
-                      <span>{this.selectMember.membershipDays || 0}</span>
+                      <span>{this.selectMember.membershipGiftDays || 0}</span>
                       天有效期
                     </div>
                   )