浏览代码

编译到小程序

shmily1213 1 月之前
父节点
当前提交
3b47b0c0dd
共有 100 个文件被更改,包括 38 次插入6741 次删除
  1. 31 14
      package.json
  2. 2 20
      src/App.vue
  3. 0 30
      src/common/modules/mx-app-links-config.js
  4. 0 43
      src/common/modules/mx-banner-config.js
  5. 0 41
      src/common/modules/mx-block-index-all-test-config.js
  6. 0 17
      src/common/modules/mx-block-index-config.js
  7. 0 25
      src/common/modules/mx-business-config.js
  8. 0 54
      src/common/modules/mx-menus-index-config.js
  9. 0 36
      src/common/modules/mx-menus-index-topic-center-config.js
  10. 0 119
      src/common/modules/mx-menus-personal-center-config.js
  11. 0 44
      src/common/modules/mx-other-config.js
  12. 0 78
      src/common/modules/mx-tiny-buttons-config.js
  13. 0 628
      src/common/mx-block-widgets.js
  14. 0 15
      src/common/mxConfig.js
  15. 0 210
      src/common/mxConst.js
  16. 0 1
      src/components/ie-dict/ie-dict.vue
  17. 5 1
      src/components/ie-picker/ie-picker.vue
  18. 0 218
      src/components/m-drag/m-drag-vue2.vue
  19. 0 239
      src/components/m-drag/m-drag.vue
  20. 0 39
      src/components/mx-bottom-buttons/mx-bottom-buttons.vue
  21. 0 53
      src/components/mx-buy-vip/mx-buy-vip.vue
  22. 0 59
      src/components/mx-condition-dropdown/mx-condition-dropdown-item.vue
  23. 0 120
      src/components/mx-condition-dropdown/mx-condition-dropdown-popup.vue
  24. 0 47
      src/components/mx-condition-dropdown/mx-condition-dropdown.vue
  25. 0 13
      src/components/mx-condition-dropdown/useConditionDropdownPopupInjection.js
  26. 0 31
      src/components/mx-condition/modules/conditionSharedConfig.js
  27. 0 12
      src/components/mx-condition/modules/useConditionCollegeFeatures.js
  28. 0 12
      src/components/mx-condition/modules/useConditionCollegeLevel.js
  29. 0 12
      src/components/mx-condition/modules/useConditionCollegeLocation.js
  30. 0 12
      src/components/mx-condition/modules/useConditionCollegeNatureTypeCN.js
  31. 0 12
      src/components/mx-condition/modules/useConditionCollegeType.js
  32. 0 29
      src/components/mx-condition/modules/useConditionPaperType.js
  33. 0 15
      src/components/mx-condition/modules/useConditionPickType.js
  34. 0 18
      src/components/mx-condition/modules/useConditionSegmentLocation.js
  35. 0 19
      src/components/mx-condition/modules/useConditionSegmentMode.js
  36. 0 19
      src/components/mx-condition/modules/useConditionSegmentYear.js
  37. 0 22
      src/components/mx-condition/mx-condition-dropdown.vue
  38. 0 23
      src/components/mx-condition/mx-condition.vue
  39. 0 61
      src/components/mx-condition/useConditionDataManager.js
  40. 0 72
      src/components/mx-condition/useConditionEventManager.js
  41. 0 108
      src/components/mx-condition/useConditionFactory.js
  42. 0 43
      src/components/mx-condition/useSearchModelInjection.js
  43. 0 60
      src/components/mx-count-down/mx-count-down.vue
  44. 0 57
      src/components/mx-echarts/mx-echarts.vue
  45. 0 56
      src/components/mx-form-item/mx-form-item.vue
  46. 0 38
      src/components/mx-index-menus/mx-index-menus-item.vue
  47. 0 60
      src/components/mx-index-menus/mx-index-menus.vue
  48. 0 21
      src/components/mx-index-menus/mx-index-paged-menus.vue
  49. 0 10
      src/components/mx-index-menus/shareProps.js
  50. 0 52
      src/components/mx-login-form-item/mx-login-form-item.vue
  51. 0 102
      src/components/mx-nav-bar/mx-nav-bar.vue
  52. 0 19
      src/components/mx-nav-bar/useH5BackHome.js
  53. 0 44
      src/components/mx-paper/components/mx-paper-completion.vue
  54. 0 43
      src/components/mx-paper/components/mx-paper-navigator-popup.vue
  55. 0 17
      src/components/mx-paper/components/mx-paper-progress.vue
  56. 0 41
      src/components/mx-paper/components/mx-paper-tab-item.vue
  57. 0 13
      src/components/mx-paper/components/usePaperNavigatorRefInjection.js
  58. 0 97
      src/components/mx-paper/mx-paper.vue
  59. 0 178
      src/components/mx-paper/usePaperInjection.js
  60. 0 134
      src/components/mx-paper/usePaperNavigationServiceInjection.js
  61. 0 131
      src/components/mx-picker/mx-picker.vue
  62. 0 45
      src/components/mx-popup-template/mx-popup-template.vue
  63. 0 21
      src/components/mx-progress/mx-progress.vue
  64. 0 20
      src/components/mx-question-content/components/mx-question-plain-option-group.vue
  65. 0 15
      src/components/mx-question-content/components/mx-question-plain-option.vue
  66. 0 97
      src/components/mx-question-content/components/mx-question-subjective.vue
  67. 0 184
      src/components/mx-question-content/mx-question-content.vue
  68. 0 14
      src/components/mx-question-content/useMathJaxSwitchInjection.js
  69. 0 25
      src/components/mx-question-content/useQuestionOptionInjection.js
  70. 0 36
      src/components/mx-question/components/mx-question-collect.vue
  71. 0 51
      src/components/mx-question/components/mx-question-correct-popup.vue
  72. 0 26
      src/components/mx-question/components/mx-question-correct.vue
  73. 0 19
      src/components/mx-question/components/mx-question-navigator.vue
  74. 0 55
      src/components/mx-question/components/mx-question-parse.vue
  75. 0 52
      src/components/mx-question/components/mx-question-score-subjective.vue
  76. 0 123
      src/components/mx-question/components/mx-question-statistic.vue
  77. 0 65
      src/components/mx-question/mx-question.vue
  78. 0 286
      src/components/mx-question/useQuestionInjection.js
  79. 0 38
      src/components/mx-question/useQuestionTranslate.js
  80. 0 46
      src/components/mx-search/mx-search.vue
  81. 0 63
      src/components/mx-steps/mx-steps.vue
  82. 0 33
      src/components/mx-submit-layout/mx-submit-layout.vue
  83. 0 44
      src/components/mx-subsection/mx-subsection.vue
  84. 0 135
      src/components/mx-tabs-swiper/mx-tabs-swiper.vue
  85. 0 19
      src/components/mx-tag-button/mx-tag-button.vue
  86. 0 95
      src/components/mx-video/mx-video.vue
  87. 0 38
      src/components/mx-video/useVideoPlay.js
  88. 0 39
      src/components/mx-video/useVideoRecord.js
  89. 0 20
      src/components/vip-guide-more/vip-guide-more.vue
  90. 0 42
      src/components/vue-svg-icons/SvgRegister.js
  91. 0 222
      src/components/vue-svg-icons/vue-svg-icons.vue
  92. 0 92
      src/hooks/defineCacheActions.js
  93. 0 185
      src/hooks/useCacheStore.js
  94. 0 90
      src/hooks/useChooseImage.js
  95. 0 147
      src/hooks/useDownload.js
  96. 0 73
      src/hooks/useEnvStore.js
  97. 0 42
      src/hooks/useMathJaxService.js
  98. 0 14
      src/hooks/usePageConfigInjection.js
  99. 0 27
      src/hooks/usePageScrollInjection.js
  100. 0 246
      src/hooks/usePayment.js

+ 31 - 14
package.json

@@ -12,8 +12,8 @@
   },
   "uni-app": {
     "scripts": {
-      "dev": {
-        "title": "测试环境",
+      "h5-dev": {
+        "title": "h5测试环境",
         "browser": "",
         "env": {
           "UNI_PLATFORM": "h5",
@@ -21,24 +21,41 @@
         },
         "define": {}
       },
-      "pro": {
-        "title": "正式环境",
+      "h5-pro": {
+        "title": "h5正式环境",
         "browser": "",
         "env": {
           "UNI_PLATFORM": "h5",
           "IE_ENV": "production"
         },
         "define": {}
+      },
+      "mp-dev": {
+        "title": "mp-weixin测试环境",
+        "browser": "",
+        "env": {
+          "UNI_PLATFORM": "mp-weixin",
+          "IE_ENV": "development"
+        },
+        "define": {}
+      },
+      "mp-pro": {
+        "title": "mp-weixin正式环境",
+        "browser": "",
+        "env": {
+          "UNI_PLATFORM": "mp-weixin",
+          "IE_ENV": "production"
+        },
+        "define": {}
       }
     }
   },
   "dependencies": {
-    "@dcloudio/uni-app": "3.0.0-4070620250821001",
-    "@dcloudio/uni-app-harmony": "3.0.0-4070620250821001",
-    "@dcloudio/uni-app-plus": "3.0.0-4070620250821001",
-    "@dcloudio/uni-components": "3.0.0-4070620250821001",
-    "@dcloudio/uni-h5": "3.0.0-4070620250821001",
-    "@dcloudio/uni-mp-weixin": "3.0.0-4070620250821001",
+    "@dcloudio/uni-app": "3.0.0-4080520251106001",
+    "@dcloudio/uni-app-plus": "3.0.0-4080520251106001",
+    "@dcloudio/uni-components": "3.0.0-4080520251106001",
+    "@dcloudio/uni-h5": "3.0.0-4080520251106001",
+    "@dcloudio/uni-mp-weixin": "3.0.0-4080520251106001",
     "@rojer/katex-mini": "^1.3.4",
     "@vueuse/core": "^11.2.0",
     "echarts": "^6.0.0",
@@ -54,10 +71,10 @@
   },
   "devDependencies": {
     "@dcloudio/types": "^3.4.8",
-    "@dcloudio/uni-automator": "3.0.0-4070620250821001",
-    "@dcloudio/uni-cli-shared": "3.0.0-4070620250821001",
-    "@dcloudio/uni-stacktracey": "3.0.0-4070620250821001",
-    "@dcloudio/vite-plugin-uni": "3.0.0-4070620250821001",
+    "@dcloudio/uni-automator": "3.0.0-4080520251106001",
+    "@dcloudio/uni-cli-shared": "3.0.0-4080520251106001",
+    "@dcloudio/uni-stacktracey": "3.0.0-4080520251106001",
+    "@dcloudio/vite-plugin-uni": "3.0.0-4080520251106001",
     "@types/node": "^24.1.0",
     "@uni-helper/vite-plugin-uni-tailwind": "^0.15.2",
     "@vue/runtime-core": "^3.4.21",

+ 2 - 20
src/App.vue

@@ -15,26 +15,8 @@ export default {
 }
 </script>
 
-<style>
+<style lang="scss">
 /*每个页面公共css */
-@import "@/uni_modules/uv-ui-tools/index.scss";
+/* @import "@/uni_modules/uv-ui-tools/index.scss"; */
 @import '@/static/theme/theme.module.scss';
-@import "@/static/common.scss";
-
-.uni-modal {
-  border-radius: 6px;
-}
-
-.uni-modal__title {
-  font-weight: bold;
-}
-
-.uni-modal__bd {
-  padding-top: 50rpx;
-  padding-bottom: 50rpx;
-}
-
-.uni-modal__btn {
-  font-size: 16px;
-}
 </style>

+ 0 - 30
src/common/modules/mx-app-links-config.js

@@ -1,30 +0,0 @@
-export default {
-	sysAppLinks: {
-		'micro-video': {
-			Android: {
-				pname: 'com.mm.whiteboard'
-			},
-			iOS: {
-				//action: 'taobao://'
-				url: 'https://a.app.qq.com/o/simple.jsp?pkgname=com.mm.whiteboard&fromcase=40002'
-			},
-			failUrl: 'https://a.app.qq.com/o/simple.jsp?pkgname=com.mm.whiteboard&fromcase=40002'
-		},
-		'hsqk-dili': {
-			Android: {
-				pname: 'com.cnhsqk.dili'
-			},
-			iOS: {
-				action: 'com.cnhsqk.dili://'
-			}
-		},
-		'hsqk-lishi': {
-			Android: {
-				pname: 'com.cnhsqk.history.student'
-			},
-			iOS: {
-				action: 'com.cnhsqk.history.student://'
-			}
-		}
-	}
-}

+ 0 - 43
src/common/modules/mx-banner-config.js

@@ -1,43 +0,0 @@
-export default {
-	indexIndexBanner: '/static/home/home_banner.png',
-	indexParentBanner: '/static/images/home/img_parent_banner@2x.png',
-	indexElectiveBanner: '/static/images/elective/img_new_gaokao_elective@2x.jpg',
-	indexCareerBanner: '/static/images/career/banner.png',
-	indexEvalBanner: '/static/images/eval-center/img_cepingzhongxin@2x.png',
-	indexOne2OneBanner: '/static/images/one2one/img_one2one_banner.png',
-	indexLibraryBanner: '/static/images/personal-center/personal_datacenter_banner.png',
-	indexAllTestBanner: '',
-	indexTopicCenterBanner: '',
-	indexIndexHeaderOption: {
-
-	},
-	indexCareerHeaderOption: {
-		disableFloat: true,
-		lineSize: 4,
-		space: -30
-	},
-	indexLibraryHeaderOption: {
-		disableFloat: false,
-		lineSize: 3,
-		space: -50
-	},
-	indexEvalHeaderOption: {
-		disableFloat: false,
-		lineSize: 4,
-		space: -50
-	},
-	indexOne2OneHeaderOption: {
-		disableFloat: false,
-		lineSize: 4,
-		space: -50
-	},
-	indexTopicCenterHeaderOption: {
-		title: '个人导学'
-	},
-	indexAllTestHeaderOption: {
-		title: '我的测评'
-	},
-	indexPersonalAiHeaderOption: {
-		title: 'AI助学'
-	}
-}

+ 0 - 41
src/common/modules/mx-block-index-all-test-config.js

@@ -1,41 +0,0 @@
-import widgets from '@/common/mx-block-widgets.js'
-
-export default {
-  indexAllTestBlocks: [
-    {
-      ...widgets.electiveTest,
-      satisfyStoreGetters: ['false']
-    },
-    {
-      ...widgets.careerTest,
-      satisfyStoreGetters: ['false']
-    },
-    {
-      ...widgets.electiveGuide,
-      satisfyStoreGetters: ['false']
-    },
-    {
-      ...widgets.hollandGuide
-    },
-    {
-      ...widgets.mbtiGuide
-    },
-    {
-      ...widgets.multiwayGuide,
-      satisfyStoreGetters: ['false']
-    },
-    // {
-    //   ...widgets.mentalHealthGuide
-    // },
-    {
-      ...widgets.psychologyTest,
-      satisfyStoreGetters: ['isSenior', 'false'],
-      satisfyAny: false
-    },
-    {
-      ...widgets.studyTest,
-      satisfyStoreGetters: ['isSenior', 'false'],
-      satisfyAny: false
-    }
-  ]
-}

+ 0 - 17
src/common/modules/mx-block-index-config.js

@@ -1,17 +0,0 @@
-export default {
-	indexIndexBlocks: [
-		{
-			is: 'carousel-banner'
-		},
-		{
-			is: 'schedule-steps',
-			satisfyStoreGetters: ['false']
-		},
-		{
-			is: 'news-tab'
-		},
-		{
-			is: 'news-top'
-		}
-	]
-}

+ 0 - 25
src/common/modules/mx-business-config.js

@@ -1,25 +0,0 @@
-export default {
-    collegeDetailPopups: {
-        double: {
-            title: '双高院校',
-            description: '',
-            content: '教育部在2019年推出了科学针对高职专科大学的"双高计划",意在建设具有中国特色的高水平高职学校和专业。' +
-                '双高院校第一轮建设单位共计202所(含双高院校A档、B档、C档,高水平专业A档、B档、C档),被称为专科中的双一流大学,值得考生们在报考时优先考虑。'
-        },
-        star: {
-            title: '院校综合竞争力星级',
-            description: '(数据仅供参考)',
-            content: '说明:数据来源于2022-2023年高职院校金平果排行榜。(仅展示前50%)<br/>' +
-                '评价指标:由5个一级指标、34个二级指标和100多个观测点组成,比往年增加了立德树人、学风建设、思政课程、' +
-                '示范基地、产学合作、上年优势专业、优秀教材、人才培养质量、产教融合、创新创业、1+X证书试点、' +
-                '本科教育试点、论文质量影响等指标或数据观测点。'
-        },
-        major: {
-            title: '专业排名',
-            description: '(数据仅供参考)',
-            content: '<strong>数据说明</strong>:示例 <span class="f-red">1</span>/213<br/>' +
-                '"1"为当前院校此专业排名<br/>' +
-                '"213"为全国开设此专业的院校总数(仅展示前50%、前30所院校)'
-        }
-    }
-}

+ 0 - 54
src/common/modules/mx-menus-index-config.js

@@ -1,54 +0,0 @@
-export default {
-    indexIndexMenus: [{
-        name: '题库',
-        path: '/pages/topic-center/index/index',
-        icon: '/static/home/library@2x.png',
-        iconMode: 'heightFix',
-        satisfyStoreGetters: ['!isK9Sensitive'],
-        useSvg: false
-    }, {
-        name: '志愿填报',
-        path: '/pages/ie/portal',
-        icon: '/static/home/form@2x.png',
-        useSvg: false
-    }, {
-        name: '自我测评',
-        path: '/pages/test-center/index/index',
-        icon: '/static/home/test@2x.png',
-        useSvg: false
-    }, {
-        name: '资讯',
-        path: '/pages/news/index/index',
-        icon: '/static/home/news@2x.png',
-        satisfyStoreGetters: ['!isCultural'],
-        useSvg: false
-    }, {
-        name: '查位次',
-        path: '/pages/career/query-segment/query-segment',
-        icon: '/static/home/news@2x.png',
-        satisfyStoreGetters: ['isCultural'],
-        useSvg: false
-    }, {
-        name: '视频',
-        path: '/pages/video-center/index/index',
-        icon: '/static/home/video@2x.png',
-        iconMode: 'heightFix',
-        satisfyStoreGetters: ['!isK9Sensitive'],
-        useSvg: false
-    }, {
-        name: '院校库',
-        path: '/pages/college-library/index/index',
-        icon: '/static/home/college@2x.png',
-        useSvg: false
-    }, {
-        name: '专业库',
-        path: '/pages/major-library/index/index',
-        icon: '/static/home/major@2x.png',
-        useSvg: false
-    }, {
-        name: '职业库',
-        path: '/pages/vocation-library/index/index',
-        icon: '/static/home/vocation@2x.png',
-        useSvg: false
-    }]
-}

+ 0 - 36
src/common/modules/mx-menus-index-topic-center-config.js

@@ -1,36 +0,0 @@
-export default {
-    indexTopicCenterMenus: [{
-        name: '知识点练习',
-        path: '',
-        icon: '/static/topic-center/paper_base.png',
-        nextData: {type: 2, testType: 0, title: '知识点练习', subjectId: ''}
-    }, {
-        name: '必刷题',
-        path: '/pages/topic-center/paper-objective/paper-objective',
-        icon: '/static/topic-center/paper_history.png',
-        nextData: {type: 6, testType: 0, subjectId: '', subjectName: ''},
-        // excludeSubjects: ['语文', '数学', '英语'],
-        satisfyStoreGetters: ['isCultural']
-    }, {
-        name: '模拟试卷',
-        path: '/pages/topic-center/paper-entry/paper-entry',
-        icon: '/static/topic-center/paper_simulate.png',
-        nextData: {paperType: '模拟试卷', subjectId: ''},
-        satisfyStoreGetters: []
-    }, {
-        name: '收藏夹',
-        path: '/pages/topic-center/topic-collection/topic-collection',
-        icon: '/static/topic-center/paper_collection.png',
-        nextData: {title: '收藏夹', subjectId: ''}
-    }, {
-        name: '错题本',
-        path: '/pages/topic-center/wrong-book/wrong-book',
-        icon: '/static/topic-center/paper_error_book.png',
-        nextData: {subjectId: ''}
-    }, {
-        name: '做题记录',
-        path: '/pages/topic-center/paper-record/paper-record',
-        icon: '/static/topic-center/paper_record.png',
-        nextData: {subjectId: ''}
-    }]
-}

+ 0 - 119
src/common/modules/mx-menus-personal-center-config.js

@@ -1,119 +0,0 @@
-export default {
-    indexPersonalCenterMenus: [
-        {
-            category: "other",
-            name: "基本资料",
-            icon: "/static/personal/icon_jibenziliao@2x.png",
-            path: "/pagesOther/pages/personal-center/basic-info/basic-info",
-            satisfyStoreGetters: [],
-            satisfyAny: false,
-        },
-        {
-            category: "other",
-            name: "修改密码",
-            icon: "/static/personal/icon_password@2x.png",
-            path: "/pagesOther/pages/personal-center/change-pwd/change-pwd",
-            satisfyStoreGetters: [],
-            satisfyAny: false,
-        },
-        {
-            category: "usual",
-            name: "错题本",
-            icon: "/static/personal/wrong_book.png",
-            path: "/pagesOther/pages/topic-center/wrong-book/wrong-book",
-            iconClass: "icon40",
-            titleClass: "f14",
-            satisfyStoreGetters: ["!isK9Sensitive"],
-            satisfyAny: false,
-            nextData: {subjectId: ""},
-        },
-        {
-            category: "usual",
-            name: "做题记录",
-            icon: "/static/personal/test_records.png",
-            path: "/pagesOther/pages/topic-center/paper-record/paper-record",
-            iconClass: "icon40",
-            titleClass: "f14",
-            satisfyStoreGetters: ["!isK9Sensitive"],
-            satisfyAny: false,
-            nextData: {subjectId: ""},
-        },
-        {
-            category: "usual",
-            name: "测评报告",
-            icon: "/static/personal/test_report.png",
-            path: "/pagesOther/pages/test-center/list/list",
-            iconClass: "icon40",
-            titleClass: "f14",
-            satisfyStoreGetters: [],
-            satisfyAny: false,
-        },
-        {
-            category: "usual",
-            name: "我的收藏",
-            icon: "/static/personal/my_collected.png",
-            path: "/pagesOther/pages/personal-center/my-concerned/my-concerned",
-            iconClass: "icon40",
-            titleClass: "f14",
-            satisfyStoreGetters: [],
-            satisfyAny: false,
-        },
-        {
-            category: "usual",
-            name: "我的志愿表",
-            icon: "/static/personal/my_simulated.png",
-            path: "/pagesOther/pages/ie/entry-ai-list/entry-ai-list",
-            iconClass: "icon40",
-            titleClass: "f14",
-            satisfyStoreGetters: ["!isCultural"],
-            satisfyAny: false,
-        },
-        {
-            category: "usual",
-            name: "我的志愿表",
-            icon: "/static/personal/my_simulated.png",
-            path: "/pagesOther/pages/voluntary/list/list",
-            iconClass: "icon40",
-            titleClass: "f14",
-            satisfyStoreGetters: ["isCultural"],
-            satisfyAny: false,
-        },
-        {
-            category: "usual",
-            name: "绑定会员卡",
-            icon: "/static/personal/bind_card.png",
-            path: "/pagesOther/pages/personal-center/bind-card/bind-card",
-            iconClass: "icon40",
-            titleClass: "f14",
-            satisfyStoreGetters: [],
-            satisfyAny: false,
-        },
-        // {
-        //     category: "usual",
-        //     name: "客服电话",
-        //     icon: "/static/personal/custom_tel.png",
-        //     iconClass: "icon40",
-        //     titleClass: "f14",
-        //     path: "400-0313-985",
-        //     handler: (m) => uni.makePhoneCall({phoneNumber: m.path}),
-        //     satisfyStoreGetters: [],
-        //     satisfyAny: false,
-        // },
-        {
-            category: "other",
-            name: "常见问题",
-            icon: "question-circle",
-            type: "FAQ",
-            satisfyStoreGetters: [],
-            satisfyAny: false,
-        },
-        {
-            category: "other",
-            name: "新手教程",
-            icon: "play-circle",
-            path: "/pagesOther/pages/personal-center/help-video/help-video",
-            satisfyStoreGetters: ["false"],
-            satisfyAny: false,
-        },
-    ],
-};

+ 0 - 44
src/common/modules/mx-other-config.js

@@ -1,44 +0,0 @@
-export default {
-    ossFileBase: '',
-    cameraAuthTips:
-        "(1)系统将获取相机权限,用于拍照等场景\n(2)系统将获取外部存储(含相册)读取权限,用于选择图片等场景",
-    scoreLockingTips: "", //'现在是志愿填报正式阶段,请准确填入您的高考分数和科目,确认后不可再修改',
-    scoreLockedTips: "", //'*志愿填报正式阶段不能修改分数与科目',
-    scoreRuleTips: "", //'*出分前分数与科目可以多次输入,出分后只能输入一次,系统使用视频可以在个人中心的使用教程查看',
-    inlineSiteFunctionPaths: {
-        protocolPrivacy: "/protocol/mxjb_privacy_IE.html",
-        protocolUser: "/protocol/mxjb_user_IE.html",
-        previewPDF: "/pdfView/index.html?src=",
-        FAQ: "/FAQ/FAQ_IE.html",
-    },
-    fileHelper: {
-        supportPDFTypes: ["pdf"],
-        supportOfficeTypes: [
-            "doc",
-            "xls",
-            "ppt",
-            "pdf",
-            "docx",
-            "xlsx",
-            "pptx",
-        ],
-        supportImageTypes: ["png", "jpg", "jpeg"],
-        isPDF: function (url) {
-            return this.supportPDFTypes.some((type) => url.endsWith(type));
-        },
-        isOffice: function (url) {
-            return this.supportOfficeTypes.some((type) => url.endsWith(type));
-        },
-        isImage: function (url) {
-            return this.supportImageTypes.some((type) => url.endsWith(type));
-        },
-        isFile: function (url) {
-            return this.isPDF(url) || this.isOffice(url);
-        },
-        getFileType: function (url) {
-            const lastDotIdx = url.lastIndexOf(".");
-            const suffix = url.substring(lastDotIdx + 1);
-            return suffix;
-        },
-    },
-};

+ 0 - 78
src/common/modules/mx-tiny-buttons-config.js

@@ -1,78 +0,0 @@
-export default {
-	sysRightItem: {
-		filter: {
-			icon: 'filter', // font-icon
-			text: '筛选',
-		},
-		about: {
-			icon: 'navigate',
-			text: ''
-		},
-		batch: {
-			icon: 'settings',
-			text: '批量'
-		},
-		plus: {
-			icon: 'paperplane',
-			text: '发送'
-		},
-		settings: {
-			icon: 'gear',
-			text: '设置'
-		},
-		history: {
-			icon: 'outline',
-			text: '历史'
-		},
-		add: {
-			icon: 'plusempty',
-			text: '新建'
-		},
-		refresh: {
-			icon: 'reload',
-			text: '刷新'
-		},
-		download: {
-			icon: 'download',
-			text: '下载'
-		},
-		edit: {
-			icon: 'compose',
-			text: '编辑'
-		}
-	},
-	sysSwipeAction: {
-		delete: {
-			text: '删除',
-			style: {
-				backgroundColor: 'var(--error-color)'
-			}
-		},
-		modify: {
-			text: '编辑',
-			style: {
-				backgroundColor: 'var(--primary-color)'
-			}
-		},
-		reply: {
-			text: '回复',
-			style: {
-				backgroundColor: 'var(--primary-color)'
-			}
-		}
-	},
-	sexOptions: [{
-		text: '男',
-		value: '0'
-	}, {
-		text: '女',
-		value: '1'
-	}],
-	yesOrNoOptions: [{
-		text: '是',
-		value: 1
-	}, {
-		text: '否',
-		value: 0
-	}]
-}

+ 0 - 628
src/common/mx-block-widgets.js

@@ -1,628 +0,0 @@
-import mxConst from '@/common/mxConst'
-
-export default {
-    // NOTE: 方便本地配置的时候重用
-
-    // 选科查询
-    selectCourseQuery: {
-        is: "mx-card-common",
-        gutter: 0,
-        lineSize: 1,
-        containerClasses: ['mb15'],
-        dataList: [{
-            height: '112px',
-            src: '/static/images/career/img_xuankechaxuxn.png',
-            path: '/pages/career/select-course/index'
-        }]
-    },
-    // 院校排名
-    collegeRanking: {
-        is: "mx-card-common",
-        gutter: 0,
-        lineSize: 1,
-        containerClasses: ['ml12 mr12 rd6'],
-        dataList: [{
-            height: '103px',
-            src: '/static/home/college-ranking@2x.png',
-            path: '/pages/college-library/index/index?data=' + JSON.stringify({
-                tab: 'rank'
-            })
-        }]
-    },
-    hollandGuide: {
-        is: "test-guide-card",
-        img: '/static/home/holland.png',
-        title: '专业倾向测评',
-        desc: 'Holland测评是一种基于职业兴趣的测评工具,根据被测者对六种职业兴趣类型的倾向性,提供对应的职业建议。',
-        bgColor: '#D8EDFD',
-        clazz: ['mx12'],
-        path: '/pagesOther/pages/test-center/holland/holland',
-        more: '/pagesOther/pages/test-center/list/list',
-        moreNext: {type: 'holland'}
-    },
-    mbtiGuide: {
-        is: "test-guide-card",
-        img: '/static/home/mbti.png',
-        title: '职业性格测评',
-        desc: 'MBTI测评是一种基于人格类型的测评工具,根据被测者对四种人格维度的偏好程度,判断其人格类型并提供相关建议。',
-        bgColor: '#D9E2FD',
-        clazz: ['mx12'],
-        path: '/pagesOther/pages/test-center/mbti/mbti',
-        more: '/pagesOther/pages/test-center/list/list',
-        moreNext: {type: 'mbti'}
-    },
-    multiwayGuide: {
-        is: "test-guide-card",
-        img: '/static/home/multiway-guide@2x.png',
-        title: '多元升学路径规划',
-        desc: '"多元录取"是新高考改革的核心,国家鼓励更多的学生能够结合自身的情况选择合适的升学路径,促进低分高就及科学的学业生涯规划。',
-        bgColor: '#75DDBD',
-        clazz: ['mx12'],
-        path: '/pagesOther/pages/career/multiway/multiway',
-        more: '/pagesOther/pages/career/multiway/history'
-    },
-    mentalHealthGuide: {
-        is: "test-guide-card",
-        img: '/static/home/mental-health.png',
-        title: '中学生心理健康诊断测验',
-        desc: '该测评是由华东师范大学心理学系教授周步成和其他心理学科研究人员,根据日本铃木清等人编制的"不安倾向诊断测验"进行修订,成为适应于我国中学学生标准化的《心理健康诊断测验》',
-        bgColor: '#E1FCDF',
-        clazz: ['mx12'],
-        path: '/pagesOther/pages/test-center/mental-health/mental-health',
-        more: '/pagesOther/pages/test-center/mental-health/history'
-    },
-    electiveGuide: {
-        is: "test-guide-card",
-        img: '/static/home/elective-guide@2x.png',
-        title: '选科测评',
-        desc: '新高考选科测评,从人生价值观、职业兴趣和知识兴趣三个维度,帮助你找到最适合的专业。',
-        bgColor: '#9595dd',
-        clazz: ['mx12'],
-        path: '/pagesOther/pages/elective/test/index/index'
-    },
-    // 选科测评
-    electiveTest: {
-        is: "mx-card-common",
-        lineSize: 1,
-        gutter: 0,
-        containerClasses: ['pl15', 'pr15', 'mb15'],
-        itemClasses: ['rd8', 'relative'],
-        header: {
-            title: '选科测评',
-            icon: '/static/images/elective/elective_block_select_icon.png',
-        },
-        titleBinder: {
-            customStyle: {
-                ...mxConst.commonStyle.blockFloatTitle,
-                color: '#336699'
-            }
-        },
-        titleMapper: {
-            'name': 'text'
-        },
-        descBinder: {
-            customStyle: {
-                ...mxConst.commonStyle.blockFloatDesc,
-                color: '#ff9966',
-                opacity: 1,
-                fontSize: '14px',
-                fontFamily: 'PingFangSC-Regular, PingFang SC',
-            }
-        },
-        descMapper: {
-            'desc': 'text'
-        },
-        dataList: [{
-            name: '',
-            desc: '',
-            src: '/static/images/elective/elective_block_test.png',
-            path: '/pages/elective/test/index/index',
-        }]
-    },
-    // 心理测评
-    psychologyTest: {
-        is: "mx-card-common",
-        lineSize: 1,
-        gutter: 0,
-        containerClasses: ['pl15', 'pr15', 'mb15'],
-        itemClasses: ['rd8', 'relative'],
-        header: {
-            title: '心理测评',
-            icon: '/static/images/elective/elective_block_select_icon.png',
-        },
-        titleBinder: {
-            customStyle: {
-                ...mxConst.commonStyle.blockFloatTitle,
-                color: '#336699'
-            }
-        },
-        titleMapper: {
-            'name': 'text'
-        },
-        dataList: [{
-            name: '心理测评',
-            src: '/static/images/elective/elective_block_test.png',
-            path: '/pages/elective/test/index/index',
-        }]
-    },
-    // 学业测评
-    studyTest: {
-        is: "mx-card-common",
-        lineSize: 1,
-        gutter: 0,
-        containerClasses: ['pl15', 'pr15', 'mb15'],
-        itemClasses: ['rd8', 'relative'],
-        header: {
-            title: '学业测评',
-            icon: '/static/images/elective/elective_block_select_icon.png',
-        },
-        titleBinder: {
-            customStyle: {
-                ...mxConst.commonStyle.blockFloatTitle,
-                color: '#336699'
-            }
-        },
-        titleMapper: {
-            'name': 'text'
-        },
-        dataList: [{
-            name: '学业测评',
-            src: '/static/images/elective/elective_block_test.png',
-            path: '/pages/elective/test/index/index',
-        }]
-    },
-    // 学科测评入口
-    evaluationSummary: {
-        is: 'mx-index-eval-summary',
-        categories: function (vm, config) {
-            return vm.storeGetterFilter(config.studentEvaluationTabs)
-        }
-    },
-    // 同步视频
-    videoSync: {
-        is: 'mx-card-video-sync',
-        lineSize: 2,
-        itemClasses: ['pl8', 'pr8', 'pt3', 'pb10', 'card-shadow'],
-        header: {
-            title: '视频课程',
-            icon: "/static/images/home/icon_mingshijingjiang@2x.png",
-            moreText: '所有视频>>',
-            path: '/pages/video-center/index/index'
-        },
-        imgMapper: {
-            'img': 'src'
-        },
-        titleBinder: {
-            // 不要使用lineHeight,手机端在单行文本时不兼容,改用margin控制
-            margin: '10px 0 5px 0',
-            lines: 1,
-            customStyle: mxConst.commonStyle.blockNormalTitle
-        },
-        titleMapper: {
-            'section_name': 'text'
-        },
-        dataList: []
-    },
-    // 热门专业
-    hotMajor: {
-        is: 'MxCardHotMajor',
-        lineSize: 2,
-        class: ['bg-white rd6 ml12 mr12'],
-        header: {
-            title: '热门专业'
-        },
-        imgBinder: {
-            height: '105px'
-        },
-        dataList: []
-    },
-    // 生涯视频
-    videoCareer: {
-        is: 'mx-card-video-career',
-        ref: 'careerVideo',
-        lineSize: 2,
-        class: ['bg-white', 'rd6', 'ml12', 'mr12'],
-        itemClasses: [],
-        header: {
-            title: '生涯课程',
-            // icon: "/static/images/home/icon_video@2x.png",
-            moreText: '查看全部 >',
-            path: '/pages/career/index/career-list?type=1'
-        },
-        imgBinder: {
-            classes: 'rd4'
-        },
-        imgMapper: {
-            'pictUrl': 'src'
-        },
-        titleBinder: {
-            // 不要使用lineHeight,手机端在单行文本时不兼容,改用margin控制
-            margin: '10px 0 5px 0',
-            lines: 1,
-            customStyle: mxConst.commonStyle.blockNormalTitle
-        },
-        titleMapper: {
-            'name': 'text'
-        },
-        descBinder: {
-            lines: 1,
-            prefixIcon: 'eye',
-            customStyle: mxConst.commonStyle.blockNormalDesc
-        },
-        descMapper: {
-            'plays': 'text'
-        },
-        dataList: []
-    },
-    gkzx: {
-        is: 'mx-card-gkzx',
-        lineSize: 1,
-        itemClasses: ['pl8', 'pr8', 'pt3', 'pb10', 'bd-b-1'],
-        header: {
-            title: '高考政策',
-            icon: "/static/images/home/icon_video@2x.png",
-            moreText: '更多>>',
-            path: '/pages/career/volunteer/info/list?type=高考政策'
-        },
-        titleBinder: {
-            // 不要使用lineHeight,手机端在单行文本时不兼容,改用margin控制
-            margin: '10px 0 5px 0',
-            lines: 1,
-            customStyle: mxConst.commonStyle.blockNormalTitle
-        },
-        titleMapper: {
-            'title': 'text'
-        },
-        dataList: []
-    },
-    videoGksp: {
-        is: 'mx-card-video-gksp',
-        lineSize: 2,
-        class: ['bg-white', 'rd6', 'ml12', 'mr12'],
-        itemClasses: [],
-        header: {
-            title: '高考政策',
-            // icon: "/static/images/home/icon_video@2x.png",
-            moreText: '查看全部 >',
-            path: '/pages/career/volunteer/new-gkvideo?type=高考政策'
-        },
-        imgBinder: {
-            mode: '',
-            width: '100%',
-            src: '/static/images/home/img_shegnyaguanli@2x.png' //default value
-        },
-        imgMapper: {
-            'coverUrl': 'src'
-        },
-        titleBinder: {
-            // 不要使用lineHeight,手机端在单行文本时不兼容,改用margin控制
-            margin: '10px 0 5px 0',
-            lines: 1,
-            customStyle: mxConst.commonStyle.blockNormalTitle
-        },
-        titleMapper: {
-            'title': 'text'
-        },
-        dataList: []
-    },
-    // 微课视频
-    videoMicro: {
-        is: 'mx-card-video-micro',
-        lineSize: 2,
-        itemClasses: ['pl8', 'pr8', 'pt3', 'pb10', 'card-shadow'],
-        header: {
-            title: '微课视频',
-            icon: "/static/images/home/icon_video@2x.png",
-            moreText: '更多>>',
-            path: '/pages/eval-center/personal-resource/index/index'
-        },
-        imgBinder: {
-            mode: '',
-            src: '/static/images/home/img_shegnyaguanli@2x.png' //default value
-        },
-        imgMapper: {
-            'resourcesCoverUrl': 'src'
-        },
-        titleBinder: {
-            // 不要使用lineHeight,手机端在单行文本时不兼容,改用margin控制
-            margin: '10px 0 5px 0',
-            lines: 1,
-            customStyle: mxConst.commonStyle.blockNormalTitle
-        },
-        titleMapper: {
-            'resourcesName': 'text'
-        },
-        dataList: []
-    },
-    // 生涯测评
-    careerTest: {
-        is: 'mx-card-common',
-        lineSize: 2,
-        itemClasses: ['rd4'],
-        header: {
-            title: '生涯测评',
-            icon: '/static/images/home/icon_shengyaceping@2x.png',
-            moreText: '测评记录>>',
-            path: '/pagesOther/pages/test-center/list/list'
-        },
-        dataList: [{
-            src: '/static/images/home/img_zhuanyexingquceping@2x.png',
-            path: '/pagesOther/pages/test-center/holland/holland'
-        }, {
-            src: '/static/images/home/img_zhiyexingquceping@2x.png',
-            path: '/pagesOther/pages/test-center/mbti/mbti'
-        }],
-    },
-    // 智能练习
-    intelligentExercise: {
-        is: 'mx-card-common',
-        lineSize: 2,
-        itemClasses: ['rd4'],
-        header: {
-            title: '智能练习',
-            icon: "/static/images/home/home_icon_tikuzhongxin@2x.png",
-        },
-        dataList: [{
-            mode: '',
-            src: '/static/images/home/img_tongbuzaixianlianxi@2x.png',
-            path: '/pages/topic-center/sync-online/sync-online?tabIndex=0'
-        }, {
-            mode: '',
-            src: '/static/images/home/img_zhishidianzaixianlianxi@2x.png',
-            path: '/pages/topic-center/sync-online/sync-online?tabIndex=1'
-        }],
-    },
-    paperWork: {
-        is: "mx-card-common",
-        lineSize: 1,
-        gutter: 0,
-        containerClasses: ['pl15', 'pr15', 'mb15'],
-        itemClasses: ['rd8', 'relative'],
-        header: {
-            title: '在线作业',
-            icon: '/static/images/topic-center/topic_center_icon_homework.png',
-        },
-        titleBinder: {
-            customStyle: {
-                ...mxConst.commonStyle.blockFloatTitle,
-                color: '#608EDF'
-            }
-        },
-        titleMapper: {
-            'name': 'text'
-        },
-        descBinder: {
-            customStyle: {
-                ...mxConst.commonStyle.blockFloatDesc,
-                color: '#FFA400',
-                opacity: 1,
-                fontSize: '14px',
-                fontFamily: 'PingFangSC-Regular, PingFang SC',
-            }
-        },
-        descMapper: {
-            'desc': 'text'
-        },
-        dataList: [{
-            name: '在线作业/视频作业/试卷作业',
-            desc: 'HOMEWORK ONLINE',
-            src: '/static/images/topic-center/topic_center_block_homework.png',
-            path: '/pages/topic-center/homework/index',
-        }]
-    },
-    paperWorkManage: {
-        is: "mx-card-common",
-        lineSize: 1,
-        gutter: 0,
-        containerClasses: ['pl15', 'pr15', 'mb15'],
-        itemClasses: ['rd8', 'relative'],
-        header: {
-            title: '作业管理',
-            icon: '/static/images/topic-center/topic_center_icon_homework.png',
-        },
-        titleBinder: {
-            customStyle: {
-                ...mxConst.commonStyle.blockFloatTitle,
-                color: '#608EDF'
-            }
-        },
-        titleMapper: {
-            'name': 'text'
-        },
-        descBinder: {
-            customStyle: {
-                ...mxConst.commonStyle.blockFloatDesc,
-                color: '#FFA400',
-                opacity: 1,
-                fontSize: '14px',
-                fontFamily: 'PingFangSC-Regular, PingFang SC',
-            }
-        },
-        descMapper: {
-            'desc': 'text'
-        },
-        dataList: [{
-            name: '发布作业/检查作业',
-            desc: 'HOMEWORK MANAGEMENT',
-            src: '/static/images/topic-center/topic_center_block_homework.png',
-            path: '/pages/topic-center/homework/manage',
-        }]
-    },
-    qualityPaper: {
-        is: 'mx-card-common',
-        lineSize: 2,
-        itemClasses: ['rd4'],
-        header: {
-            title: '精品试卷',
-            icon: "/static/images/home/icon_jingpinshijuan@2x.png",
-        },
-        dataList: [{
-            mode: '',
-            src: '/static/images/home/img_mingxiao@2x.png',
-            path: '/pages/topic-center/best-paper/index/index?tabIndex=0'
-        }, {
-            mode: '',
-            src: '/static/images/home/IMG_linianzhent@2x.png',
-            path: '/pages/topic-center/best-paper/index/index?tabIndex=1'
-        }]
-    },
-    // 高考志愿
-    simulatedVolunteer: {
-        is: "mx-card-common",
-        lineSize: 1,
-        gutter: 0,
-        itemClasses: ['rd8', 'rel'],
-        header: {
-            title: '高考志愿',
-            icon: '/static/images/career/icon_zhiyuan.png',
-        },
-        titleBinder: {
-            customStyle: mxConst.commonStyle.blockFloatTitle
-        },
-        titleMapper: {
-            'name': 'text'
-        },
-        descBinder: {
-            customStyle: mxConst.commonStyle.blockFloatDesc
-        },
-        descMapper: {
-            'desc': 'text'
-        },
-        dataList: [{
-            name: '志愿填报',
-            desc: 'SIMULATED VOLUNTEER',
-            src: '/static/images/career/img_zhiyuantianbao.png',
-            path: '/pages/voluntary/index/index',
-        }]
-    },
-    // 高考志愿F4
-    simulatedVolunteerF4: {
-        is: "mx-card-common",
-        lineSize: 4,
-        itemClasses: ['fx-cen-cen'],
-        imgBinder: {
-            width: '64px',
-            height: '64px',
-        },
-        titleBinder: {
-            margin: '10px 0 0 0',
-            lines: 1
-        },
-        titleMapper: {
-            'name': 'text'
-        },
-        dataList: [{
-            name: '招生计划',
-            src: '/static/images/career/icon_gaokaomingci.png',
-            path: '/pages/career/volunteer/plan/index'
-        }, {
-            name: '批次控制线',
-            src: '/static/images/career/icon_kongzhixian.png',
-            path: '/pages/career/volunteer/batch'
-        }, {
-            name: '一分一段',
-            src: '/static/images/career/icon_yifenyiduan.png',
-            path: '/pages/career/volunteer/yfyd'
-        }, {
-            name: '投档线',
-            src: '/static/images/career/icon_toudangxian.png',
-            path: '/pages/career/volunteer/admission-line'
-        }]
-    },
-    // 3库
-    collegeMajorVocation: {
-        is: "mx-card-common",
-        lineSize: 3,
-        itemClasses: ['fx-cen-cen'],
-        header: {
-            title: "院校专业",
-            icon: "/static/images/career/icon_star.png",
-        },
-        imgBinder: {
-            width: '64px',
-            height: '64px',
-        },
-        titleBinder: {
-            margin: '10px 0 0 0',
-            lines: 1
-        },
-        titleMapper: {
-            'name': 'text'
-        },
-        dataList: [{
-            name: '院校库',
-            src: '/static/images/career/icon_yuanxiaoku.png',
-            path: '/pages/college-library/index/index'
-        }, {
-            name: '专业库',
-            src: '/static/images/career/icon_zhuanyeku.png',
-            path: '/pages/major-library/index/index'
-        }, {
-            name: '职业库',
-            src: '/static/images/career/icon_zhiyeku.png',
-            path: '/pages/vocation-library/index/index'
-        }]
-    },
-    // 大数据选科
-    elective: {
-        is: "mx-card-common",
-        lineSize: 2,
-        itemClasses: ['fx-cen-cen'],
-        header: {
-            title: "大数据选科",
-            icon: "/static/images/career/icon_star.png",
-        },
-        imgBinder: {
-            width: '96px',
-            height: '96px',
-        },
-        titleBinder: {
-            margin: '10px 0 0 0',
-            customStyle: mxConst.commonStyle.blockNormalTitle
-        },
-        titleMapper: {
-            'name': 'text'
-        },
-        dataList: function (vm, config) {
-            return vm.storeGetterFilter(config.indexElectiveBlockOptions)
-        }
-    },
-    // 精准ai助学
-    PreciseAi: {
-        is: 'mx-card-common',
-        lineSize: 2,
-        itemClasses: ['rd4'],
-        header: {
-            title: '精准AI助学',
-            icon: "/static/images/home/home_icon_tikuzhongxin@2x.png",
-        },
-        dataList: [{
-            mode: '',
-            src: '/static/images/home/precise_micro.png',
-            path: '/pages/eval-center/ai/preciseAi/microVideo/index'
-        }, {
-            mode: '',
-            src: '/static/images/home/precise_pratise.png',
-            path: '/pages/eval-center/ai/preciseAi/practise/index'
-        }],
-    },
-    // 普通ai助学
-    CommonAi: {
-        is: 'mx-card-common',
-        lineSize: 2,
-        itemClasses: ['rd4'],
-        header: {
-            title: '普通AI助学',
-            icon: "/static/images/home/home_icon_tikuzhongxin@2x.png",
-        },
-        dataList: [{
-            mode: '',
-            src: '/static/images/home/common_micro.png',
-            path: '/pages/eval-center/ai/commonAi/microVideo/index'
-        }, {
-            mode: '',
-            src: '/static/images/home/common_pratise.png',
-            path: '/pages/eval-center/ai/commonAi/practise/index'
-        }],
-    }
-}

+ 0 - 15
src/common/mxConfig.js

@@ -1,15 +0,0 @@
-const config = {}
-
-// #ifdef VUE3
-const configModules = import.meta.glob('./modules/*.js')
-const tasks = Object.values(configModules).map(m => m().then(c => Object.assign(config, c.default)))
-Promise.all(tasks).then()
-// #endif
-
-// #ifndef VUE3
-const configModules = require.context('./modules', false, /\.js$/)
-const configs = configModules.keys().map(key => configModules(key).default)
-configs.forEach(module => Object.assign(config, module))
-// #endif
-
-export default config

+ 0 - 210
src/common/mxConst.js

@@ -1,210 +0,0 @@
-const consts = {
-    keyToken: 'mx-token',
-    keyUserInfo: 'mx-userInfo',
-    keyAppConfig: 'mx-appConfig',
-    keyCacheUniquePrefix: 'mx-transfer-cache-',
-    keyTheme: 'mx-theme',
-    keyParentIdentifier: 'from=wechat',
-    keyGuideRead: 'mx-guideRead-v1',
-    keyUserAgreed: 'mx-userAgreed',
-    keyDisplayPermission: 'mx-displayPermission',
-    keyOutTradeNo: 'mx-outTradeNo',
-    keyCulturalExamType: '职高对口升学',
-    propAppConfig: 'appConfig',
-    routes: {
-        index: {url: '/pagesMain/pages/index/index', type: 'tab'},
-        portal: {url: '/pages/ie/portal', type: 'tab'},
-        personalCenter: {url: '/pages/personal-center/index/index', type: 'tab'},
-        login: '/pagesSystem/pages/login/login',
-        register: '/pages/login/register',
-        activate: '/pages/personal-center/bind-card/bind-card',
-        forgetPwd: '/pages/login/forget-pwd',
-        guide: {url: '/pages/index/guide', animationType: 'pop-in'},
-        bindMobile: {url: '/pages/login/bind-mobile', animationType: 'pop-in'},
-        bindProfile: '/pages/login/bind-profile',
-        setting: '/pages/personal-center/setting/setting',
-        basicInfo: '/pages/personal-center/basic-info/basic-info',
-        newsIndex: '/pages/news/index/index',
-        newsDetail: '/pagesOther/pages/news/detail/detail',
-        newsGroup: '/pagesOther/pages/news/group/group',
-        videoPlay: '/pagesOther/pages/video-center/play/play'
-    },
-    globalEvents: {
-        paperCompleted: 'globalEvents-paperCompleted',
-        collegeSelected: 'globalEvents-collegeSelected',
-        aiFilterShortcutClear: 'globalEvents-aiFilterShortcutClear',
-        voluntaryChanged: 'globalEvents-voluntaryChanged'
-    },
-    serverErrors: {
-        400: '请求无效 (Bad request)',
-        401: '操作未授权,请登陆',
-        402: '登陆凭据失效,请重新登陆',
-        403: '没有权限访问',
-        404: '请求资源不存在',
-        405: '权限不足,请开通VIP'
-    },
-    commonStyle: {
-        blockFloatTitle: {
-            position: 'absolute',
-            color: '#fff',
-            fontSize: '18px',
-            fontFamily: 'PingFangSC-Medium, PingFang SC',
-            left: '15px',
-            top: '15px'
-        },
-        blockFloatDesc: {
-            position: 'absolute',
-            color: '#fff',
-            fontSize: '18px',
-            fontFamily: 'PingFangSC-Medium, PingFang SC',
-            left: '15px',
-            top: '40px',
-            opacity: 0.5
-        },
-        blockNormalTitle: {
-            color: '#666666',
-            fontSize: '14px',
-            fontFamily: 'PingFangSC-Medium, PingFang SC',
-        },
-        blockNormalDesc: {
-            color: '#999999',
-            fontSize: '12px',
-            fontFamily: 'PingFangSC-Regular, PingFang SC'
-        }
-    },
-    enum: {
-        scoreLock: {
-            // 待锁定状态
-            locking: -1,
-            // 正常状态
-            unlock: 0,
-            // 已锁定状态
-            locked: 1
-        },
-        /// @description 心理健康测评
-        mentalHealthTestType: 4,
-        brochureType: {
-            introduction: 1,
-            enrollRule: 2,
-            examTime: 3,
-            work: 4,
-            otherRule: 5
-        },
-        ai: {
-            voluntaryType: {
-                ai: 'AI',
-                multiple: 'Multiple'
-            },
-            renderType: {
-                def: 0,
-                ai: 1,
-                calculate: 2
-            },
-            inputType: {
-                text: 'Text', // 普通文本,一般情况下输入均使用此类。数值也可以使用此类型,结合校验规则使用
-                score: 'Score', // AI的特殊输入类型,带分制的分数 // 此类输入之后可能涉及输入条件动态变化,NOTE:后面再考虑这种情况。
-                number: 'Number', // 数值,会限制键盘,但因为小数点之类的键盘并不太好控制,所以如果能不使用还是使用Text
-                radio: 'Radio', // radio单选。如果固定2个,用radio,否则用picker。一般超过3个会折行,影响美观
-                picker: 'Picker', // picker单选,一般使用Picker做单选。
-                checkbox: 'Checkbox', // 多选
-                eyesight: 'Eyesight' // 视力,特殊项,前端自己生成了选项
-            },
-            pickType: {
-                all: {
-                    text: '全部',
-                    value: 'All'
-                },
-                danger: {
-                    text: '冲刺型',
-                    value: 'Danger'
-                },
-                normal: {
-                    text: '稳妥型',
-                    value: 'Normal'
-                },
-                safety: {
-                    text: '保守型',
-                    value: 'Safety'
-                }
-            },
-            pickEmpty: {
-                enrollPass: {
-                    text: '无概率',
-                    value: '0'
-                },
-                new: {
-                    text: '新增',
-                    value: '1'
-                }
-            },
-            ruleCategory: {
-                none: {
-                    text: '未知',
-                    value: 'None'
-                },
-                enroll: {
-                    text: '录取规则',
-                    formText: '填写成绩',
-                    value: 'Enroll'
-                },
-                special: {
-                    text: '特殊要求',
-                    formText: '其它信息',
-                    value: 'Special'
-                }
-            },
-            ruleType: {
-                none: 'None', // 缺省类型
-                scoreTotal: 'ScoreTotal', // 总分,综合分
-                scoreUnion: 'ScoreUnion', // 学考,文化成绩
-                scoreBase: 'ScoreBase', // 校考,非学考,文化成绩
-                scoreSkill: 'ScoreSkill', // 技能分,非文化成绩
-                scoreSingle: 'ScoreSingle', // 单科成绩
-                special: 'Special', // 附加要求
-                readonly: 'Readonly', // 只读项,只展示,不参与判定
-                other: 'Other' // 未知项
-            }
-        },
-        simulatePickTypes: [{
-            text: '冲刺型',
-            value: '0'
-        }, {
-            text: '稳妥型',
-            value: '1'
-        }, {
-            text: '保守型',
-            value: '2'
-        }]
-    },
-    question: {
-        // 客观题类型
-        objectiveTypes: [1, 3, 6, 7],
-        // 客观题-单选类型
-        radioTypes: [1, 6, 7],
-        // 客观题-多选类型
-        checkboxTypes: [3],
-        // 转方法
-        isObjective: function (typeId) {
-            return this.objectiveTypes.some(t => t == typeId)
-        },
-        isRadio: function (typeId) {
-            return this.radioTypes.some(t => t == typeId)
-        },
-        isCheckbox: function (typeId) {
-            return this.checkboxTypes.some(t => t == typeId)
-        }
-    },
-    scrollIntoOption: {
-        block: 'center',
-        behavior: 'smooth'
-    },
-    recommendMajorSortFn: (a, b) => a.localPriority - b.localPriority
-}
-
-export function getTabRoutes() {
-    return Object.values(consts.routes)
-        .filter(r => r.type == 'tab')
-        .map(r => r.url)
-}
-
-export default consts

+ 0 - 1
src/components/ie-dict/ie-dict.vue

@@ -30,6 +30,5 @@ const dictLabels = computed(() => {
   }
   return dictStore.getDictLabel(props.dictName, props.dictValue);
 });
-dictStore.loadDict(props.dictName);
 </script>
 <style lang="scss" scoped></style>

+ 5 - 1
src/components/ie-picker/ie-picker.vue

@@ -41,7 +41,11 @@
   <!-- #endif -->
 </template>
 <script lang="ts" setup>
-
+defineOptions({
+  options: {
+    virtualHost: true
+  }
+});
 const modelValue = defineModel<string | number>('modelValue', {
   default: ''
 });

+ 0 - 218
src/components/m-drag/m-drag-vue2.vue

@@ -1,218 +0,0 @@
-<template>
-  <scroll-view class="m-drag" scroll-y :style="{ height: itemHeight * newList.length + 'px' }">
-    <view
-      v-for="(item, index) in newList"
-      :key="index"
-      class="m-drag-item"
-      :class="{ active: currentIndex === index }"
-      :style="{
-        top: itemYList[index].top + 'px'
-      }"
-    >
-      <slot :item="item" />
-      <view class="icon" @touchstart="touchStart($event, index)" @touchmove="touchMove" @touchend="touchEnd">
-        <i class="lines" />
-      </view>
-    </view>
-  </scroll-view>
-</template>
-<script>
-  export default {
-    props: {
-      // 每一项item高度
-      itemHeight: {
-        type: Number,
-        required: true
-      },
-      // 数据列表
-      list: {
-        type: Array,
-        required: true
-      },
-      // 是否只读
-      readonly: {
-        type: Boolean,
-        default: false
-      }
-    },
-    data() {
-      return {
-        // 数据
-        newList: [],
-        // 记录所有item的初始坐标
-        initialItemYList: [],
-        // 坐标数据
-        itemYList: [],
-        // 记录当前手指的垂直方向的坐标
-        touchY: 0,
-        // 记录当前操作的item数据
-        currentItemY: {},
-        // 当前操作的item的下标
-        currentIndex: -1
-      }
-    },
-    watch: {
-      list: {
-        handler(val) {
-          if (!val?.length) return
-          // 获取数据列表
-          this.newList = val
-          // 获取所有item的初始坐标
-          this.initialItemYList = this.getItemsY()
-          // 初始化坐标
-          this.itemYList = this.getItemsY()
-        },
-        immediate: true
-      }
-    },
-    created() {},
-    methods: {
-      /** @初始化各个item的坐标 **/
-      getItemsY() {
-        return this.list.map((item, i) => {
-          return {
-            left: 0,
-            top: i * this.itemHeight
-          }
-        })
-      },
-      /** @开始触摸 */
-      touchStart(event, index) {
-        // 只读
-        if (this.readonly) return
-        // H5拖拽时,禁止触发ios回弹
-        this.h5BodyScroll(false)
-        const [{ pageY }] = event.touches
-
-        // 记录数据
-        this.currentIndex = index
-        this.touchY = pageY
-        this.currentItemY = this.itemYList[index]
-      },
-      /** @手指滑动 **/
-      touchMove(event) {
-        // 只读
-        if (this.readonly) return
-        const [{ pageY }] = event.touches
-        const current = this.itemYList[this.currentIndex]
-        const prep = this.itemYList[this.currentIndex - 1]
-        const next = this.itemYList[this.currentIndex + 1]
-        // 获取移动差值
-        this.itemYList[this.currentIndex] = {
-          top: current.top + (pageY - this.touchY)
-        }
-        // 记录手指坐标
-        this.touchY = pageY
-        // 向下移动(超过下一个的1/2就进行换位)
-        if (next && current.top > next.top - this.itemHeight / 2) {
-          this.changePosition(this.currentIndex + 1)
-        } else if (prep && current.top < prep.top + this.itemHeight / 2) {
-          // 向上移动(超过上一个的1/2就进行换位)
-          this.changePosition(this.currentIndex - 1)
-        }
-      },
-      /** @手指松开 */
-      touchEnd() {
-        // 只读
-        if (this.readonly) return
-        // 传给父组件新数据
-        this.$emit('change', this.newList, this.newList[this.currentIndex])
-        // 将拖拽的item归位
-        this.itemYList[this.currentIndex] = this.initialItemYList[this.currentIndex]
-        this.currentIndex = -1
-        // H5开启ios回弹
-        this.h5BodyScroll(true)
-      },
-      /** @交换位置 **/
-      // index 需要与第几个下标交换位置
-      changePosition(index) {
-        // 记录当前拖拽的item数据
-        const tempItem = this.newList[this.currentIndex]
-        // 设置原来位置的item
-        this.newList[this.currentIndex] = this.newList[index]
-        // 将临时存放的数据设置好
-        this.newList[index] = tempItem
-
-        // 调整位置item
-        this.itemYList[index] = this.itemYList[this.currentIndex]
-        this.itemYList[this.currentIndex] = this.currentItemY
-
-        // 改变当前操作的的下标
-        this.currentIndex = index
-
-        // 记录新位置的数据
-        this.currentItemY = this.initialItemYList[this.currentIndex]
-      },
-      // h5 ios回弹
-      h5BodyScroll(flag) {
-        // #ifdef H5
-        document.body.style.overflow = flag ? 'initial' : 'hidden'
-        // #endif
-      }
-    }
-  }
-</script>
-
-<style scoped lang="scss">
-  .m-drag {
-    position: relative;
-    width: 100%;
-    ::-webkit-scrollbar {
-      display: none;
-    }
-    .m-drag-item {
-      position: absolute;
-      left: 0;
-      right: 0;
-      transition: all ease 0.25s;
-      display: flex;
-      align-items: center;
-      > :deep(view:not(.icon)) {
-        flex: 1;
-      }
-      .icon {
-        padding: 30rpx;
-        .lines {
-          background: #e0e0e0;
-          width: 20px;
-          height: 2px;
-          border-radius: 100rpx;
-          margin-left: auto;
-          position: relative;
-          display: block;
-          transition: all ease 0.25s;
-          &::before,
-          &::after {
-            position: absolute;
-            width: inherit;
-            height: inherit;
-            border-radius: inherit;
-            background: #e0e0e0;
-            transition: inherit;
-            content: '';
-            display: block;
-          }
-          &::before {
-            top: -14rpx;
-          }
-          &::after {
-            bottom: -14rpx;
-          }
-        }
-      }
-      // 拖拽中的元素,添加阴影、关闭动画、层级置顶
-      &.active {
-        box-shadow: 0 0 14rpx rgba(0, 0, 0, 0.08);
-        transition: initial;
-        z-index: 1;
-        .icon .lines {
-          background: #2e97f9;
-          &::before,
-          &::after {
-            background: #2e97f9;
-          }
-        }
-      }
-    }
-  }
-</style>

+ 0 - 239
src/components/m-drag/m-drag.vue

@@ -1,239 +0,0 @@
-<template>
-    <scroll-view class="m-drag" scroll-y :style="{ height: itemHeight * state.newList.length + 'px' }">
-        <view
-            v-for="(item, index) in state.newList"
-            :key="index"
-            class="m-drag-item"
-            :class="{ active: state.currentIndex === index }"
-            :style="{ top: state.itemYList[index].top + 'px' }">
-            <slot v-bind="{item, index}"/>
-            <view class="icon" @touchstart="touchStart($event, index)" @touchmove="touchMove" @touchend="touchEnd">
-                <i class="lines"/>
-            </view>
-        </view>
-    </scroll-view>
-</template>
-
-<script setup>
-import {reactive, watch, toRefs} from 'vue';
-
-const emits = defineEmits(['change'])
-const props = defineProps({
-    // 每一项item高度
-    itemHeight: {
-        type: Number,
-        required: true
-    },
-    // 数据列表
-    list: {
-        type: Array,
-        required: true
-    },
-    // 是否只读
-    readonly: {
-        type: Boolean,
-        default: false
-    }
-})
-
-const state = reactive({
-    // 数据
-    newList: [],
-    // 记录所有item的初始坐标
-    initialItemYList: [],
-    // 坐标数据
-    itemYList: [],
-    // 记录当前手指的垂直方向的坐标
-    touchY: 0,
-    // 记录当前操作的item数据
-    currentItemY: {},
-    // 当前操作的item的下标
-    currentIndex: -1,
-    // 正在拖拽
-    dragging: false
-})
-
-watch(
-    () => props.list,
-    (val) => {
-        if (!val?.length) return
-        // 获取数据列表
-        state.newList = [...val] // copy array
-        // 获取所有item的初始坐标
-        state.initialItemYList = getItemsY()
-        // 初始化坐标
-        state.itemYList = getItemsY()
-    },
-    {
-        immediate: true
-    }
-)
-
-/** @初始化各个item的坐标 **/
-function getItemsY() {
-    return props.list.map((item, i) => {
-        return {
-            left: 0,
-            top: i * props.itemHeight
-        }
-    })
-}
-
-/** @开始触摸 */
-function touchStart(event, index) {
-    // 只读
-    if (props.readonly) return
-    // H5拖拽时,禁止触发ios回弹
-    h5BodyScroll(false)
-    const [{pageY}] = event.touches
-
-    // 记录数据
-    state.dragging = true
-    state.currentIndex = index
-    state.touchY = pageY
-    state.currentItemY = state.itemYList[index]
-}
-
-/** @手指滑动 **/
-function touchMove(event) {
-    // 只读
-    if (props.readonly) return
-    const [{pageY}] = event.touches
-    const current = state.itemYList[state.currentIndex]
-    const prep = state.itemYList[state.currentIndex - 1]
-    const next = state.itemYList[state.currentIndex + 1]
-    // 获取移动差值
-    state.itemYList[state.currentIndex] = {
-        top: current.top + (pageY - state.touchY)
-    }
-    // 记录手指坐标
-    state.touchY = pageY
-    // 向下移动(超过下一个的1/2就进行换位)
-    if (next && current.top > next.top - props.itemHeight / 2) {
-        changePosition(state.currentIndex + 1)
-    } else if (prep && current.top < prep.top + props.itemHeight / 2) {
-        // 向上移动(超过上一个的1/2就进行换位)
-        changePosition(state.currentIndex - 1)
-    }
-}
-
-/** @手指松开 */
-function touchEnd() {
-    // 只读
-    if (props.readonly) return
-    // 传给父组件新数据
-    emits('change', state.newList, props.list, state.newList[state.currentIndex])
-    // 将拖拽的item归位
-    state.itemYList[state.currentIndex] = state.initialItemYList[state.currentIndex]
-    state.currentIndex = -1
-    // H5开启ios回弹
-    h5BodyScroll(true)
-    state.dragging = false
-}
-
-/** @交换位置 **/
-// index 需要与第几个下标交换位置
-function changePosition(index) {
-    console.log(index)
-    // 记录当前拖拽的item数据
-    const tempItem = state.newList[state.currentIndex]
-    // 设置原来位置的item
-    state.newList[state.currentIndex] = state.newList[index]
-    // 将临时存放的数据设置好
-    state.newList[index] = tempItem
-
-    // 调整位置item
-    state.itemYList[index] = state.itemYList[state.currentIndex]
-    state.itemYList[state.currentIndex] = state.currentItemY
-
-    // 改变当前操作的的下标
-    state.currentIndex = index
-
-    // 记录新位置的数据
-    state.currentItemY = state.initialItemYList[state.currentIndex]
-}
-
-// h5 ios回弹
-function h5BodyScroll(flag) {
-    // #ifdef H5
-    document.body.style.overflow = flag ? 'initial' : 'hidden'
-    // #endif
-}
-
-defineExpose({...toRefs(state)})
-</script>
-
-<style scoped lang="scss">
-.m-drag {
-    position: relative;
-    width: 100%;
-
-    ::-webkit-scrollbar {
-        display: none;
-    }
-
-    .m-drag-item {
-        position: absolute;
-        left: 0;
-        right: 0;
-        transition: all ease 0.25s;
-        display: flex;
-        align-items: center;
-
-        > :deep(view:not(.icon)) {
-            flex: 1;
-        }
-
-        .icon {
-            padding: 30rpx;
-
-            .lines {
-                background: #e0e0e0;
-                width: 20px;
-                height: 2px;
-                border-radius: 100rpx;
-                margin-left: auto;
-                position: relative;
-                display: block;
-                transition: all ease 0.25s;
-
-                &::before,
-                &::after {
-                    position: absolute;
-                    width: inherit;
-                    height: inherit;
-                    border-radius: inherit;
-                    background: #e0e0e0;
-                    transition: inherit;
-                    content: '';
-                    display: block;
-                }
-
-                &::before {
-                    top: -14rpx;
-                }
-
-                &::after {
-                    bottom: -14rpx;
-                }
-            }
-        }
-
-        // 拖拽中的元素,添加阴影、关闭动画、层级置顶
-        &.active {
-            box-shadow: 0 0 14rpx rgba(0, 0, 0, 0.08);
-            transition: initial;
-            z-index: 1;
-
-            .icon .lines {
-                background: #2e97f9;
-
-                &::before,
-                &::after {
-                    background: #2e97f9;
-                }
-            }
-        }
-    }
-}
-</style>

+ 0 - 39
src/components/mx-bottom-buttons/mx-bottom-buttons.vue

@@ -1,39 +0,0 @@
-<template>
-    <view v-if="left||right" class="fx-row gap-30" :class="containerClass">
-        <uv-button v-if="left" :text="left" :type="leftType" :icon="leftIcon" :icon-color="leftType" plain
-                   v-bind="buttonStyle" @click="$emit('left')"/>
-        <uv-button v-if="right" :text="right" :type="rightType" :icon="rightIcon" icon-color="white"
-                   :loading="loading" v-bind="buttonStyle" @click="$emit('right')"/>
-    </view>
-</template>
-
-<script setup>
-import {computed} from 'vue';
-import {createPropDefine} from "@/utils";
-
-const props = defineProps({
-    // left 和 right 必须提供值才会显示
-    left: createPropDefine('取消'),
-    leftIcon: createPropDefine(undefined),
-    right: createPropDefine('保存'),
-    rightIcon: createPropDefine(undefined),
-    leftType: createPropDefine('error'),
-    rightType: createPropDefine('primary'),
-    loading: createPropDefine(false, Boolean)
-})
-defineEmits(['left', 'right'])
-
-const single = computed(() => !props.left || !props.right)
-const buttonStyle = computed(() => ({
-    customStyle: {
-        height: '44px',
-    },
-    shape: 'circle',
-    class: single.value ? '!w-1/2' : '!flex-1'
-}))
-const containerClass = computed(() => single.value ? 'fx-cen-cen' : 'fx-bet-cen')
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 53
src/components/mx-buy-vip/mx-buy-vip.vue

@@ -1,53 +0,0 @@
-<template>
-    <uv-popup ref="popup" mode="center" bg-color="transparent" @close="close" :close-on-click-overlay="false">
-        <view class="fx-col items-center">
-            <view class="relative">
-                <uv-image width="280px" height="406px" mode="widthFix" :src="buyVipPopup"/>
-                <uv-button shape="circle" size="large" :text="`¥${price} 立即开通`" :style="btnBuyStyle"
-                           @click="gotoPay"/>
-            </view>
-            <uv-icon size="32" color="white" name="close-circle" @click="close" class="mt-80"/>
-        </view>
-    </uv-popup>
-</template>
-
-<script setup>
-import {ref} from 'vue'
-import {usePayment} from "@/hooks/usePayment";
-import {useTransfer} from "@/hooks/useTransfer";
-import buyVipPopup from '@/static/personal/buy_vip_popup.png'
-
-const popup = ref(null)
-const {price, payment} = usePayment()
-
-
-const btnBuyStyle = {
-    position: 'absolute',
-    backgroundColor: 'transparent',
-    border: 'none',
-    width: '180px',
-    bottom: '-2px',
-    left: '50px'
-}
-
-const open = function () {
-    popup.value.open()
-}
-
-const close = function () {
-    popup.value.close()
-    // NOTE: 只调用了popup的显示与隐藏
-    // usePayment会持续监听订单信息,直到获取到一个终止态,否则不能进行下一次payment响应
-    // TODO:待后续观察是否合适,看是否需要在这里释放支付的一些状态
-}
-
-function gotoPay() {
-  close();
-  const {transferTo} = useTransfer();
-  transferTo("/pagesOther/pages/personal-center/pay/pay");
-}
-
-defineExpose({open, close})
-</script>
-
-<style scoped></style>

+ 0 - 59
src/components/mx-condition-dropdown/mx-condition-dropdown-item.vue

@@ -1,59 +0,0 @@
-<template>
-    <view class="fx-row items-center gap-5" @click="handleClick" @touchmove.stop>
-        <text class="whitespace-nowrap relative" :class="{'text-primary': opening, 'text-fired': fired}">
-            {{ display }}
-        </text>
-        <uv-icon :name="opening?'arrow-up':'arrow-down'" :color="opening?'primary':undefined"/>
-    </view>
-</template>
-
-<script setup>
-import {computed} from 'vue';
-import {createPropDefine} from "@/utils";
-import {useInjectSearchModel} from "@/components/mx-condition/useSearchModelInjection";
-import {toValue} from "@vueuse/core";
-import {useInjectConditionDropdownPopup} from "@/components/mx-condition-dropdown/useConditionDropdownPopupInjection";
-
-const props = defineProps({
-    condition: createPropDefine({}, Object),
-    useValueAsTitle: createPropDefine(false, Boolean)
-})
-
-const {popup} = useInjectConditionDropdownPopup()
-const {queryParams} = useInjectSearchModel()
-const list = computed(() => props.condition.list)
-const config = computed(() => props.condition.config)
-const current = computed(() => toValue(queryParams)[config.value.key])
-const currentItems = computed(() => list.value.filter(i => [].concat(current.value).includes(getValue(i))))
-const display = computed(() => {
-    if (props.useValueAsTitle && currentItems.value.length == 1) return getLabel(currentItems.value[0])
-    return config.value.title
-})
-const fired = computed(() => {
-    // 命中的是默认条件
-    if (currentItems.value.length == 1 && !getValue(currentItems.value[0])) return false
-    // 有条件命中了
-    return !props.useValueAsTitle && currentItems.value.length > 0
-})
-const opening = computed(() => popup.value?.condition == props.condition) // 当前弹窗打开
-
-const getLabel = (item) => config.value.keyName ? item[config.value.keyName] : item
-const getValue = (item) => config.value.keyValue ? item[config.value.keyValue] : item
-
-const handleClick = () => {
-    popup.value.open(props.condition)
-}
-</script>
-
-<style scoped lang="scss">
-.text-fired::before {
-    content: ' ';
-    position: absolute;
-    top: 0;
-    right: -3px;
-    width: 6px;
-    height: 6px;
-    border-radius: 3px;
-    background-color: var(--error-light-color);
-}
-</style>

+ 0 - 120
src/components/mx-condition-dropdown/mx-condition-dropdown-popup.vue

@@ -1,120 +0,0 @@
-<template>
-    <uv-popup ref="popup" mode="top" :safe-area-inset-bottom="false" :offset-top="relTop" @change="handleChange">
-        <scroll-view scroll-y style="max-height: 35vh;">
-            <view class="w-screen px-30 py-10 box-border" @touchmove.stop>
-                <component :is="comp" v-model="model[config.key]" placement="column" icon-placement="right">
-                    <component :is="itemComp" v-for="i in list" :name="getValue(i)" :label="getLabel(i)"/>
-                </component>
-            </view>
-        </scroll-view>
-        <mx-bottom-buttons left-type="primary" left="重置" :right="right" class="p-30"
-                           @left="handleReset" @right="handleConfirm"/>
-    </uv-popup>
-</template>
-
-<script setup>
-import {ref, computed} from 'vue';
-import {useElementBounding} from "@vueuse/core";
-import {useInjectConditionDropdownPopup} from "@/components/mx-condition-dropdown/useConditionDropdownPopupInjection";
-import {deepClone, sleep} from "@/uni_modules/uv-ui-tools/libs/function";
-import {useInjectSearchModel} from "@/components/mx-condition/useSearchModelInjection";
-import UvCheckboxGroup from "@/uni_modules/uv-checkbox/components/uv-checkbox-group/uv-checkbox-group.vue";
-import UvRadioGroup from "@/uni_modules/uv-radio/components/uv-radio-group/uv-radio-group.vue";
-import UvCheckbox from "@/uni_modules/uv-checkbox/components/uv-checkbox/uv-checkbox.vue";
-import UvRadio from "@/uni_modules/uv-radio/components/uv-radio/uv-radio.vue";
-import {func} from "@/uni_modules/uv-ui-tools/libs/function/test";
-
-const popup = ref(null)
-const {bottom, container} = useInjectConditionDropdownPopup()
-const {queryParams} = useInjectSearchModel()
-const model = ref({}) // 存放queryParams副本
-const show = ref(false)
-
-const condition = ref(null)
-const list = computed(() => condition.value?.list || [])
-const config = computed(() => condition.value?.config || {})
-const multiple = computed(() => config.value?.multiple)
-const comp = computed(() => multiple.value ? UvCheckboxGroup : UvRadioGroup)
-const itemComp = computed(() => multiple.value ? UvCheckbox : UvRadio)
-
-const right = computed(() => {
-    if (!multiple.value) return '确定'
-    const cur = model.value[config.value.key]
-    if (cur.length < 1) return '确定'
-    return `确定(${cur.length})`
-})
-
-// TODO: 相对位置不精准
-// baseTop 理论上应该找当前容器的值,但目前筛选器是紧贴swiper容器的,所以取的是筛选器的顶部
-// 如果以后有定位不对的情况,请修正该值
-const {top: baseTop} = useElementBounding(container)
-const {bottom: baseBottom} = useElementBounding(bottom)
-const relTop = computed(() => baseBottom.value - baseTop.value - 1)
-
-const handleReset = () => {
-    // 重置本地model状态
-    if (multiple.value) model.value[config.value.key] = []
-    else model.value[config.value.key] = ''
-    if (config.value.defaultValue) {
-        let val = config.value.defaultValue
-        if (func(val)) val = val(condition.value)
-        model.value[config.value.key] = val
-    }
-}
-
-const handleConfirm = () => {
-    // 将本地model状态赋值回queryParams
-    const key = config.value.key
-    queryParams.value[key] = model.value[key]
-    close()
-}
-
-const open = function (cond) {
-    // console.log('open begin', new Date().getTime())
-    if (cond == condition.value && show.value) {
-        popup.value.close()
-        // console.log('open end', new Date().getTime())
-    } else {
-        condition.value = cond
-        // 每次打开都创建一个副本,用户确认后才将结果反向从model赋回queryParams
-        model.value = deepClone(queryParams.value)
-        popup.value.open()
-        // console.log('open end', new Date().getTime())
-    }
-}
-
-const close = function () {
-    // console.log('close begin', new Date().getTime())
-    popup.value.close()
-    // console.log('close end', new Date().getTime())
-}
-
-const handleChange = async function (e) {
-    // console.log('change begin', new Date().getTime())
-    show.value = e.show
-    await sleep()
-    if (!show.value) {
-        // always clean current condition&model while hidden.
-        condition.value = null
-        model.value = {}
-    }
-    // console.log('change end', new Date().getTime())
-}
-
-const getLabel = (item) => config.value.keyName ? item[config.value.keyName] : item
-const getValue = (item) => config.value.keyValue ? item[config.value.keyValue] : item
-
-defineExpose({open, close, show, condition})
-</script>
-
-<style scoped lang="scss">
-::v-deep(.uv-checkbox-group),
-::v-deep(.uv-radio-group) {
-    .uv-checkbox-label--right,
-    .uv-radio-label--right {
-        padding-top: 20rpx;
-        padding-bottom: 20rpx;
-        border-bottom: 0.5px solid var(--border-color);
-    }
-}
-</style>

+ 0 - 47
src/components/mx-condition-dropdown/mx-condition-dropdown.vue

@@ -1,47 +0,0 @@
-<template>
-    <scroll-view :scroll-x="x" :style="{zIndex: z}">
-        <view class="bg-white h-[44px] text-xs text-main px-30" :class="layout">
-            <mx-condition-dropdown-item v-for="c in conditions" :condition="c" :use-value-as-title="useValueAsTitle"/>
-        </view>
-    </scroll-view>
-    <mx-condition-dropdown-popup ref="popup"/>
-    <uv-line ref="bottom" :style="{zIndex: z}"/>
-</template>
-
-<script setup>
-/*TODO:暂不要给mx-condition-dropdown套uv-form,会导致zIndex失效,待改善*/
-import {ref, getCurrentInstance, onMounted} from 'vue';
-import {createPropDefine} from "@/utils";
-import {useInjectSearchModel} from "@/components/mx-condition/useSearchModelInjection";
-import MxConditionDropdownItem from "@/components/mx-condition-dropdown/mx-condition-dropdown-item.vue";
-import MxConditionDropdownPopup from "@/components/mx-condition-dropdown/mx-condition-dropdown-popup.vue";
-import {useProvideConditionDropdownPopup} from "@/components/mx-condition-dropdown/useConditionDropdownPopupInjection";
-import {findAncestorComponentByName} from "@/utils/uni-helper";
-
-const props = defineProps({
-    border: createPropDefine(false, Boolean),
-    layout: createPropDefine('fx-row fx-bet-cen'),
-    useValueAsTitle: createPropDefine(false, Boolean),
-    x: createPropDefine(false, Boolean),
-    z: createPropDefine(20000, Number) // 防止被弹层遮挡
-})
-
-const bottom = ref(null) // 最底部的元素,用于定位弹层
-const popup = ref(null) // 弹层元素,将贴合bottom往下弹出
-// relative容器元素,确定弹出层的相对位置 // TODO:理论上这里应该使用外部的swiper
-const container = ref(null)
-const {conditions} = useInjectSearchModel()
-useProvideConditionDropdownPopup(popup, bottom, container)
-
-onMounted(() => {
-    const instance = getCurrentInstance()
-    const swiper = findAncestorComponentByName(instance, 'Swiper')
-    container.value = swiper || document.getElementById('app')
-})
-
-defineExpose({terminate: () => popup.value.close(), getShow: () => popup.value.show})
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 13
src/components/mx-condition-dropdown/useConditionDropdownPopupInjection.js

@@ -1,13 +0,0 @@
-import {injectLocal, provideLocal} from "@vueuse/core";
-
-const key = Symbol('CONDITION_DROPDOWN_POPUP')
-
-export const useProvideConditionDropdownPopup = (refOrGetter, bottomEleRef, containerEleRef) => {
-    const options = {popup: refOrGetter, bottom: bottomEleRef, container: containerEleRef}
-    provideLocal(key, options)
-    return options
-}
-
-export const useInjectConditionDropdownPopup = function () {
-    return injectLocal(key)
-}

+ 0 - 31
src/components/mx-condition/modules/conditionSharedConfig.js

@@ -1,31 +0,0 @@
-import {fnPlaceholder} from "@/utils/uni-helper";
-
-export const conditionSharedConfig = {
-    key: '',
-    // 标题名称
-    title: '',
-    // 如果queryParams参数中未初始化,是否在请求后自动初始化,初始化也和required相关,如果required=false
-    autoInit: true,
-    // 如果required=false,
-    allLabel: '不限',
-    // 获取数据的方法
-    handler: fnPlaceholder,
-    // 依赖项,在其之前dependentKeys必须准备好,将作为watch依据
-    dependentKeys: [],
-    // 非依赖项,不要求一定具备,但会和依赖项一起作用handler的请求参数
-    independentKeys: [],
-    // 如果是对象,则配置keyName作为显示用
-    keyName: '',
-    // 如果是对象,则配置keyValue作为传值用
-    keyValue: '',
-    // 校验规则,uv-form validation rule,array or object
-    required: false, // required 会自动生成非空校验,主要是方便用户改写这个属性
-    rule: [], // 如果有了required=true,这里就不需要重复添加非空校验了
-    // 隐藏,只是不显示(渲染),还是在工作的。
-    hidden: false,
-    // 多选
-    multiple: false,
-    // 重置时的默认条件 也可以配置方法// function(condition)
-    // TODO: 目前只用在了重置功能上,理论上也可以用在autoInit上,看后面的需要
-    defaultValue: ''
-}

+ 0 - 12
src/components/mx-condition/modules/useConditionCollegeFeatures.js

@@ -1,12 +0,0 @@
-import {conditionSharedConfig} from "@/components/mx-condition/modules/conditionSharedConfig";
-
-export const useConditionCollegeFeatures = function (featuresRefOrGetter, options = {}) {
-    return {
-        ...conditionSharedConfig,
-        handler: () => featuresRefOrGetter,
-        key: 'features',
-        title: '院校层次',
-        allLabel: '', // 不需要填充`全部`
-        multiple: true
-    }
-}

+ 0 - 12
src/components/mx-condition/modules/useConditionCollegeLevel.js

@@ -1,12 +0,0 @@
-import {conditionSharedConfig} from "@/components/mx-condition/modules/conditionSharedConfig";
-
-export const useConditionCollegeLevel = function (refOrGetter, options = {}) {
-    return {
-        ...conditionSharedConfig,
-        handler: () => refOrGetter,
-        key: 'level',
-        title: '学历层次',
-        allLabel: '', // 不需要填充`全部`
-        multiple: true
-    }
-}

+ 0 - 12
src/components/mx-condition/modules/useConditionCollegeLocation.js

@@ -1,12 +0,0 @@
-import {conditionSharedConfig} from "@/components/mx-condition/modules/conditionSharedConfig";
-
-export const useConditionCollegeLocation = function (refOrGetter, options = {}) {
-    return {
-        ...conditionSharedConfig,
-        handler: () => refOrGetter,
-        key: 'location',
-        title: '院校省份',
-        allLabel: '', // 不需要填充`全部`
-        multiple: true
-    }
-}

+ 0 - 12
src/components/mx-condition/modules/useConditionCollegeNatureTypeCN.js

@@ -1,12 +0,0 @@
-import {conditionSharedConfig} from "@/components/mx-condition/modules/conditionSharedConfig";
-
-export const useConditionCollegeNatureTypeCN = function (refOrGetter, options = {}) {
-    return {
-        ...conditionSharedConfig,
-        handler: () => refOrGetter,
-        key: 'natureTypeCN',
-        title: '办学类型',
-        allLabel: '', // 不需要填充`全部`
-        multiple: true
-    }
-}

+ 0 - 12
src/components/mx-condition/modules/useConditionCollegeType.js

@@ -1,12 +0,0 @@
-import {conditionSharedConfig} from "@/components/mx-condition/modules/conditionSharedConfig";
-
-export const useConditionCollegeType = function (typeRefOrGetter, options = {}) {
-    return {
-        ...conditionSharedConfig,
-        handler: () => typeRefOrGetter,
-        key: 'type',
-        title: '院校类型',
-        allLabel: '', // 不需要填充`全部`
-        multiple: true
-    }
-}

+ 0 - 29
src/components/mx-condition/modules/useConditionPaperType.js

@@ -1,29 +0,0 @@
-/*
-*
-* */
-import {useUserStore} from "@/hooks/useUserStore";
-import {useCacheStore} from "@/hooks/useCacheStore";
-import {cacheActions} from "@/hooks/defineCacheActions";
-import _ from "lodash";
-import {conditionSharedConfig} from "@/components/mx-condition/modules/conditionSharedConfig";
-
-export const useConditionPaperType = function (options = {}) {
-    const {isCultural} = useUserStore()
-    const {dispatchCache} = useCacheStore()
-
-    return {
-        ...conditionSharedConfig,
-        handler: async () => {
-            const result = await dispatchCache(cacheActions.getDicts, 'paper_type')
-            const source = [...result] // 创建source副本
-            // TODO:注意跟进,现在`必刷题`只提供给河南的职高对口
-            if (!isCultural.value) _.remove(source, i => i.dictValue == '必刷题')
-            return source
-        },
-        key: 'paperType',
-        title: '做题类型',
-        keyName: 'dictLabel',
-        keyValue: 'dictValue',
-        ...options
-    }
-}

+ 0 - 15
src/components/mx-condition/modules/useConditionPickType.js

@@ -1,15 +0,0 @@
-import {conditionSharedConfig} from "@/components/mx-condition/modules/conditionSharedConfig";
-import MxConst from "@/common/mxConst";
-
-export const useConditionPickType = function (options = {}) {
-    return {
-        ...conditionSharedConfig,
-        key: 'pickType',
-        title: '概率筛选',
-        handler: () => MxConst.enum.simulatePickTypes,
-        immediate: true,
-        keyName: 'text',
-        keyValue: 'value',
-        allLabel: '所有'
-    }
-}

+ 0 - 18
src/components/mx-condition/modules/useConditionSegmentLocation.js

@@ -1,18 +0,0 @@
-import {conditionSharedConfig} from "@/components/mx-condition/modules/conditionSharedConfig";
-import {useCacheStore} from "@/hooks/useCacheStore";
-import {cacheActions} from "@/hooks/defineCacheActions";
-
-export const useConditionSegmentLocation = function (options = {}) {
-    const {dispatchCache} = useCacheStore()
-
-    return {
-        ...conditionSharedConfig,
-        handler: async () => await dispatchCache(cacheActions.getSectionLocations),
-        key: 'location',
-        title: '地域',
-        keyName: 'text',
-        keyValue: 'value',
-        required: true,
-        ...options
-    }
-}

+ 0 - 19
src/components/mx-condition/modules/useConditionSegmentMode.js

@@ -1,19 +0,0 @@
-import {useCacheStore} from "@/hooks/useCacheStore";
-import {conditionSharedConfig} from "@/components/mx-condition/modules/conditionSharedConfig";
-import {cacheActions} from "@/hooks/defineCacheActions";
-
-export const useConditionSegmentMode = function (options = {}) {
-    const {dispatchCache} = useCacheStore()
-
-    return {
-        ...conditionSharedConfig,
-        dependentKeys: ['year'],
-        independentKeys: ['location'],
-        handler: async (params) => await dispatchCache(cacheActions.getSectionModes, params),
-        key: 'mode',
-        title: '科类',
-        keyName: 'modeName',
-        keyValue: 'mode',
-        ...options
-    }
-}

+ 0 - 19
src/components/mx-condition/modules/useConditionSegmentYear.js

@@ -1,19 +0,0 @@
-import {useCacheStore} from "@/hooks/useCacheStore";
-import {conditionSharedConfig} from "@/components/mx-condition/modules/conditionSharedConfig";
-import {cacheActions} from "@/hooks/defineCacheActions";
-
-export const useConditionSegmentYear = function (options = {}) {
-    const {dispatchCache} = useCacheStore()
-
-    return {
-        ...conditionSharedConfig,
-        dependentKeys: ['location'],
-        handler: async (params) => await dispatchCache(cacheActions.getSectionYears, params),
-        key: 'year',
-        title: '年份',
-        keyName: 'text',
-        keyValue: 'value',
-        required: true,
-        ...options
-    }
-}

+ 0 - 22
src/components/mx-condition/mx-condition-dropdown.vue

@@ -1,22 +0,0 @@
-<template>
-    <uv-drop-down ref="dropdown">
-        <uv-drop-down-item v-for="c in conditions" :label="c.config.title"/>
-        <uv-drop-down-popup/>
-    </uv-drop-down>
-</template>
-
-<script setup>
-import {ref, watch} from 'vue';
-import {useInjectPageScroll} from "@/hooks/usePageScrollInjection";
-import {useInjectSearchModel} from "@/components/mx-condition/useSearchModelInjection";
-
-const {scrollTop} = useInjectPageScroll()
-const {conditions} = useInjectSearchModel()
-const dropdown = ref(null)
-
-watch(scrollTop, () => dropdown.value.init()) // 位置修正
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 23
src/components/mx-condition/mx-condition.vue

@@ -1,23 +0,0 @@
-<template>
-    <view class="h-90 fx-row items-center gap-40">
-        <component :is="wrapper" v-for="c in conditions" :prop="c.config.key">
-            <mx-picker v-model="queryParams[c.config.key]" :data="c.list"
-                       :label-prop="c.config.keyName" :value-prop="c.config.keyValue"
-                       :empty-display="c.config.title" class="!flex-none"/>
-        </component>
-    </view>
-</template>
-
-<script setup>
-import {computed} from 'vue'
-import {useInjectSearchModel} from "@/components/mx-condition/useSearchModelInjection";
-import UvFormItem from "@/uni_modules/uv-form/components/uv-form-item/uv-form-item.vue";
-
-const {conditions, queryParams, needValidation} = useInjectSearchModel()
-// 这里考虑到结构一致性,如果有校验需要,则用uv-form-item包装一下
-const wrapper = computed(() => needValidation.value ? UvFormItem : 'div')
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 61
src/components/mx-condition/useConditionDataManager.js

@@ -1,61 +0,0 @@
-import {toValue, ref, watch, computed} from 'vue'
-import _ from "lodash";
-import {empty} from "@/uni_modules/uv-ui-tools/libs/function/test";
-import {useConditionFactory} from "@/components/mx-condition/useConditionFactory";
-
-export const useConditionDataManager = function (configs, eventManager, queryParams, sharedData, console) {
-
-    const container = ref(new Map())
-    const rules = ref({})
-
-    watch(queryParams, async () => {
-        console.log('dataManager begin init', new Date().getTime())
-        const keys = Object.keys(queryParams.value)
-        if (!keys.length) return
-        const keysConfig = configs.map(c => c.key)
-        const missingKeys = _.difference(keys, keysConfig)
-        if (missingKeys.length) console.warn('missing keys: ' + missingKeys.toString())
-
-        // 没有被依赖的属性需要在改变时触发查询
-        // 依赖是在config中指定的,但是否没有被依赖需要遍历所有条件才能知道
-        const keysNotBeDependent = [...keys]
-        keys.forEach(key => {
-            const config = configs.find(c => c.key == key)
-            if (!config) return _.pull(keysNotBeDependent, key)
-            _.pull(keysNotBeDependent, ...config.dependentKeys)
-
-            const condition = useConditionFactory(config, eventManager, queryParams, sharedData, console)
-            container.value.set(key, condition)
-
-            // useConditionFactory可能会对规则进行加工
-            if (!empty(config.rule)) rules.value[config.key] = config.rule
-
-            // 必须条件立马加入workingList,防止事件过早触发
-            if (condition.config.required) eventManager.push('dataManager init of required', key)
-        })
-
-        // 一般来说必须得有1个非被依赖项,才能构成一个响应链,否则没有机会触发onSearch
-        // TODO:这里只是当1个警告,实际运行效果还待评估
-        if (!keysNotBeDependent.length) console.warn('At least one key must not be dependent!')
-        keysNotBeDependent.forEach(k => {
-            watch(() => toValue(queryParams)[k], () => eventManager.trigger('dataManager watch not be dependent of ' + k))
-        })
-    }, {immediate: true})
-
-    const conditions = computed(() => [...container.value.values()])
-    const filterConditions = computed(() => conditions.value.filter(c => !c.config.hidden))
-    const needValidation = computed(() => !empty(rules.value))
-
-    const reset = function () {
-        container.value.clear()
-        rules.value = {}
-    }
-
-    return {
-        container,
-        conditions: filterConditions,
-        rules,
-        needValidation,
-        reset
-    }
-}

+ 0 - 72
src/components/mx-condition/useConditionEventManager.js

@@ -1,72 +0,0 @@
-import {ref, watch} from 'vue'
-import {createEventHook} from "@vueuse/core";
-import _ from "lodash";
-import {empty} from "@/uni_modules/uv-ui-tools/libs/function/test";
-
-export const useConditionEventManager = function (queryParams, validator, console) {
-    // 工作列表,工作列表清空时,表示条件已经准备好了
-    const workingList = ref([])
-    // 初次完成标识
-    const initialized = ref(false)
-    // 外抛事件,一般事件
-    const searchEvent = createEventHook()
-    // 初始化成功时的外抛事件,会优先于searchEvent外抛
-    const initEvent = createEventHook()
-
-    const push = function (reason, ...keys) {
-        workingList.value.push(...keys)
-        console.log('eventManager push reason:', reason, ', key:', keys + '', ', result:', workingList.value + '')
-    }
-
-    const pop = async (reason, key) => {
-        if (workingList.value.includes(key)) {
-            _.pull(workingList.value, key)
-            console.log('eventManager pop reason:', reason, ', key:', key, ', result:', workingList.value + '')
-
-            await trigger('working list check')
-        }
-    }
-
-    // loadData 引发的pop 与 dataManager 非被依赖项watch 有时会并发触发trigger
-    // 用triggerLock来控制并发只执行一次,TODO:待观察看有没有更好的方式
-    let triggerLock = false
-    const trigger = async (reason) => {
-        if (triggerLock) {
-            console.log('eventManager trigger ignore by lock,reason', reason)
-            return
-        }
-        triggerLock = true
-        console.log('eventManager trigger reason:', reason)
-
-        try {
-            // check trigger event
-            if (empty(workingList.value) && !empty(queryParams.value)) {
-                // 看当前是否数据有效
-                await validator.value()
-
-                if (!initialized.value) {
-                    console.log('eventManager init trigger')
-                    await initEvent.trigger()
-                    initialized.value = true
-                }
-                console.log('eventManager search trigger', new Date().getTime())
-                await searchEvent.trigger()
-            }
-        } finally {
-            triggerLock = false
-        }
-    }
-
-    const reset = function () {
-        workingList.value.length = 0
-    }
-
-    return {
-        push,
-        pop,
-        trigger,
-        reset,
-        onSearch: searchEvent.on,
-        onInit: initEvent.on
-    }
-}

+ 0 - 108
src/components/mx-condition/useConditionFactory.js

@@ -1,108 +0,0 @@
-import {ref, toValue, watch, nextTick, isRef} from 'vue';
-import {empty, func} from "@/uni_modules/uv-ui-tools/libs/function/test";
-import _ from "lodash";
-import {fnPlaceholder} from "@/utils/uni-helper";
-
-
-// 一般情况下用queryParams即可,但有可能有些全局参数不在queryParams里面,则用sharedData注入进来
-// 通过dependentKeys和independentKeys,获取请求必须的参数
-// dependentKeys是必须参数,independentKeys是非必须参数
-export const useConditionFactory = function (config, eventManager, queryParams, sharedData, console) {
-    const {
-        key, handler, autoInit, required, rule, allLabel, title, multiple,
-        dependentKeys, independentKeys, keyName, keyValue
-    } = config
-
-    if (empty(key)) throw new Error('condition must define a key')
-    if (!func(handler)) throw new Error('handler must be a function')
-
-    const list = ref([])
-    if (required && !rule.some(r => r.required)) {
-        // 没有必填规则就自动创建一条
-        const requiredRule = {required: true, message: `${title}不能为空`, transform: (v) => v + ''}
-        config.rule = [...rule, requiredRule] // 不要直接修改rule,rule可能来自于conditionSharedConfig
-    }
-
-    const makePayload = async () => {
-        const payload = {}
-        const params = toValue(queryParams)
-        // 先检查依赖项
-        for (const key of dependentKeys) {
-            const val = params[key]
-            if (empty(val)) {
-                // 只是给一个警告,reject的方式太重了,会让后续开发认为产生了不可修复问题。
-                // 一般情况下缺必传字段,只需要等必传字段autoInit完成就会自动修复这个问题。
-                // 当然万一运行不符合预期,这里也确实是需要着重检查的点。
-                console.warn(`${config.key}: independent key ${key} is required, wait for next turn.`)
-                return new Promise(fnPlaceholder).then()
-                // return Promise.reject('some message')
-            }
-            payload[key] = val
-        }
-        for (const key of independentKeys) {
-            payload[key] = params[key]
-        }
-        return {...toValue(sharedData), ...payload}
-    }
-
-    const loadData = async () => {
-        eventManager.push('loadData calling', key)
-
-        const payload = await makePayload()
-        const results = await config.handler(payload)
-        console.log('dataManager data loaded', key, results)
-
-        const processData = function (results) {
-            if (allLabel && !required) {
-                const add = keyValue ? {[keyName]: allLabel, [keyValue]: ''} : allLabel
-                list.value = [add, ...results] // 不直接修改,可能会影响缓存结果
-            } else {
-                list.value = results
-            }
-
-            const current = toValue(queryParams)[key]
-            const currentInvalid = (val) => {
-                const valSource = list.value.map(i => keyValue ? i[keyValue] : i)
-                const diff = _.difference(valSource, [].concat(val))
-                console.log('loadData invalid current:', val, ', diff', diff)
-                return diff.length > 0
-            }
-            if (!empty(current) && currentInvalid(current)) {
-                console.log('loadData clear invalid', key, current)
-                queryParams.value[key] = multiple ? [] : ''
-            }
-            if (required && autoInit && empty(current)) {
-                const first = _.first(list.value)
-                const firstValue = keyValue ? first[keyValue] : first
-                if (first) queryParams.value[key] = multiple ? [firstValue] : firstValue
-                console.log('loadData auto init', key, firstValue)
-            }
-
-            eventManager.pop('loadData called', key)
-        }
-
-        if (isRef(results)) {
-            const pureResults = toValue(results)
-            if (empty(pureResults)) {
-                // 此时没有数据,说明后续可能会更新,使用watch监听即可
-                watch(results, (values) => processData(values))
-            } else {
-                processData(pureResults)
-            }
-        } else {
-            processData(results)
-        }
-    }
-
-    // 数据加载
-    if (!dependentKeys.length) {
-        // 无依赖项,直接调用。这里使用了nextTick做保险,万一立即进行校验,要保证uv-form渲染之后才能进行
-        nextTick(async () => await loadData())
-    } else {
-        // 有依赖项,建立监听,依赖变更时重新加载数据
-        const watches = dependentKeys.map(k => () => toValue(queryParams)[k])
-        watch(watches, async () => await loadData(), {immediate: true})
-    }
-
-    return {list, config}
-}

+ 0 - 43
src/components/mx-condition/useSearchModelInjection.js

@@ -1,43 +0,0 @@
-import {computed} from 'vue'
-import {injectLocal, provideLocal} from "@vueuse/core";
-import {useConditionEventManager} from "@/components/mx-condition/useConditionEventManager";
-import {useConditionDataManager} from "@/components/mx-condition/useConditionDataManager";
-import {fnPlaceholder} from "@/utils/uni-helper";
-
-const key = Symbol('SEARCH_MODEL_SERVICE')
-
-/* 给业务方使用,希望只是定制查询条件的动态参数与静态参数 */
-export const useProvideSearchModel = function (configs, queryParams, formRef = null, sharedData = {}) {
-    const silence = true
-    // console
-    const log = silence
-        ? {log: fnPlaceholder, warn: console.warn, error: console.error}
-        : {log: console.log, warn: console.warn, error: console.error}
-    // uv-form validation
-    const validator = computed(() => formRef?.value?.validate || fnPlaceholder)
-    // event manager for trigger onSearch/onInit
-    const eventManager = useConditionEventManager(queryParams, validator, log)
-    // request data manager. configs必须提供当前场景所需要的所有condition
-    const dataManager = useConditionDataManager(configs, eventManager, queryParams, sharedData, log)
-    const reset = () => {
-        dataManager.reset()
-        eventManager.reset()
-    }
-    const options = {
-        queryParams, // 原样返回
-        sharedData, // 原样返回
-        rules: dataManager.rules, // 给forms用的校验规则,因为利用uv-form的校验是最简便的
-        conditions: dataManager.conditions, // 显示条件选项的数据
-        needValidation: dataManager.needValidation, // 是否需要校验,和渲染uv-form有关
-        onSearch: eventManager.onSearch, // 搜索事件
-        onInit: eventManager.onInit, // 搜索事件,第一次
-        reset // 重置搜索条件状态
-    }
-    provideLocal(key, options)
-    window.searchModel = options
-    return options
-}
-
-export const useInjectSearchModel = function () {
-    return injectLocal(key)
-}

+ 0 - 60
src/components/mx-count-down/mx-count-down.vue

@@ -1,60 +0,0 @@
-<template>
-    <uv-button v-bind="binding" :disabled="counting" @click="handleClick">
-        <template v-if="counting" #default>
-            <uv-count-down ref="timer" :time="time" :auto-start="false" :format="format" @finish="counting=false"/>
-        </template>
-    </uv-button>
-</template>
-
-<script>
-import {nextTick, ref} from 'vue'
-import {buttonProps} from "@/uni_modules/uv-button/components/uv-button/uv-button.vue";
-import {blockRunner, createPropDefine} from "@/utils";
-
-export default {
-    name: "mx-count-down",
-    props: {
-        ...buttonProps,
-        time: createPropDefine(60 * 1000, Number),
-        format: createPropDefine('ss'),
-        type: createPropDefine('text'),
-        plain: createPropDefine(true, Boolean),
-        customStyle: createPropDefine({
-            width: '40px',
-            height: '32px',
-            color: 'var(--primary-deep-color)'
-        }, [Object, String]),
-        validateBlock: createPropDefine(null, Function)
-    },
-    emits: ['click'],
-    setup(props, {attrs, emit}) {
-        const binding = {
-            ...props,
-            ...attrs
-        }
-        const counting = ref(false)
-        const timer = ref(null)
-
-        const handleClick = async function () {
-            await blockRunner(props.validateBlock)
-            if (counting.value) return
-            counting.value = true // NOTE:这里手工控制,让template#default渲染出来timer才能挂钩
-            emit('click')
-            await nextTick() // NOTE: 等待template#default渲染之后
-            timer.value.start()
-        }
-        return {
-            binding,
-            timer,
-            counting,
-            handleClick
-        }
-    }
-}
-</script>
-
-<style scoped lang="scss">
-::v-deep .uv-count-down__text {
-    color: var(--primary-color);
-}
-</style>

+ 0 - 57
src/components/mx-echarts/mx-echarts.vue

@@ -1,57 +0,0 @@
-<template>
-    <vue3-echarts ref="echarts" :options="localOption" :canvas-id="canvasId" :style="style"/>
-</template>
-
-<script setup>
-import {ref, computed, onActivated, onDeactivated, watch} from 'vue'
-/* NOTE: 因为vue3-echarts在页面快速切换过程中渲染时会报错,用这个组件包装一下,加强容错性*/
-import {createPropDefine} from "@/utils";
-import {empty} from "@/uni_modules/uv-ui-tools/libs/function/test";
-import {sleep} from "@/uni_modules/uv-ui-tools/libs/function";
-
-const props = defineProps({
-    option: createPropDefine(null, Object),
-    canvasId: createPropDefine('mx-echarts'),
-    style: createPropDefine({height: '240px'}, Object)
-})
-
-const localOption = ref({})
-const echarts = ref(null)
-const paused = ref(false)
-
-const success = computed(() => echarts.value?.chart)
-
-const syncOption = async () => {
-    const option = props.option
-    if (empty(option)) return
-
-    const valid = () => {
-        if (paused.value) return false
-        // 渲染成功也不用管了,如果要更精细控制,这里也要考虑
-        if (success.value && option == localOption.value) return false
-        return true
-    }
-
-    if (!valid()) return
-    await sleep(500)
-    // 双重判定
-    if (!valid()) return
-
-    localOption.value = option
-}
-
-onActivated(() => {
-    paused.value = false
-    syncOption()
-})
-onDeactivated(() => {
-    paused.value = true
-})
-// TODO:我们的业务场景一般都是一次性赋值,所以这里没有开启深度监听
-// Cannot access 'syncOption' before initialization // watch.immediate只能放在定义之后
-watch(() => props.option, () => syncOption(), {immediate: true})
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 56
src/components/mx-form-item/mx-form-item.vue

@@ -1,56 +0,0 @@
-<template>
-    <uv-form-item :prop="prop">
-        <template v-if="!$slots.default">
-            <uv-input :model-value="modelValue" :disabled="disabled" input-align="right" border="bottom"
-                      disabled-color="transparent" :color="disabled?'var(--disabled-color)':undefined"
-                      :type="type" :placeholder="autoPlaceholder" @input="$emit('update:modelValue', $event)">
-                <template #prefix>
-                    <slot name="prefix">
-                        <view class="text-main font-bold break-keep fx-row gap-3">
-                            {{ label }}
-                            <uv-icon name="lock" v-if="disabled"/>
-                        </view>
-                    </slot>
-                </template>
-                <template v-if="$slots.suffix" #suffix>
-                    <slot name="suffix"/>
-                </template>
-            </uv-input>
-        </template>
-        <template v-else>
-            <view
-                class="flex-1 px-[10px] py-[7px] fx-row items-center mx-border-b">
-                <view class="text-main font-bold break-keep fx-row gap-3">
-                    {{ label }}
-                    <uv-icon name="lock" v-if="disabled"/>
-                </view>
-                <view class="flex-1">
-                    <slot/>
-                </view>
-            </view>
-        </template>
-    </uv-form-item>
-</template>
-
-<script setup>
-import {computed} from 'vue'
-import {createPropDefine} from "@/utils";
-
-const props = defineProps({
-    prop: createPropDefine(''),
-    label: createPropDefine(''),
-    placeholder: createPropDefine(''),
-    modelValue: createPropDefine('', [String, Number]),
-    disabled: createPropDefine(false, Boolean),
-    type: createPropDefine(undefined)
-})
-defineEmits(['input', 'update:modelValue'])
-
-const autoPlaceholder = computed(() => props.placeholder || '请输入' + props.label)
-</script>
-
-<style scoped lang="scss">
-::v-deep .uv-form-item__body__right__message {
-    margin-left: 0 !important;
-}
-</style>

+ 0 - 38
src/components/mx-index-menus/mx-index-menus-item.vue

@@ -1,38 +0,0 @@
-<template>
-    <view class="fx-col fx-cen-cen gap-8 h-[70px]">
-        <vue-svg-icons v-if="useSvg" :name="icon" :class="svgClass"/>
-        <uv-image :src="combineOssFile(icon)" :mode="imgMode" :width="imgWidth" :height="imgHeight"/>
-        <text class="break-keep" :class="titleClass">{{ name }}</text>
-    </view>
-</template>
-
-<script setup>
-import {computed} from "vue";
-import {combineOssFile, createPropDefine} from "@/utils";
-
-const props = defineProps({
-    icon: createPropDefine(''),
-    name: createPropDefine(''),
-    useSvg: createPropDefine(false, Boolean),
-    svgClass: createPropDefine('w-100 h-100'),
-    iconMode: createPropDefine('widthFix'),
-    iconSize: createPropDefine(42, [Number, String]),
-    titleClass: createPropDefine('text-main'),
-});
-
-const imgMode = computed(() =>
-    ["widthFix", "heightFix"].includes(props.iconMode)
-        ? props.iconMode
-        : "widthFix"
-);
-const imgWidth = computed(() =>
-    imgMode.value == "widthFix" ? props.iconSize : "auto"
-);
-const imgHeight = computed(() =>
-    imgMode.value == "heightFix" ? props.iconSize : "auto"
-);
-
-</script>
-
-<style scoped>
-</style>

+ 0 - 60
src/components/mx-index-menus/mx-index-menus.vue

@@ -1,60 +0,0 @@
-<template>
-    <view class="mx-30 mx-card bg-white overflow-hidden">
-        <view v-if="pageSize<=1" :class="containerWrapClass">
-            <mx-index-paged-menus v-bind="props" @click="handleMenuClick"/>
-        </view>
-        <uv-swiper v-else :height="swiperHeight" :indicator-active-color="theme.brands.primary" :list="pagedData"
-                   bg-color="white" circular indicator indicator-inactive-color="#ccc" interval="10000">
-            <template #default="{item}">
-                <mx-index-paged-menus class="flex-1 mb-60 mt-40" v-bind="props" @click="handleMenuClick"/>
-            </template>
-        </uv-swiper>
-    </view>
-</template>
-
-<script setup>
-import {computed} from "vue";
-import {shareProps} from "./shareProps";
-import _ from "lodash";
-import {useTheme} from "@/hooks/useTheme";
-import MxIndexPagedMenus from "@/components/mx-index-menus/mx-index-paged-menus.vue";
-import {useTransfer} from "@/hooks/useTransfer";
-import {func} from "@/uni_modules/uv-ui-tools/libs/function/test";
-import {openAppLink} from "@/utils/plus-helper";
-import {createPropDefine} from "@/utils";
-
-// NOTE:这样写idea才能正常提示!
-// defineProps(shareProps) 在本页template无法直接提示shareProps中的属性;无法在外部使用时提示包含的属性
-const props = defineProps({
-    ...shareProps,
-    containerWrapClass: createPropDefine('py-40 px-20', [String, Object]),
-    customAction: createPropDefine(false, Boolean)
-});
-const emits = defineEmits(["click"]);
-const {theme} = useTheme();
-const {transferTo} = useTransfer();
-
-const pagedData = computed(() => {
-    if (props.columns <= 0 || props.rows <= 0 || !props.data.length) return [props.data];
-    return _.chunk(props.data, props.columns * props.rows);
-});
-const pageSize = computed(() => pagedData.value.length);
-const swiperHeight = computed(() => props.rows * 70 + 60); // 这个高度并不太准,只保证少量几行没问题
-
-const handleMenuClick = function (menu) {
-    if (menu.customAction || props.customAction) return emits("click", menu);
-    if (func(menu.handler)) {
-        menu.handler(menu);
-        return;
-    }
-    if (!menu?.path) return;
-    if (menu.appLink) {
-        openAppLink(menu.path);
-        return;
-    }
-    transferTo(menu.path, menu.nextData);
-};
-</script>
-
-<style scoped>
-</style>

+ 0 - 21
src/components/mx-index-menus/mx-index-paged-menus.vue

@@ -1,21 +0,0 @@
-<template>
-    <view :class="[containerClass, gapClass]" class="grid">
-        <mx-index-menus-item v-for="item in props.data" v-bind="item" :icon-size="iconSize" :title-class="titleClass"
-                             @click="emits('click', item)"/>
-    </view>
-</template>
-
-<script setup>
-import {computed} from "vue";
-import {shareProps} from "./shareProps";
-import MxIndexMenusItem from "@/components/mx-index-menus/mx-index-menus-item.vue";
-
-const props = defineProps({...shareProps});
-const emits = defineEmits(["click"]);
-
-// NOTE: dynamic class name is not be allowed in tailwindcss, notice `safelist` in `tailwind.config`
-const containerClass = computed(() => "grid-cols-" + props.columns);
-</script>
-
-<style scoped>
-</style>

+ 0 - 10
src/components/mx-index-menus/shareProps.js

@@ -1,10 +0,0 @@
-import {createPropDefine} from "@/utils";
-
-export const shareProps = {
-    data: createPropDefine([], Array),
-    columns: createPropDefine(4, Number),
-    rows: createPropDefine(2, Number),// 0 表示没有限制
-    titleClass: createPropDefine('text-main'),
-    gapClass: createPropDefine('gap-y-20'),
-    iconSize: createPropDefine(42, [Number, String])
-};

+ 0 - 52
src/components/mx-login-form-item/mx-login-form-item.vue

@@ -1,52 +0,0 @@
-<template>
-    <uv-form-item :prop="prop">
-        <template #label>
-            <uv-text :text="label" :prefix-icon="icon" v-bind="titleBindings"/>
-        </template>
-        <uv-input v-model="model[prop]" v-bind="inputBindings" :placeholder="autoPlaceholder"
-                  :type="type" @update:modelValue="$emit('update:modelValue', $event)">
-            <template v-for="(s, name) in $slots" #[name]>
-                <slot :name="name"/>
-            </template>
-        </uv-input>
-    </uv-form-item>
-</template>
-
-<script setup>
-/*NOTE: 必须设置父级uv-form.labelPosition=`top`来确保该组件的布局*/
-import {computed, ref} from 'vue'
-import {createPropDefine} from "@/utils";
-import {useInjectFormData} from "@/pagesOther/pages/login/components/hooks/useFormDataInjection";
-
-const props = defineProps({
-    prop: createPropDefine(''),
-    modelValue: createPropDefine(''),
-    label: createPropDefine(''),
-    placeholder: createPropDefine(''),
-    icon: createPropDefine(''),
-    type: createPropDefine(undefined)
-})
-defineEmits(['update:modelValue'])
-
-const [model] = useInjectFormData()
-const autoPlaceholder = computed(() => props.placeholder || '请输入' + props.label)
-const titleBindings = ref({
-    color: 'var(--primary-color)',
-    size: 20,
-    iconStyle: {
-        fontSize: '24px',
-        color: 'var(--primary-color)'
-    }
-})
-const inputBindings = ref({
-    fontSize: 18,
-    clearable: true,
-    border: 'bottom',
-    customStyle: {padding: '10px 0'}
-})
-
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 102
src/components/mx-nav-bar/mx-nav-bar.vue

@@ -1,102 +0,0 @@
-<template>
-    <uv-navbar v-bind="bindings">
-        <!--  slot 穿透  -->
-        <template v-for="name in Object.keys($slots)" #[name]>
-            <slot :name="name"/>
-        </template>
-        <template v-if="!$slots.center&&subTitle" #center>
-            <view class="max-w-[400rpx] text-center" :style="bindings.titleStyle">
-                <view class="truncate">{{ bindings.title }}</view>
-                <view class="text-3xs font-normal truncate">{{ subTitle }}</view>
-            </view>
-        </template>
-    </uv-navbar>
-</template>
-
-<script>
-import {computed} from 'vue';
-import {empty} from "@/uni_modules/uv-ui-tools/libs/function/test";
-import {navbarProps} from "@/uni_modules/uv-navbar/components/uv-navbar/uv-navbar.vue";
-import {useH5BackHome} from "@/components/mx-nav-bar/useH5BackHome";
-import {useTransfer} from "@/hooks/useTransfer";
-import {blockRunner, createPropDefine} from "@/utils";
-import {fnPlaceholder} from "@/utils/uni-helper";
-
-export default {
-    name: 'mx-nav-bar',
-    props: {
-        ...navbarProps,
-        mode: createPropDefine('light'),
-        subTitle: createPropDefine(''),
-        opacity: createPropDefine(1, Number),
-        // 用于支持左图标点击的AOP方法,支持返回boolean或者promise
-        leftClickBlock: createPropDefine(null, Function),
-        // leftClick&leftIcon被mx-nav-bar自行控制了,如果要隐藏left,设置leftDisabled=true
-        leftDisabled: createPropDefine(false, Boolean)
-    },
-    setup(props, {attrs}) {
-        const {hasPreviousPage, shouldShowHome} = useH5BackHome()
-        const {relaunch, transferBack} = useTransfer()
-
-        const darkMode = computed(() => props.mode == 'dark')
-        const lightMode = computed(() => props.mode == 'light')
-        const bindings = computed(() => {
-            const override = {...props, ...attrs}
-            // 主题色适配
-            if (darkMode.value) {
-                override.bgColor = 'var(--primary-color)'
-                override.titleStyle = {color: 'white', fontSize: '16px', fontWeight: 700}
-                override.leftIconColor = 'white'
-            } else {
-                override.bgColor = '#ffffff'
-                override.titleStyle = {color: 'var(--main-color)', fontSize: '16px', fontWeight: 700}
-                override.leftIconColor = 'var(--content-color)'
-            }
-            if (props.opacity == 0) {
-                override.bgColor = 'transparent'
-            } else if (props.opacity < 1) {
-                override.bgColor = uni.$uv.colorToRgba(override.bgColor, props.opacity)
-            }
-
-            if (!props.leftDisabled) {
-                // 自行控制后退逻辑
-                override.autoBack = false
-                override.leftIconSize = 20
-                if (hasPreviousPage.value) {
-                    override.leftIcon = 'arrow-left'
-                    if (empty(attrs.onLeftClick)) override.onLeftClick = handleNavBack
-                } else if (shouldShowHome.value) {
-                    override.leftIcon = 'home'
-                    override.leftIconSize = 22
-                    if (empty(attrs.onLeftClick)) override.onLeftClick = handleHomeClick
-                }
-            } else {
-                override.autoBack = false
-                override.leftIcon = ''
-                override.onLeftClick = fnPlaceholder
-            }
-
-            return override
-        })
-
-        async function handleNavBack() {
-            await blockRunner(props.leftClickBlock)
-            transferBack()
-        }
-
-        async function handleHomeClick() {
-            await blockRunner(props.leftClickBlock)
-            relaunch()
-        }
-
-        return {
-            darkMode,
-            lightMode,
-            bindings
-        }
-    }
-}
-
-</script>
-<style scoped>
-</style>

+ 0 - 19
src/components/mx-nav-bar/useH5BackHome.js

@@ -1,19 +0,0 @@
-import {computed} from 'vue'
-import {page, pages} from "@/uni_modules/uv-ui-tools/libs/function";
-import {getTabRoutes} from "@/common/mxConst";
-
-export function useH5BackHome() {
-    const hasPreviousPage = computed(() => {
-        const allPages = pages()
-        return allPages.length > 1
-    })
-
-    const shouldShowHome = computed(() => {
-        if (hasPreviousPage.value) return false
-        const tabs = getTabRoutes()
-        const current = page()
-        return !tabs.includes(current)
-    })
-
-    return {hasPreviousPage, shouldShowHome}
-}

+ 0 - 44
src/components/mx-paper/components/mx-paper-completion.vue

@@ -1,44 +0,0 @@
-<template>
-    <view class="h-[60px] fx-row fx-cen-cen gap-40 mx-shadow-up">
-        <uv-button :loading="loading" :text="text" type="primary" size="large" shape="circle"
-                   :custom-style="{width: '55vw', height: '44px'}" @click="handleComplete"/>
-    </view>
-</template>
-
-<script setup>
-import {computed} from 'vue'
-import {createPropDefine} from "@/utils";
-import {useInjectPaperService} from "@/components/mx-paper/usePaperInjection";
-import {useInjectPaperNavigatorRef} from "@/components/mx-paper/components/usePaperNavigatorRefInjection";
-import {useInjectPaperNavigationService} from "@/components/mx-paper/usePaperNavigationServiceInjection";
-
-const props = defineProps({
-    continueName: createPropDefine('')
-})
-
-const {loading, allowAnswer, allowScore, commitPaper, scorePaper, triggerContinueNext} = useInjectPaperService()
-const {navigator} = useInjectPaperNavigatorRef()
-const {uncompletedCheckForSubmit} = useInjectPaperNavigationService()
-
-const text = computed(() => {
-    if (allowAnswer.value) return '完成'
-    if (allowScore.value) return '结束阅卷'
-    return props.continueName || '继续做题'
-})
-
-const handleComplete = async () => {
-    await uncompletedCheckForSubmit(navigator.value)
-
-    if (allowAnswer.value) {
-        commitPaper()
-    } else if (allowScore.value) {
-        scorePaper()
-    } else {
-        triggerContinueNext()
-    }
-}
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 43
src/components/mx-paper/components/mx-paper-navigator-popup.vue

@@ -1,43 +0,0 @@
-<template>
-    <mx-popup-template :title="title" v-bind="extBinding" ref="popup">
-        <view class="grid grid-cols-4 gap-20">
-            <mx-paper-tab-item v-for="q in list" :item="q" clean-mode class="!h-[44px] !w-auto"
-                               @click="handleNavigateQuestion(q)"/>
-        </view>
-    </mx-popup-template>
-</template>
-
-<script setup>
-import {ref} from 'vue'
-import MxPaperTabItem from "@/components/mx-paper/components/mx-paper-tab-item.vue";
-import {useInjectPaperService} from "@/components/mx-paper/usePaperInjection";
-
-const popup = ref(null)
-const title = ref('')
-const list = ref([])
-const extBinding = ref({})
-
-const {goToQuestion} = useInjectPaperService()
-
-const handleNavigateQuestion = function (q) {
-    goToQuestion(q)
-    close()
-}
-
-const open = function (questions, description, ext = {left: '', right: ''}) {
-    list.value = questions
-    title.value = description
-    extBinding.value = ext
-    popup.value.open()
-}
-
-const close = function () {
-    popup.value.close()
-}
-
-defineExpose({open, close})
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 17
src/components/mx-paper/components/mx-paper-progress.vue

@@ -1,17 +0,0 @@
-<template>
-    <mx-progress :progress="progress" class="h-3 shadow-up shadow-green-100"/>
-</template>
-
-<script setup>
-import {computed} from 'vue';
-import {useInjectPaperService} from "@/components/mx-paper/usePaperInjection";
-import {useInjectQuestionService} from "@/components/mx-question/useQuestionInjection";
-
-const {questions} = useInjectPaperService()
-const {answeredQuestions} = useInjectQuestionService()
-const progress = computed(() => questions.value.length ? answeredQuestions.value.length / questions.value.length : 0)
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 41
src/components/mx-paper/components/mx-paper-tab-item.vue

@@ -1,41 +0,0 @@
-<template>
-    <view class="w-full h-full px-15 fx-col fx-cen-cen rounded relative"
-          :class="tabClass">
-        <text class="text-sm">第{{ item.seq }}题</text>
-        <template v-if="!cleanMode">
-            <view v-if="allowAnswer&&isAnswered(item)"
-                  class="absolute top-5 right-5 h-15 w-15 rounded shadow shadow-green-100 bg-success"/>
-            <template v-else-if="isScored(item)">
-                <uv-icon v-if="isPassed(item)" name="checkmark-circle-fill" size="12" color="var(--success-color)"/>
-                <uv-icon v-else name="close-circle-fill" size="12" color="var(--error-color)"/>
-            </template>
-        </template>
-    </view>
-</template>
-
-<script setup>
-import {computed} from 'vue'
-import {createPropDefine} from "@/utils";
-import {useInjectPaperService} from "@/components/mx-paper/usePaperInjection";
-import {useInjectQuestionService} from "@/components/mx-question/useQuestionInjection";
-
-const props = defineProps({
-    // item is a question
-    item: createPropDefine({}, Object),
-    // clean mode 给弹层用,不用展示状态特效
-    cleanMode: createPropDefine(false, Boolean)
-})
-
-const {current, allowAnswer} = useInjectPaperService()
-const {isAnswered, isScored, isPassed} = useInjectQuestionService()
-
-const isActive = computed(() => current.value == props.item)
-const tabClass = computed(() =>
-    isActive.value && !props.cleanMode ?
-        ['text-primary bg-white shadow shadow-blue-100 bg-white']
-        : ['text-main bg-slate-100 shadow-xs'])
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 13
src/components/mx-paper/components/usePaperNavigatorRefInjection.js

@@ -1,13 +0,0 @@
-import {injectLocal, provideLocal} from "@vueuse/core";
-
-const key = Symbol('PAPER_NAVIGATOR_POPUP')
-
-export const useProvidePaperNavigatorRef = function (popupRef) {
-    const options = {navigator: popupRef}
-    provideLocal(key, options)
-    return options
-}
-
-export const useInjectPaperNavigatorRef = function () {
-    return injectLocal(key)
-}

+ 0 - 97
src/components/mx-paper/mx-paper.vue

@@ -1,97 +0,0 @@
-<template>
-    <view class="page-content h-screen !bg-white">
-        <mx-nav-bar :title="paperName" :left-click-block="paperQuitBlock"/>
-        <slot name="top"/>
-        <mx-tabs-swiper ref="swiper" v-model="index" :tabs="questions" :tab-options="tabOptions" border
-                        :delay="false" :tabs-height="60" template="question">
-            <template #tab="scope">
-                <mx-paper-tab-item v-bind="scope"/>
-            </template>
-            <template #question="question">
-                <mx-question :question="question"/>
-            </template>
-        </mx-tabs-swiper>
-        <slot name="bottom">
-            <mx-question-score-subjective v-if="showScoreSubjective"/>
-            <mx-question-statistic/>
-            <mx-question-navigator/>
-            <mx-paper-completion v-if="!readonly" :continue-name="continueName"/>
-        </slot>
-        <mx-paper-navigator-popup ref="navigator"/>
-        <uv-safe-bottom/>
-    </view>
-</template>
-
-<script setup>
-import {ref, watch, computed} from 'vue'
-import {useInjectPaperService} from "@/components/mx-paper/usePaperInjection";
-import MxPaperTabItem from "@/components/mx-paper/components/mx-paper-tab-item.vue";
-import {useInjectQuestionService} from "@/components/mx-question/useQuestionInjection";
-import MxPaperCompletion from "@/components/mx-paper/components/mx-paper-completion.vue";
-import MxQuestionScoreSubjective from "@/components/mx-question/components/mx-question-score-subjective.vue";
-import MxQuestionNavigator from "@/components/mx-question/components/mx-question-navigator.vue";
-import MxQuestionStatistic from "@/components/mx-question/components/mx-question-statistic.vue";
-import MxPaperNavigatorPopup from "@/components/mx-paper/components/mx-paper-navigator-popup.vue";
-import {useProvidePaperNavigatorRef} from "@/components/mx-paper/components/usePaperNavigatorRefInjection";
-import {createPropDefine} from "@/utils";
-import {useInjectPaperNavigationService} from "@/components/mx-paper/usePaperNavigationServiceInjection";
-
-const props = defineProps({
-    // 底部按钮
-    readonly: createPropDefine(false, Boolean),
-    continueName: createPropDefine('')
-})
-
-const {
-    paperName,
-    allowScore,
-    questions,
-    index,
-    current,
-    isObjective
-} = useInjectPaperService()
-const {} = useInjectQuestionService()
-const swiper = ref(null)
-const navigator = ref(null)
-const isCurrentSubjective = computed(() => !isObjective(current.value))
-const showScoreSubjective = computed(() => allowScore.value && isCurrentSubjective.value)
-useProvidePaperNavigatorRef(navigator)
-const {uncompletedCheckForQuit} = useInjectPaperNavigationService()
-
-const tabOptions = {
-    scrollable: true,
-    keyName: 'seq',
-    lineHeight: 0.5,
-    lineWidth: 40,
-    lineColor: 'white'
-}
-
-const paperQuitBlock = async () => {
-    await uncompletedCheckForQuit(navigator.value)
-}
-</script>
-
-<style scoped lang="scss">
-::v-deep(.uv-tabs) {
-    .uv-tabs__wrapper__nav {
-        /* 不能设置__nav的间隔相关属性,会导致uv-tabs内部无法准确计算位置 */
-        /* 重写__item的样式是没有关系的 */
-        &__item {
-            padding: 8px 4px;
-            box-sizing: border-box;
-
-            &:first-child {
-                padding-left: 8px;
-            }
-
-            &:nth-last-child(2) {
-                padding-right: 8px;
-            }
-        }
-
-        &__line {
-            box-shadow: 0px -2px 5px rgba(0, 0, 255, 0.6);
-        }
-    }
-}
-</style>

+ 0 - 178
src/components/mx-paper/usePaperInjection.js

@@ -1,178 +0,0 @@
-import {computed, ref} from 'vue'
-import {createEventHook, injectLocal, provideLocal} from "@vueuse/core";
-import {empty} from "@/uni_modules/uv-ui-tools/libs/function/test";
-import {
-    commitExamineePaper,
-    commitExamineeQuestion,
-    scoreExamineeQuestion,
-    scoreFinish
-} from "@/api/webApi/studentEvaluating";
-import mxConst from "@/common/mxConst";
-import _ from "lodash";
-import {toast} from "@/uni_modules/uv-ui-tools/libs/function";
-
-const key = Symbol('PAPER_SERVICE')
-
-// 尽量保持只需要依靠paper/paper.questions中计算的相关API在paperService中
-// 与userData计算相关的API集中的questionService中
-export const useProvidePaperService = function (paperDataRef, overrideService = {}) {
-    const paper = paperDataRef
-    // 因为注入服务时paper可能还没有返回,为了方便注入时的有明确的属性名参考,这里将可能有用的属性抄了一遍。
-    const props = {
-        paperName: computed(() => paper.value?.name),
-        allowAnswer: computed(() => paper.value?.allowAnswer),
-        allowScore: computed(() => paper.value?.allowScore),
-        questions: computed(() => paper.value?.questions || []),
-        examineeId: computed(() => paper.value?.examineeId),
-        examineeType: computed(() => paper.value?.examineeType),
-        scoringType: computed(() => paper.value?.scoringType),
-        remaining: computed(() => paper.value?.remaining),
-
-        // for fast query by questionId
-        questionsMap: computed(() => _.keyBy(props.questions.value, 'questionId'))
-    }
-
-    // 事件定义
-    const events = {
-        questionChange: createEventHook(), // display question change(index change)
-        answerChange: createEventHook(), // user answered question
-        answerComplete: createEventHook(), // all questions are answered
-        scoreChange: createEventHook(), // user scored question
-        scoreComplete: createEventHook(), // all questions are scored
-        continueNext: createEventHook() // continue next paper
-    }
-
-    // data & service define
-    const index = ref(0)
-    const loading = ref(false)
-    const current = computed(() => props.questions.value[index.value] || {})
-    const hasNext = computed(() => index.value < props.questions.value.length - 1)
-    const hasPrev = computed(() => index.value > 0)
-
-    // methods
-    const methods = {
-        reset: () => {
-            paper.value = {}
-            index.value = 0
-            loading.value = false
-        },
-        isObjective: (q) => mxConst.question.isObjective(q.typeId),
-        isCheckbox: (q) => mxConst.question.isCheckbox(q.typeId),
-        isRadio: (q) => mxConst.question.isRadio(q.typeId),
-        isAnswerCorrect: (q) => q.answers?.length && q.answer == q.answers[0].toString(),
-        tryGoNext: () => {
-            if (!hasNext.value) return toast('已经是最后一题了')
-            index.value += 1
-        },
-        tryGoPrev: () => {
-            if (!hasPrev.value) return toast('已经是第一题了')
-            index.value -= 1
-        },
-        goToQuestion: (q) => {
-            const idx = props.questions.value.indexOf(q)
-            if (idx > -1) index.value = idx
-        },
-        commitQuestion: async (commits) => {
-            if (empty(commits)) return
-            if (loading.value) return
-            try {
-                loading.value = true
-                const examineeId = props.examineeId.value
-                const examineeType = props.examineeType.value
-                const commit = {examineeId, examineeType, questions: commits}
-                await commitExamineeQuestion(commit)
-                await events.answerChange.trigger(commits)
-            } finally {
-                loading.value = false
-            }
-        },
-        commitPaper: async () => {
-            if (loading.value) return
-            try {
-                // uni.showLoading() // 不需要,交卷时有自定义弹窗
-                loading.value = true
-                const examineeId = props.examineeId.value
-                const examineeType = props.examineeType.value
-                const commit = {examineeId, examineeType}
-                await commitExamineePaper(commit)
-                await events.answerComplete.trigger()
-            } finally {
-                loading.value = false
-                // uni.hideLoading()
-            }
-        },
-        scoreQuestion: async (commits) => {
-            if (empty(commits)) return
-            if (loading.value) return
-            try {
-                loading.value = true
-                const examineeId = props.examineeId.value
-                const examineeType = props.examineeType.value
-                const commit = {examineeId, examineeType, questions: commits}
-                await scoreExamineeQuestion(commit)
-                await events.scoreChange.trigger(commits)
-            } finally {
-                loading.value = false
-            }
-        },
-        scorePaper: async () => {
-            if (loading.value) return
-            try {
-                uni.showLoading()
-                loading.value = true
-                const examineeId = props.examineeId.value
-                const examineeType = props.examineeType.value
-                const commit = {examineeId, examineeType}
-                await scoreFinish(commit)
-                await events.scoreComplete.trigger()
-            } finally {
-                loading.value = false
-                uni.hideLoading()
-            }
-        },
-        triggerContinueNext: async () => {
-            await events.continueNext.trigger()
-        }
-    }
-
-    // api that should combine `props` & `methods`
-    const extend = {
-        isAllObjective: computed(() => {
-            return props.questions.value.every(q => methods.isObjective(q))
-        }),
-        answerCompletedTips: computed(() => {
-            const type = props.scoringType.value + ''
-            const source = {
-                "1": `客观题已自动计分,主观题需要手动计分`,
-                "2": `此卷为老师阅卷,需要学生交卷后老师进行阅卷计分`,
-                "3": `此卷为系统阅卷,提交后系统自动计分`
-            }
-            return source[type] || source['1']
-        })
-    }
-
-    const options = {
-        loading,
-        paper,
-        ...props,
-        onAnswerChange: events.answerChange.on,
-        onAnswerComplete: events.answerComplete.on,
-        onScoreChange: events.scoreChange.on,
-        onScoreComplete: events.scoreComplete.on,
-        onContinueNext: events.continueNext.on,
-        index,
-        current,
-        hasNext,
-        hasPrev,
-        ...methods,
-        ...extend,
-        ...overrideService
-    }
-
-    provideLocal(key, options)
-    return options
-}
-
-export const useInjectPaperService = function () {
-    return injectLocal(key)
-}

+ 0 - 134
src/components/mx-paper/usePaperNavigationServiceInjection.js

@@ -1,134 +0,0 @@
-import {injectLocal, provideLocal} from "@vueuse/core";
-import {toast} from "@/uni_modules/uv-ui-tools/libs/function";
-import _ from "lodash";
-import {useInjectPaperService} from "@/components/mx-paper/usePaperInjection";
-import {useInjectQuestionService} from "@/components/mx-question/useQuestionInjection";
-
-const key = Symbol('PAPER_NAVIGATION_SERVICE')
-
-export const useProvidePaperNavigationService = (paperService, questionService, overrideService = {}) => {
-
-    const {goToQuestion, allowAnswer, allowScore, commitPaper, scorePaper} = useInjectPaperService()
-    const {unansweredQuestions, unscoredQuestions, doChunk} = useInjectQuestionService()
-
-    const questionsNavigationHandler = (navigator, list, label) => {
-        if (!list.length) return toast(`没有${label}的题`)
-        if (list.length == 1) return goToQuestion(_.first(list))
-        navigator.open(list, `${label}的题`)
-    }
-
-    const uncompletedCheckForQuit = async (navigator) => {
-        await doChunk()
-        return new Promise((resolve, reject) => {
-            if (allowAnswer.value) {
-                if (unansweredQuestions.value.length) {
-                    navigator.open(unansweredQuestions.value, '您还有未做的题', {
-                        left: '强制退出',
-                        right: '继续做题',
-                        onLeft: () => {
-                            navigator.close()
-                            resolve() // 允许用户继续执行后续逻辑
-                        },
-                        onRight: () => {
-                            navigator.close()
-                            goToQuestion(_.first(unansweredQuestions.value))
-                            reject('操作终止,用户选择继续做题')
-                        }
-                    })
-                } else {
-                    navigator.open([], '提示', {
-                        description: '您已经做完所有题,是否完成提交',
-                        left: '强制退出',
-                        right: '完成提交',
-                        onLeft: () => {
-                            navigator.close()
-                            resolve()
-                        },
-                        onRight: () => {
-                            navigator.close()
-                            commitPaper()
-                            reject('操作终止,用户选择完成提交')
-                        }
-                    })
-                }
-            } else if (allowScore.value) {
-                if (unscoredQuestions.value.length) {
-                    navigator.open(unscoredQuestions.value, '您还有未阅的题', {
-                        left: '强制退出',
-                        right: '继续阅卷',
-                        onLeft: () => {
-                            navigator.close()
-                            resolve() // 允许用户继续执行后续逻辑
-                        },
-                        onRight: () => {
-                            navigator.close()
-                            goToQuestion(_.first(unscoredQuestions.value))
-                            reject('操作终止,用户选择继续阅卷')
-                        }
-                    })
-                } else {
-                    navigator.open([], '提示', {
-                        description: '您已经阅完所有题,是否完成阅卷',
-                        left: '强制退出',
-                        right: '完成阅卷',
-                        onLeft: () => {
-                            navigator.close()
-                            resolve()
-                        },
-                        onRight: () => {
-                            navigator.close()
-                            scorePaper()
-                            reject('操作终止,用户选择完成阅卷')
-                        }
-                    })
-                }
-            } else {
-                resolve()
-            }
-        })
-    }
-
-    const uncompletedCheckForSubmit = async (navigator) => {
-        await doChunk()
-        return new Promise((resolve, reject) => {
-            if (allowAnswer.value) {
-                if (unansweredQuestions.value.length) {
-                    navigator.open(unansweredQuestions.value, '您还有未做的题', {
-                        left: '仍然提交',
-                        right: '继续做题',
-                        onLeft: () => {
-                            navigator.close()
-                            resolve() // 题没有做完是可以强制交卷的!
-                        },
-                        onRight: () => {
-                            navigator.close()
-                            goToQuestion(_.first(unansweredQuestions.value))
-                            reject('操作终止,用户选择继续做题') // 注意这里也是reject
-                        }
-                    })
-                    return // 只要有题未做,弹层都会阻止原提交行为
-                }
-            } else if (allowScore.value) {
-                if (unscoredQuestions.value.length) {
-                    navigator.open(unscoredQuestions.value, '未阅的题')
-                    reject() // 阅卷时必须给所有题打完分才行
-                    return
-                }
-            }
-            resolve()
-        })
-    }
-
-    const options = {
-        questionsNavigationHandler,
-        uncompletedCheckForQuit,
-        uncompletedCheckForSubmit,
-        ...overrideService
-    }
-    provideLocal(key, options)
-    return options
-}
-
-export const useInjectPaperNavigationService = () => {
-    return injectLocal(key)
-}

+ 0 - 131
src/components/mx-picker/mx-picker.vue

@@ -1,131 +0,0 @@
-<template>
-    <view class="flex-1 fx-row fx-end-cen gap-10" @click="handleClick">
-        <view v-if="valueMode" class="text-sm">{{ display }}</view>
-        <view v-else class="text-sm text-light">{{ placeholder }}</view>
-        <uv-icon v-if="!disabled" name="arrow-down"/>
-        <uv-picker ref="picker" :columns="columns" :default-index="defaultIndex" :title="title" :key-name="labelProp"
-                   @confirm="handleConfirm" @change="handleChange"/>
-    </view>
-</template>
-
-<script setup>
-import {computed, getCurrentInstance, ref} from 'vue'
-import {empty} from "@/uni_modules/uv-ui-tools/libs/function/test";
-import _ from "lodash";
-import {createPropDefine} from "@/utils";
-import {findTreePath} from "@/utils/tree-helper";
-import {autoFormValidate} from "@/utils/uni-helper";
-
-const picker = ref(null)
-const props = defineProps({
-    title: createPropDefine(''),
-    placeholder: createPropDefine(''),
-    data: createPropDefine([], Array),
-    modelValue: createPropDefine(null, [String, Number, Object]),
-    labelProp: createPropDefine(''),
-    valueProp: createPropDefine(''),
-    emptyDisplay: createPropDefine(''),
-    treeProp: createPropDefine(''),
-    disabled: createPropDefine(false, Boolean)
-})
-const emits = defineEmits(['change', 'update:modelValue'])
-
-const instance = getCurrentInstance()
-const valueMode = computed(() => !empty(props.modelValue) || !!props.emptyDisplay)
-const display = computed(() => {
-    if (empty(props.modelValue) && !!props.emptyDisplay) return props.emptyDisplay
-    return getLabel(current.value) || props.emptyDisplay
-})
-
-const currentPath = computed(() => findTreePath(props.data, item => getValue(item) == props.modelValue, props.treeProp))
-const current = computed(() => _.last(currentPath.value))
-const columns = ref([])
-const defaultIndex = ref([])
-
-const handleClick = function () {
-  if (props.disabled) {
-    return;
-  }
-    setDefaultColumnsAndIndexs()
-    picker.value.open()
-}
-
-// columnIndex 正在改变的列 > index 改变后的序号 > indexs 当前选中的全部序号
-// > value 当前选中的全部元素 > values 当前columns
-const handleConfirm = function ({value}) {
-    const selected = _.last(value)
-    const selectedValue = getValue(selected)
-    emits('update:modelValue', selectedValue)
-    emits('change', selected)
-
-    // try trigger uv-form validate
-    autoFormValidate(instance, 'change')
-}
-
-const handleChange = function ({columnIndex, value}) {
-    if (!props.treeProp) return // 不是树形结构
-    if (columnIndex == value.length - 1) return // 操作的是最后一列
-    const changToValue = getValue(value[columnIndex])
-    const {cols, idxes} = calculateColumnsAndIndexs(changToValue)
-    console.log('calculateColumnsAndIndexs', cols, idxes)
-    columns.value = cols
-    picker.value.setIndexs(idxes, true)
-}
-
-const setDefaultColumnsAndIndexs = function () {
-    const {cols, idxes} = calculateColumnsAndIndexs(props.modelValue)
-    columns.value = cols
-    defaultIndex.value = idxes
-}
-
-/*
-* @description 根据给定的值计算picker所需要的columns和indexs
-* */
-const calculateColumnsAndIndexs = function (value, autoEnd = true) {
-    const {data, treeProp} = props
-    const cols = []
-    const idxes = []
-    const path = findTreePath(data, n => getValue(n) == value, treeProp)
-    // auto end // 指定的条件,可能查出来的是中间某列的值,此时自动帮它选中至最后一列
-    if (autoEnd && treeProp && path?.length) {
-        let final = _.last(path)
-        while (final[treeProp]?.length) {
-            final = final[treeProp][0]
-            path.push(final)
-        }
-    }
-    // modelValue logic
-    let prevNode = data
-    _.forEach(path, item => {
-        const idx = prevNode.findIndex(n => getValue(n) == getValue(item))
-        if (idx < 0) {
-            // 无效值,停止迭代
-            cols.length = 0
-            idxes.length = 0
-            return false // lodash.forEach接收到false返回会终止迭代
-        }
-        cols.push(prevNode)
-        idxes.push(idx)
-        prevNode = _.get(prevNode, idx + '.' + treeProp)
-    })
-    // default logic
-    if (!cols.length && data.length) {
-        prevNode = data
-        do {
-            cols.push(prevNode)
-            idxes.push(0)
-            prevNode = _.get(prevNode, '0.' + props.treeProp)
-        } while (prevNode)
-    }
-    return {cols, idxes}
-}
-
-const getValue = item => _.get(item, props.valueProp, item)
-const getLabel = item => _.get(item, props.labelProp, item)
-
-defineExpose({current, currentPath})
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 45
src/components/mx-popup-template/mx-popup-template.vue

@@ -1,45 +0,0 @@
-<template>
-    <uv-popup ref="popup" v-bind="props" :safe-area-inset-bottom="mode=='bottom'"
-              @change="$emit('change',$event)" @mask-click="$emit('maskClick', $event)">
-        <view :class="containerClass">
-            <view class="mb-40 text-main text-xl text-center">{{ title }}</view>
-            <view v-if="description" class="mb-30 text-content">{{ description }}</view>
-            <scroll-view scroll-y style="max-height: 50vh">
-                <slot>
-                    <uv-parse v-if="content" :content="content" container-style="color:var(--main-color)"/>
-                </slot>
-            </scroll-view>
-            <mx-bottom-buttons :left="left" :right="right" :loading="loading" @left="$emit('left')"
-                               @right="$emit('right')" class="mt-40"/>
-        </view>
-    </uv-popup>
-</template>
-
-<script setup>
-import {ref} from 'vue'
-import {popupProps} from "@/uni_modules/uv-popup/components/uv-popup/uv-popup.vue";
-import {createPropDefine} from "@/utils";
-
-const props = defineProps({
-    ...popupProps,
-    mode: createPropDefine('center'),
-    round: createPropDefine(16, [Number, String]),
-    title: createPropDefine(''),
-    description: createPropDefine(''),
-    content: createPropDefine(''),
-    // left/right 没有默认值,不会显示底部按钮
-    left: createPropDefine('取消'),
-    right: createPropDefine('保存'),
-    loading: createPropDefine(false, Boolean),
-    containerClass: createPropDefine('w-[80vw] px-30 py-40')
-})
-const emits = defineEmits(['change', 'maskClick', 'left', 'right'])
-
-const popup = ref(null)
-
-defineExpose({open: () => popup.value.open(), close: () => popup.value.close()})
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 21
src/components/mx-progress/mx-progress.vue

@@ -1,21 +0,0 @@
-<template>
-    <view class="progress-bar flex rounded" :style="{backgroundColor: bgColor}">
-        <view class="progress-bar-thumb h-full rounded" :style="{flex: progress, backgroundColor: activeColor}"></view>
-    </view>
-</template>
-
-<script setup>
-import {createPropDefine} from "@/utils";
-
-defineProps({
-    progress: createPropDefine(0, Number),
-    bgColor: createPropDefine('#CBFCEB'),
-    activeColor: createPropDefine('#0FD296')
-})
-</script>
-
-<style scoped lang="scss">
-.progress-bar-thumb {
-    transition: flex 0.5s ease;
-}
-</style>

+ 0 - 20
src/components/mx-question-content/components/mx-question-plain-option-group.vue

@@ -1,20 +0,0 @@
-<template>
-    <view class="flex flex-col">
-        <slot>
-            <text>{{ label }}</text>
-        </slot>
-    </view>
-</template>
-
-<script setup>
-import {createPropDefine} from "@/utils";
-
-defineProps({
-    name: createPropDefine(''),
-    label: createPropDefine('')
-})
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 15
src/components/mx-question-content/components/mx-question-plain-option.vue

@@ -1,15 +0,0 @@
-<template>
-    <view>
-        <slot/>
-    </view>
-</template>
-
-<script>
-export default {
-    name: "mx-question-plain-option"
-}
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 97
src/components/mx-question-content/components/mx-question-subjective.vue

@@ -1,97 +0,0 @@
-<template>
-    <view class="py-20 fx-col gap-20">
-        <view v-if="!disabled" class="fx-row fx-bet-cen">
-            <view class="fx-row">
-                <!-- NOTE:uv-subsection必须有外部宽度才能正常工作 -->
-                <mx-subsection v-model="current" :list="list" key-name="text" width="50vw" @change="handleTypeChange">
-                    <template #="{item,style}">
-                        <view class="flex flex-row items-center gap-5">
-                            <uv-icon :name="item.icon" :color="style.color"/>
-                            <text>{{ item.text }}</text>
-                        </view>
-                    </template>
-                </mx-subsection>
-            </view>
-            <uv-tags icon="reload" type="error" plain text="重做" custom-style="height:28px" @click="handleRedo"/>
-        </view>
-        <uv-textarea v-if="current==0" :model-value="modelValue" :disabled="disabled" height="150" :count="!disabled"
-                     maxlength="500" :placeholder="disabled?'':'这里输入答案'"
-                     @update:modelValue="handleAnswerChange"/>
-        <view v-else class="h-[169px] p-[10px] box-border mx-border rounded fx-col relative">
-            <text v-if="imageLimit>1" class="absolute bottom-[2px] right-[5px] text-tips text-2xs">
-                {{ attachments.length }}/{{ imageLimit }}
-            </text>
-            <view class="flex-1 grid gap-10" :class="`grid-cols-${imageLimit}`">
-                <view v-for="(i, idx) in attachments">
-                    <uv-image :src="i" width="100%" height="auto" mode="widthFix"
-                              @click="previewImage(attachments, idx)"/>
-                </view>
-                <view v-if="allowImages&&!disabled" class="fx-col fx-cen-cen">
-                    <uv-icon name="plus" size="60" color="var(--light-color)" @click="handleAddImage"/>
-                </view>
-            </view>
-        </view>
-    </view>
-</template>
-
-<script setup>
-import {ref, onMounted, computed} from 'vue'
-import {createPropDefine} from "@/utils";
-import {empty} from "@/uni_modules/uv-ui-tools/libs/function/test";
-import {toast} from "@/uni_modules/uv-ui-tools/libs/function";
-import {useChooseImage} from "@/hooks/useChooseImage";
-
-const props = defineProps({
-    modelValue: createPropDefine(''),
-    attachments: createPropDefine([], [Array, String]),
-    disabled: createPropDefine(false, Boolean)
-})
-const emits = defineEmits(['update:modelValue', 'update:attachments'])
-
-const current = ref(0)
-const imageLimit = ref(3)
-const list = [
-    {icon: 'edit-pen', text: '手动答题'},
-    {icon: 'camera', text: '拍照答题'}
-]
-const {chooseImage, previewImage} = useChooseImage()
-const noImages = computed(() => empty(props.attachments))
-const allowImages = computed(() => imageLimit.value - props.attachments.length)
-
-const handleTypeChange = (index) => {
-    if (index == 1 && noImages.value) handleAddImage()
-}
-
-const handleAddImage = () => {
-    if (allowImages.value <= 0) return toast(`最多上传${imageLimit.value}张图片!`)
-    chooseImage((result) => {
-        const arr = []
-        if (!empty(props.attachments)) arr.push(...props.attachments)
-        handleAttachmentsChange(arr.concat(result.msg))
-    }, 6, 1)
-}
-
-const handleAnswerChange = (val) => {
-    emits('update:modelValue', val)
-    emits('update:attachments', [])
-}
-
-const handleAttachmentsChange = (val) => {
-    emits('update:modelValue', '')
-    emits('update:attachments', val)
-}
-
-const handleRedo = () => {
-    emits('update:modelValue', '')
-    emits('update:attachments', [])
-}
-
-onMounted(() => {
-    // 初始化时,根据当前答题情况选中
-    if (!empty(props.attachments)) current.value = 1
-})
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 184
src/components/mx-question-content/mx-question-content.vue

@@ -1,184 +0,0 @@
-<template>
-    <view ref="questionEl" class="mx-question-content flex flex-col gap-40 text-content">
-        <view v-html="titleWithSeq"/>
-        <view class="mx-question-content-opt" :class="{'pl-30': deep>0}">
-            <template v-if="question.options?.length">
-                <slot name="options" v-bind="{options: optionsWithCode, deep, disabled}">
-                    <component :is="optGroupComponent" :modelValue="modelValue" :disabled="disabled" placement="column"
-                               :class="groupClass" @change="emits('update:modelValue', $event)">
-                        <component :is="optComponent" v-for="op in optionsWithCode" :name="op.code"
-                                   :class="calcOptionClass(op)">
-                            <view v-html="formatOption(op.option)" :class="textClass"/>
-                        </component>
-                    </component>
-                </slot>
-            </template>
-            <template v-if="question.subQuestions?.length">
-                <mx-question-content v-for="sub in question.subQuestions" :question="sub" :deep="deep+1"
-                                     :disabled="disabled">
-                    <template v-for="(fn, name) in $slots" #[name]="scope">
-                        <slot :name="name" v-bind="scope"/>
-                    </template>
-                </mx-question-content>
-            </template>
-        </view>
-        <mx-question-subjective v-if="useSubjectiveStyle" :modelValue="modelValue" :attachments="attachments"
-                                :disabled="disabled" @update:modelValue="emits('update:modelValue', $event)"
-                                @update:attachments="emits('update:attachments', $event)"/>
-    </view>
-</template>
-
-<script setup>
-import {computed, onMounted, ref, watch} from 'vue'
-import {createPropDefine} from "@/utils";
-import _ from 'lodash';
-import UvCheckboxGroup from "@/uni_modules/uv-checkbox/components/uv-checkbox-group/uv-checkbox-group.vue";
-import UvRadioGroup from "@/uni_modules/uv-radio/components/uv-radio-group/uv-radio-group.vue";
-import MxQuestionPlainOptionGroup from "@/components/mx-question-content/components/mx-question-plain-option-group.vue";
-import UvRadio from "@/uni_modules/uv-radio/components/uv-radio/uv-radio.vue";
-import UvCheckbox from "@/uni_modules/uv-checkbox/components/uv-checkbox/uv-checkbox.vue";
-import MxQuestionPlainOption from "@/components/mx-question-content/components/mx-question-plain-option.vue";
-import mxConst from "@/common/mxConst";
-import MxQuestionSubjective from "@/components/mx-question-content/components/mx-question-subjective.vue";
-import {useMathJaxService} from "@/hooks/useMathJaxService";
-import {useInjectQuestionOptionFormatter} from "@/components/mx-question-content/useQuestionOptionInjection";
-import {useInjectMathJaxSwitch} from "@/components/mx-question-content/useMathJaxSwitchInjection";
-import {func} from "@/uni_modules/uv-ui-tools/libs/function/test";
-
-/*NOTE:递归调用目前没有做事件穿透,所以子题还不支持作答
-NOTE: mx-question-content 支持MathJax
-* */
-
-const props = defineProps({
-    question: createPropDefine({}, Object),
-    deep: createPropDefine(0, Number),
-    disabled: createPropDefine(false, Boolean),
-    modelValue: createPropDefine('', [String, Number, Array]),
-    attachments: createPropDefine([], [String, Array]),
-    sysAnswer: createPropDefine('', [String, Array]),
-    readonly: createPropDefine(false, Boolean)
-})
-const emits = defineEmits(['update:modelValue', 'update:attachments'])
-
-const questionEl = ref(null)
-const {updateMathJax} = useMathJaxService()
-const {formatter, optionClass} = useInjectQuestionOptionFormatter()
-const {disabled: disabledMathJax} = useInjectMathJaxSwitch()
-
-const titleWithSeq = computed(() => {
-    const {title, seq, type, questionId} = props.question
-    const arr = []
-    if (seq) arr.push(`(${seq})`)
-    if (type) arr.push(`[${type}]`)
-    if (questionId) arr.push(`[${questionId}]`)
-    arr.push(title)
-    return arr.join('')
-})
-
-const optionsWithCode = computed(() => {
-    const {options} = props.question
-    return formatter(options)
-})
-
-const useMultipleStyle = computed(() => {
-    const {deep, readonly, question: {typeId}} = props
-    return deep == 0 && !readonly && mxConst.question.isCheckbox(typeId)
-})
-
-const useRadioStyle = computed(() => {
-    // 子题都算主观题, 学生手写作答
-    const {deep, readonly, question: {typeId}} = props
-    return deep == 0 && !readonly && mxConst.question.isRadio(typeId)
-})
-
-const useSubjectiveStyle = computed(() => {
-    // 一级题的主观题渲染输入框
-    const {deep, readonly, question: {typeId}} = props
-    return deep == 0 && !readonly && !mxConst.question.isObjective(typeId)
-})
-
-const optGroupComponent = computed(() => {
-    if (useRadioStyle.value) return UvRadioGroup
-    if (useMultipleStyle.value) return UvCheckboxGroup
-    return MxQuestionPlainOptionGroup // 为了对齐UvCheckboxGroup/UvRadioGroup
-})
-
-const optComponent = computed(() => {
-    if (useRadioStyle.value) return UvRadio
-    if (useMultipleStyle.value) return UvCheckbox
-    return MxQuestionPlainOption // 为了对齐UvCheckbox/UvRadio
-})
-
-const groupClass = computed(() => {
-    return ['gap-20']
-})
-
-const textClass = computed(() => {
-    return []
-})
-
-onMounted(() => updateMathJaxIfNeeded())
-watch(() => props.question, () => updateMathJaxIfNeeded())
-
-const updateMathJaxIfNeeded = () => {
-    if (disabledMathJax) return
-    updateMathJax(questionEl.value?.$el)
-}
-
-const calcOptionClass = function (op) {
-    const defaultClass = 'px-15 py-20 border border-solid rounded'
-    const injectClass = func(optionClass) ? optionClass(props.question) : optionClass
-    const classes = [injectClass === undefined ? defaultClass : injectClass]
-    if (props.sysAnswer) {
-        // 如果有传入标准答案,则突出显示正确错误
-        if (op.code == props.modelValue || props.modelValue.includes(op.code)) {
-            // classes.push('border-primary')
-            if (op.code != props.sysAnswer && !props.sysAnswer.includes(op.code)) {
-                // 答错了
-                classes.push('bg-[#f9e6e6] text-[#571515] border-[#e6c3c3]')
-            }
-        }
-        if (op.code == props.sysAnswer || props.sysAnswer.includes(op.code)) {
-            // 正确答案
-            classes.push('bg-[#e0f9e6] text-[#155724] border-[#c3e6cb]')
-        }
-    } else {
-        classes.push('border-border')
-    }
-    return classes
-}
-
-const tryRandomAnswer = () => {
-    if (useRadioStyle.value) {
-        // 随机选1个
-        const rd = _.sample(optionsWithCode.value)
-        emits('update:modelValue', rd.code)
-    } else if (useMultipleStyle.value) {
-        // 随机选N个
-        const num = _.random(1, optionsWithCode.value.length + 1)
-        const rd = _.sampleSize(optionsWithCode.value, num)
-        // 与原选项保持顺序并提取code
-        const codes = optionsWithCode.value.filter(i => rd.includes(i)).map(i => i.code)
-        emits('update:modelValue', codes)
-    }
-}
-
-const formatOption = (option) => {
-  // 替换 option 中可能存在的A.这样的序号
-    return option.replace(/A\./g, '')
-    .replace(/B\./g, '')
-    .replace(/C\./g, '')
-    .replace(/D\./g, '')
-    .replace(/E\./g, '')
-    .replace(/F\./g, '')
-    .replace(/G\./g, '')
-}
-
-defineExpose({tryRandomAnswer})
-</script>
-
-<style scoped lang="scss">
-.mx-question-content + .mx-question-content {
-    margin-top: 40rpx;
-}
-</style>

+ 0 - 14
src/components/mx-question-content/useMathJaxSwitchInjection.js

@@ -1,14 +0,0 @@
-import {injectLocal, provideLocal} from "@vueuse/core";
-
-const key = Symbol('MATH_JAX_SWITCH')
-
-export const useProvideMathJaxSwitch = (disabled = false) => {
-    const options = {disabled}
-    provideLocal(key, options)
-    return options
-}
-
-export const useInjectMathJaxSwitch = () => {
-    // 默认是开放公式的,除非明确不会用到公式,否则不用主动注入该开关
-    return injectLocal(key, {disabled: false})
-}

+ 0 - 25
src/components/mx-question-content/useQuestionOptionInjection.js

@@ -1,25 +0,0 @@
-import {func} from "@/uni_modules/uv-ui-tools/libs/function/test";
-import {injectLocal, provideLocal} from "@vueuse/core";
-import {numberToLetter} from "@/utils";
-
-const key = Symbol('QUESTION_OPTION_SERVICE')
-
-export const useProvideQuestionOptionFormatter = function (formatter, optionClass) {
-    if (!func(formatter)) throw new Error('formatter must be a function')
-    const options = {formatter, optionClass}
-    provideLocal(key, options)
-    return options
-}
-
-export const useInjectQuestionOptionFormatter = function () {
-    // 提供了默认注入,默认注入是题库的组织格式
-    return injectLocal(key, {
-        formatter: (options) => {
-            return options.map((opt, idx) => {
-                const code = numberToLetter(idx)
-                return {code, option: code + '、' + opt}
-            })
-        },
-        optionClass: undefined
-    })
-}

+ 0 - 36
src/components/mx-question/components/mx-question-collect.vue

@@ -1,36 +0,0 @@
-<template>
-    <mx-tag-button :text="collected?'已收藏':'收藏'" :icon="collected?'heart-fill':'heart'" @click="handleToggle"/>
-</template>
-
-<script setup>
-import {computed} from 'vue'
-import {createPropDefine} from "@/utils";
-import {toast} from "@/uni_modules/uv-ui-tools/libs/function";
-import {questionCancelCollect, questionCollect} from "@/api/webApi/webQue";
-
-const props = defineProps({
-    question: createPropDefine({}, Object)
-})
-const emits = defineEmits(['change'])
-
-const id = computed(() => props.question.questionId || props.question.id)
-const collected = computed(() => props.question.collect)
-let loading = false
-
-const handleToggle = async () => {
-    if (loading) return toast('请稍候')
-    try {
-        loading = true
-        const op = collected.value ? questionCancelCollect : questionCollect
-        await op(id.value)
-        props.question.collect = !collected.value
-        emits('change', props.question.collect)
-    } finally {
-        loading = false
-    }
-}
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 51
src/components/mx-question/components/mx-question-correct-popup.vue

@@ -1,51 +0,0 @@
-<template>
-    <mx-popup-template ref="popup" title="问题纠错" right="提交" @left="close" @right="handleSubmit">
-        <uv-form ref="form" :model="model" :rules="rules">
-            <mx-form-item v-model="model.questionid" disabled label="问题编号"/>
-            <uv-form-item prop="remark">
-                <uv-textarea v-model="model.remark" count maxlength="200" placeholder="在这里填写问题描述"/>
-            </uv-form-item>
-        </uv-form>
-    </mx-popup-template>
-</template>
-
-<script setup>
-import {ref} from 'vue'
-import {correctQuestion} from "@/api/webApi/front";
-import {toast} from "@/uni_modules/uv-ui-tools/libs/function";
-
-const popup = ref(null)
-const form = ref(null)
-const model = ref({questionid: '', remark: ''})
-const loading = ref(false)
-const rules = {
-    remark: [{required: true, message: '问题描述不能为空'}, {max: 200, message: '问题描述不超过200字'}]
-}
-
-const open = (question) => {
-    const id = question.questionId || question.id
-    if (id != model.value.questionid) {
-        // reset when question changed
-        model.value.questionid = id
-        model.value.remark = ''
-    }
-    popup.value.open()
-}
-
-const close = () => {
-    popup.value.close()
-}
-
-const handleSubmit = async () => {
-    await form.value.validate()
-    await correctQuestion(model.value)
-    toast('提交成功,等待工作人员处理')
-    model.value.remark = '' // clean if success commit.
-    close()
-}
-defineExpose({open, close})
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 26
src/components/mx-question/components/mx-question-correct.vue

@@ -1,26 +0,0 @@
-<template>
-    <mx-tag-button text="纠错" icon="info-circle" @click="openCorrectPopup"/>
-</template>
-
-<script setup>
-import {computed} from 'vue'
-import {createPropDefine} from "@/utils";
-import {useSingletonCorrectQuestion} from "@/hooks/useSingletonComponent";
-
-const props = defineProps({
-    question: createPropDefine({}, Object)
-})
-const emits = defineEmits(['change'])
-
-const id = computed(() => props.question.questionId || props.question.id)
-
-const openCorrectPopup = () => {
-    if (!id.value) return
-    const correctPopup = useSingletonCorrectQuestion()
-    correctPopup.open(props.question)
-}
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 19
src/components/mx-question/components/mx-question-navigator.vue

@@ -1,19 +0,0 @@
-<template>
-    <view class="h-[50px] grid grid-cols-4 items-center bg-bg">
-        <mx-question-collect :question="current"/>
-        <mx-question-correct :question="current"/>
-        <mx-tag-button :type="hasPrev?'primary':'info'" icon="arrow-left" text="上一题" @click="tryGoPrev"/>
-        <mx-tag-button :type="hasNext?'primary':'info'" icon="arrow-right" text="下一题" @click="tryGoNext"/>
-    </view>
-</template>
-
-<script setup>
-import {useInjectPaperService} from "@/components/mx-paper/usePaperInjection";
-import MxQuestionCorrect from "@/components/mx-question/components/mx-question-correct.vue";
-import MxQuestionCollect from "@/components/mx-question/components/mx-question-collect.vue";
-
-const {current, hasNext, hasPrev, tryGoNext, tryGoPrev} = useInjectPaperService()
-</script>
-
-<style scoped lang="scss">
-</style>

+ 0 - 55
src/components/mx-question/components/mx-question-parse.vue

@@ -1,55 +0,0 @@
-<template>
-    <view ref="parseEl" class="mx-question-parse fx-col text-content gap-40">
-        <view v-if="question.knowledge">
-            <view class="text-primary">[知识点]</view>
-            <view class="mt-10" v-html="question.knowledge"/>
-        </view>
-        <view v-if="sysAnswer">
-            <view class="text-primary">[正确答案]</view>
-            <view class="mt-10" v-html="sysAnswer"/>
-        </view>
-        <view v-if="question.parse">
-            <view class="text-primary">[解析]</view>
-            <view class="mt-10" v-html="question.parse"/>
-        </view>
-    </view>
-</template>
-
-<script setup>
-import {computed, onMounted, ref, watch} from 'vue'
-import {createPropDefine} from "@/utils";
-import {array, empty} from "@/uni_modules/uv-ui-tools/libs/function/test";
-import {useMathJaxService} from "@/hooks/useMathJaxService";
-import {useInjectMathJaxSwitch} from "@/components/mx-question-content/useMathJaxSwitchInjection";
-
-/*
-NOTE: mx-question-parse 支持MathJax
-* */
-
-const props = defineProps({
-    question: createPropDefine({}, Object)
-})
-
-const parseEl = ref(null)
-const {updateMathJax} = useMathJaxService()
-const {disabled: disabledMathJax} = useInjectMathJaxSwitch()
-
-const sysAnswer = computed(() => {
-    // if (array(props.question.answers) && !empty(props.question.answers)) {
-    //     return props.question.answers[0]
-    // }
-    return props.question.answer1 || ''
-})
-
-onMounted(() => updateMathJaxIfNeeded())
-watch(() => props.question, () => updateMathJaxIfNeeded())
-
-const updateMathJaxIfNeeded = () => {
-    if (disabledMathJax) return
-    // updateMathJax(parseEl.value?.$el)
-}
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 52
src/components/mx-question/components/mx-question-score-subjective.vue

@@ -1,52 +0,0 @@
-<template>
-    <view class="h-[50px] px-30 fx-row fx-bet-cen bg-bg">
-        <view class="text-tips text-sm">请为该题评分</view>
-        <view class="fx-row items-center text-content gap-10">
-            <text>错误</text>
-            <uv-switch v-model="wrongSwitchOn" active-color="var(--error-color)" @change="handleWrong"/>
-            <text class="ml-10">正确</text>
-            <uv-switch v-model="correctSwitchOn" active-color="var(--success-color)" @change="handleCorrect"/>
-        </view>
-    </view>
-</template>
-
-<script setup>
-import {ref, watch} from 'vue'
-import {useInjectQuestionService} from "@/components/mx-question/useQuestionInjection";
-import {useInjectPaperService} from "@/components/mx-paper/usePaperInjection";
-
-const {current} = useInjectPaperService()
-const {getUserData, isScored, isPassed} = useInjectQuestionService()
-
-const wrongSwitchOn = ref(false)
-const correctSwitchOn = ref(false) // 通过只需要1个判定就可以了
-
-watch(current, () => {
-    wrongSwitchOn.value = isScored(current.value) && !isPassed(current.value)
-    correctSwitchOn.value = isPassed(current.value)
-}, {immediate: true})
-
-const handleWrong = async () => {
-    const userData = getUserData(current.value)
-    if (wrongSwitchOn.value) {
-        correctSwitchOn.value = false
-        userData.score = 0
-    } else {
-        userData.score = ''
-    }
-}
-
-const handleCorrect = async () => {
-    const userData = getUserData(current.value)
-    if (correctSwitchOn.value) {
-        wrongSwitchOn.value = false
-        userData.score = current.value.scoreTotal * 1
-    } else {
-        userData.score = ''
-    }
-}
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 123
src/components/mx-question/components/mx-question-statistic.vue

@@ -1,123 +0,0 @@
-<template>
-    <view class="h-[50px] px-30 bg-bg flex items-end">
-        <view class="flex-1 grid items-center gap-30" :class="`grid-cols-${tags.length}`">
-            <view v-for="tag in tags" :class="tag.clazz" @click="tag.handler"
-                  class="h-[44px] fx-row fx-cen-cen gap-5 shadow-sm bg-white rounded">
-                <text class="text-3xs text-tips -mb-10">{{ tag.label }}</text>
-                <text class="text-2xl" :class="tag.valueClazz">{{ tag.value }}</text>
-            </view>
-        </view>
-    </view>
-</template>
-
-<script setup>
-import {ref, watch} from 'vue'
-import {useInjectPaperService} from "@/components/mx-paper/usePaperInjection";
-import {useInjectQuestionService} from "@/components/mx-question/useQuestionInjection";
-import {fnPlaceholder} from "@/utils/uni-helper";
-import _ from "lodash";
-import {useInjectPaperNavigatorRef} from "@/components/mx-paper/components/usePaperNavigatorRefInjection";
-import {toast} from "@/uni_modules/uv-ui-tools/libs/function";
-import {useInjectPaperNavigationService} from "@/components/mx-paper/usePaperNavigationServiceInjection";
-
-const {
-    allowAnswer,
-    allowScore,
-    questions
-} = useInjectPaperService()
-const {
-    answeredQuestions,
-    unansweredQuestions,
-    scoredQuestions,
-    unscoredQuestions,
-    passedQuestions,
-    unpassedQuestions,
-} = useInjectQuestionService()
-const {navigator} = useInjectPaperNavigatorRef()
-const {questionsNavigationHandler} = useInjectPaperNavigationService()
-
-const tags = ref([])
-
-watch([answeredQuestions, unansweredQuestions,
-    scoredQuestions, unscoredQuestions,
-    passedQuestions, unpassedQuestions,
-    allowAnswer, allowScore], ([answered, unanswered, scored, unscored, passed, unpassed]) => {
-    tags.value.length = 0 // reset
-    if (allowAnswer.value) {
-        tags.value.push({
-            label: '已做',
-            value: answered.length,
-            valueClazz: ['text-primary'],
-            handler: () => questionsNavigationHandler(navigator.value, answered, '已做')
-        })
-        tags.value.push({
-            label: '未做',
-            value: unanswered.length,
-            valueClazz: ['text-warning'],
-            handler: () => questionsNavigationHandler(navigator.value, unanswered, '未做')
-        })
-        let rate = 0
-        const total = questions.value.length
-        if (total > 0) rate = Math.round(answered.length * 100 / total)
-        tags.value.push({
-            label: '完成率',
-            value: rate + '%',
-            valueClazz: ['text-success'],
-            handler: fnPlaceholder
-        })
-    } else if (allowScore.value) {
-        tags.value.push({
-            label: '已阅',
-            value: scored.length,
-            valueClazz: ['text-primary'],
-            handler: () => questionsNavigationHandler(navigator.value, scored, '已阅')
-        })
-        tags.value.push({
-            label: '未阅',
-            value: unscored.length,
-            valueClazz: ['text-warning'],
-            handler: () => questionsNavigationHandler(navigator.value, unscored, '未阅')
-        })
-        tags.value.push({
-            label: '正确',
-            value: passed.length,
-            valueClazz: ['text-success'],
-            handler: () => questionsNavigationHandler(navigator.value, passed, '正确')
-        })
-        tags.value.push({
-            label: '错误',
-            value: unpassed.length,
-            valueClazz: ['text-error'],
-            handler: () => questionsNavigationHandler(navigator.value, unpassed, '错误')
-        })
-    } else {
-        tags.value.push({
-            label: '正确',
-            value: passed.length,
-            valueClazz: ['text-success'],
-            handler: () => questionsNavigationHandler(navigator.value, passed, '正确')
-        })
-        tags.value.push({
-            label: '错误',
-            value: unpassed.length,
-            valueClazz: ['text-error'],
-            handler: () => questionsNavigationHandler(navigator.value, unpassed, '错误')
-        })
-        let rate = 0
-        const sumTotal = _.sumBy(questions.value, q => q.scoreTotal * 1)
-        const sumScore = _.sumBy(scored, q => q.score * 1)
-        if (sumTotal > 0) rate = Math.round(sumScore * 100 / sumTotal)
-        tags.value.push({
-            label: '得分率',
-            value: rate + '%',
-            valueClazz: ['text-primary'],
-            handler: fnPlaceholder
-        })
-    }
-})
-
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 65
src/components/mx-question/mx-question.vue

@@ -1,65 +0,0 @@
-<template>
-    <view class="mx-question tabs-swiper-content">
-        <mx-question-content ref="q" v-model="userData.answer" :disabled="!allowAnswer" :sys-answer="sysAnswerIf"
-                             v-model:attachments="userData.attachments" :question="question" class="p-40"/>
-        <mx-question-parse v-if="!allowAnswer" :question="question" class="p-40"/>
-    </view>
-</template>
-
-<script setup>
-import {computed, watch, ref, nextTick} from 'vue'
-import {createPropDefine} from "@/utils";
-import {useInjectQuestionService} from "@/components/mx-question/useQuestionInjection";
-import {useInjectPaperService} from "@/components/mx-paper/usePaperInjection";
-import {array} from "@/uni_modules/uv-ui-tools/libs/function/test";
-import MxQuestionParse from "@/components/mx-question/components/mx-question-parse.vue";
-import {sleep} from "@/uni_modules/uv-ui-tools/libs/function";
-
-const props = defineProps({
-    question: createPropDefine({}, Object),
-    autoNext: createPropDefine(true, Boolean),
-    // 自动随机答题,有时候题太多了,测试不方便,注意随时关闭 !important.
-    autoAnswer: createPropDefine(false, Boolean)
-})
-
-const q = ref(null)
-const {isRadio, tryGoNext, index, current, questions, hasNext, allowAnswer} = useInjectPaperService()
-const {getUserData, pushChunk, isAnswered, isScored} = useInjectQuestionService()
-const userData = computed(() => getUserData(props.question) || {})
-const sysAnswerIf = computed(() => allowAnswer.value ? '' : array(props.question.answers) ? props.question.answers[0] : '')
-
-watch(index, async () => {
-    if (!props.autoAnswer) return
-    if (isAnswered(current)) return
-    await nextTick()
-    q.value?.tryRandomAnswer()
-}, {immediate: true})
-
-watch([() => userData.value.answer, () => userData.value.attachments], async () => {
-    pushChunk(userData.value)
-    // 自动前往一下题
-    if (props.autoNext && isRadio(props.question) && hasNext.value) {
-        const next = questions.value[index.value + 1]
-        if (!isAnswered(next)) {
-            await sleep(300)
-            tryGoNext()
-        }
-    }
-})
-
-watch(() => userData.value.score, async () => {
-    pushChunk(userData.value)
-    // 自动前往下一题
-    if (props.autoNext && hasNext.value) {
-        const next = questions.value[index.value + 1]
-        if (!isScored(next)) {
-            await sleep(300)
-            tryGoNext()
-        }
-    }
-})
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 286
src/components/mx-question/useQuestionInjection.js

@@ -1,286 +0,0 @@
-import {computed, ref, watch} from 'vue'
-import {injectLocal, provideLocal} from "@vueuse/core";
-import {fnPlaceholder} from "@/utils/uni-helper";
-import _ from "lodash";
-import {array, empty, number} from "@/uni_modules/uv-ui-tools/libs/function/test";
-
-const key = Symbol('QUESTION_SERVICE')
-
-// 从外部提供一个存储答题和提交的触发机制
-// questionService依赖于paperService,主要用来分担一部分paperService的功能
-// 与答案和打分相关的一些API集中在了这个服务
-// 尽量保持只需要依靠paper/paper.questions中计算的相关API在paperService中
-// 与userData计算相关的API集中的questionService中
-export const useProvideQuestionService = function (
-    paperService,
-    chunkSize = 10,
-    enableDuration = true,
-    overrideService = {}) {
-    const {
-        allowAnswer,
-        allowScore,
-        index,
-        questions,
-        questionsMap,
-        isCheckbox,
-        commitQuestion,
-        scoreQuestion,
-        isObjective,
-        isAnswerCorrect,
-        goToQuestion,
-        scorePaper
-    } = paperService
-
-    const container = ref({})
-    const committing = ref([])
-    const answerProps = ref(['answer', 'attachments', 'duration', 'questionId'])
-    const scoreProps = ref(['answer', 'attachments', 'score', 'questionId']) // 这个answer/attachments是显示用的,不会提交
-    const errorCount = ref(0)
-    const chunk = computed(() => (errorCount.value + 1) * chunkSize)
-    const timestampMap = new Map() // 另一个结构体存放时间,不污染container中的userData
-    const errorLimit = 3 // 如果提交API连续超过3次失败,则不用再重试了
-
-    const createUserDataFn = computed(() => {
-        if (allowAnswer.value && allowScore.value)
-            throw new Error('allowAnswer,allowScore can not be `true` at the same time!')
-        if (!allowAnswer.value && !allowScore.value) return (q) => q // 原样返回,因为不会涉及到更改
-
-        if (allowAnswer.value) return (question) => _.pick(question, answerProps.value)
-        if (allowScore.value) return (question) => _.pick(question, scoreProps.value)
-    })
-
-    const getUserData = function (q) {
-        let userData = container.value[q.questionId]
-        if (userData) return userData
-        userData = createUserDataFn.value(q)
-        encodeToUserData(q, userData)
-        container.value[q.questionId] = userData
-        return userData
-    }
-
-    const cleanUserData = function () {
-        container.value = {}
-        committing.value = []
-        timestampMap.clear()
-    }
-
-    const isAnswered = function (q) {
-        const data = getUserData(q)
-        return !empty(data.answer) || !empty(data.attachments)
-    }
-
-    const isScored = function (q) {
-        const data = getUserData(q)
-        return number(data.score)
-    }
-
-    const answeredPartition = computed(() => _.partition(questions.value, q => isAnswered(q)))
-    const answeredQuestions = computed(() => answeredPartition.value[0])
-    const unansweredQuestions = computed(() => answeredPartition.value[1])
-    const isAllAnswered = computed(() => questions.value.length == answeredQuestions.value.length)
-
-    const scoredPartition = computed(() => _.partition(questions.value, q => isScored(q)))
-    const scoredQuestions = computed(() => scoredPartition.value[0])
-    const unscoredQuestions = computed(() => scoredPartition.value[1])
-    const isAllScored = computed(() => questions.value.length == scoredQuestions.value.length)
-
-    const passedPartition = computed(() => _.partition(scoredQuestions.value, q => isPassed(q)))
-    const passedQuestions = computed(() => passedPartition.value[0])
-    const unpassedQuestions = computed(() => passedPartition.value[1])
-
-    const isCompleteLocal = (q) =>
-        (allowAnswer.value && isAnswered(q)) ||
-        (allowScore.value && isScored(q))
-    const isAllLocalComplete = computed(() =>
-        (allowAnswer.value && isAllAnswered.value) ||
-        (allowScore.value && isAllScored.value)
-    )
-    const allUncompleteLocalQuestions = computed(() => {
-        if (allowAnswer.value) return unansweredQuestions.value
-        if (allowScore.value) return unscoredQuestions.value
-        return []
-    })
-
-    const isPassed = (q) => {
-        const userData = getUserData(q)
-        if (!number(userData.score)) return false
-        return userData.score >= q.scoreTotal * 0.8
-    }
-
-    const encodeToUserData = (q, userData) => {
-        // 与decodeFromUserData对应,有些字段,前后台需要转义
-        // 多选兼容视图类型,要注意这里,转换answer是因为组件不支持string类型的传型,提交时会适配后台
-        if (isCheckbox(q)) userData.answer = empty(userData.answer) ? [] : q.answer.split(',')
-        // attachments做兼容性转换
-        userData.attachments = empty(userData.attachments)
-            ? []
-            : array(userData.attachments)
-                ? userData.attachments
-                : q.attachments.split(',')
-    }
-
-    const decodeFromUserData = (userData) => {
-        // 与encodeToUserData对应,提交或反向赋值时要使用
-        if (allowAnswer.value) {
-            return {
-                ...userData,
-                answer: userData.answer?.toString(), // 兼容多选
-                attachments: userData.attachments // 图片提交时不用转
-            }
-        }
-        if (allowScore.value) {
-            const copy = {...userData}
-            delete copy.answer // 打分时不需要提交答案
-            delete copy.attachments
-            return copy
-        }
-        return userData
-    }
-
-    /*@description 本地提交答案,达到chunkSize时自动向服务端同步
-    * */
-    const pushChunk = function (userData) {
-        // 结算duration
-        if (allowAnswer.value && enableDuration) {
-            const duration = userData.duration || 0
-            const diffSeconds = (new Date().getTime() - timestampMap.get(userData.questionId)) / 1000
-            userData.duration = Math.round(duration + diffSeconds)
-        }
-
-        // chunk 机制只关心分段提交行为,并不关心提交的数据值
-        // 因为userData在container中是唯一引用,所以用户的答题或者打分可能会反复保存
-        // 但只要保持最后一次操作后,有提交行为,即可保证userData均与服务端同步了
-        if (committing.value.includes(userData)) return
-        committing.value.push(userData)
-
-        // chunk = 0 不执行分段提交,要等forceChunk调用
-        if (chunk.value
-            && errorCount.value <= errorLimit
-            && committing.value.length >= chunk.value) {
-            return doChunk()
-        }
-    }
-
-    const rollbackChunk = function (commits) {
-        // 不触发,只是回加,doChunk等一下次自然机会
-        _.remove(committing.value, item => commits.includes(item))
-        committing.value.push(...commits)
-    }
-
-    /*
-    * @description 同步本地数据到服务端
-    * */
-    const doChunk = function () {
-        if (empty(committing.value)) return
-
-        // prepare commit data, 需要兼容多选,所以重组了对象,后面使用的时候要小心引用变更
-        const rawCommits = [...committing.value]
-        const commits = rawCommits.map(decodeFromUserData)
-        committing.value.length = 0
-
-        const commitFn = allowAnswer.value
-            ? commitQuestion
-            : allowScore.value
-                ? scoreQuestion
-                : fnPlaceholder
-        return commitFn(commits)
-            .then(res => {
-                errorCount.value = 0
-
-                // sync props to realtime
-                commits.forEach(userData => {
-                    const raw = questionsMap.value[userData.questionId]
-                    _.assign(raw, userData)
-                })
-
-            })
-            .catch(e => {
-                console.log('commitQuestion failed', e, rawCommits)
-                errorCount.value += 1 // 扩容延缓提交
-
-                // rollback to committing
-                rollbackChunk(rawCommits)
-                throw e // 原样抛出,使得外部调用await doChunk时能正常中断
-            })
-    }
-
-    const getAllCommits = () => questions.value.map(q => userDataToCommit(getUserData(q)))
-
-    // hooks
-    // 完成答题计时功能
-    // NOTE:如果页面上要显示计时,只需要在当前题上,从userData.duration按秒计数即可。
-    watch([index, questions], ([newIndex], [oldIndex]) => {
-        if (enableDuration && allowAnswer.value) { // 只有答题开放此功能
-            if (oldIndex != undefined) {
-                // 旧题清空时间戳
-                const old = questions.value[oldIndex]
-                if (old) timestampMap.delete(old.questionId)
-            }
-            // 新进入的题重新打时间戳
-            // 如果在这期间,用户有pushChunk行为,则会结算duration
-            const q = questions.value[newIndex]
-            timestampMap.set(q.questionId, new Date().getTime())
-        }
-    })
-
-    // 完成客观题自动评分
-    // 如果全是客观题,直接自动完成阅卷
-    watch([allowScore, questions], async () => {
-        if (allowScore.value) {
-            const targets = questions.value
-                .filter(q => isObjective(q) && !isScored(q))
-            if (targets.length) {
-                // 提交所有客观题
-                const commits = targets.map(q => {
-                    const userData = getUserData(q)
-                    userData.score = isAnswerCorrect(q) ? q.scoreTotal * 1 : 0
-                    return userData
-                })
-                rollbackChunk(commits)
-                await doChunk()
-            }
-            const nextTargets = unscoredQuestions.value
-            if (nextTargets.length) {
-                goToQuestion(_.first(nextTargets))
-            } else if (isAllScored) {
-                // 如果全是客观题,直接结束阅卷
-                await scorePaper()
-                index.value = 0 // 回到第1题
-            }
-        }
-    })
-
-    const options = {
-        container,
-        committing,
-        createUserDataFn,
-        cleanUserData,
-        getUserData,
-        getAllCommits,
-        pushChunk,
-        doChunk,
-        isAnswered,
-        isScored,
-        isAllAnswered,
-        isAllScored,
-        isCompleteLocal,
-        isAllLocalComplete,
-        isPassed,
-        answeredQuestions,
-        unansweredQuestions,
-        scoredQuestions,
-        unscoredQuestions,
-        passedQuestions,
-        unpassedQuestions,
-        allUncompleteLocalQuestions,
-        encodeToUserData,
-        decodeFromUserData,
-        ...overrideService
-    }
-    provideLocal(key, options)
-    return options
-}
-
-export const useInjectQuestionService = function () {
-    return injectLocal(key)
-}

+ 0 - 38
src/components/mx-question/useQuestionTranslate.js

@@ -1,38 +0,0 @@
-import {array} from "@/uni_modules/uv-ui-tools/libs/function/test";
-
-export function useQuestionTranslate(question) {
-    // 将旧的题属性转换为新题属性,以方便
-    // 使用mx-question-content 和mx-question-parse
-    // 自动获得MathJax支持
-    const makeOptions = () => {
-        const result = []
-        if (array(question.options)) result.push(...question.options)
-        const opts = Object.keys(question)
-            .filter(k => k.startsWith('option') && k != 'options' && question[k])
-            .sort()
-            .map(k => question[k])
-        result.push(...opts)
-        return result
-    }
-
-    const fixTypeId = (q) => {
-        if (q.typeId) return
-        if (['单选题', '选择题', '判断题'].includes(q.type)) return q.typeId = 1
-        if (['多选题'].includes(q.type)) return q.typeId = 3
-    }
-
-    const formatted = {
-        ...question,
-        questionId: question.id || question.questionId,
-        type: question.qtpye || question.type,
-        options: makeOptions(),
-        // answers: [question.answer1, question.answer2],
-        answers: question.answers,
-        knowledge: question.knownledgeName || question.knowledges || question.knowledge,
-        _raw: question
-    }
-
-    fixTypeId(formatted)
-
-    return formatted
-}

+ 0 - 46
src/components/mx-search/mx-search.vue

@@ -1,46 +0,0 @@
-<template>
-    <uv-search v-bind="{...props, ...attrs}">
-        <template v-for="name in Object.keys($slots)" #[name]>
-            <slot :name="name"/>
-        </template>
-        <template v-if="!$slots.suffix" #suffix>
-            <uv-button type="primary" shape="circle" size="mini" :text="actionText" @click="handleSearch"/>
-        </template>
-    </uv-search>
-</template>
-
-<script setup>
-import {useAttrs} from 'vue';
-import {createPropDefine} from "@/utils";
-import {func} from "@/uni_modules/uv-ui-tools/libs/function/test";
-import {searchProps} from "@/uni_modules/uv-search/components/uv-search/uv-search.vue";
-
-// 事件会自然穿透,因为默认inheritAttrs=true
-// 因为外部事件会被分别为attrs,但如果这里也申明事件,则attrs中就会接收不到
-const props = defineProps({
-    ...searchProps,
-    showAction: createPropDefine(false, Boolean)
-})
-const attrs = useAttrs()
-
-const handleSearch = () => {
-    // 因为这里用了事件穿透的方式,这里就不要emit外抛了,直接调用attrs中的方法就可以了
-    if (func(attrs.onSearch)) attrs.onSearch()
-}
-</script>
-
-<style scoped lang="scss">
-::v-deep(.uv-search) {
-    .uv-search__content {
-        padding-right: 3px;
-
-        .uv-button--mini {
-            height: 28px;
-        }
-
-        .uv-button__text {
-            font-size: 12px !important;
-        }
-    }
-}
-</style>

+ 0 - 63
src/components/mx-steps/mx-steps.vue

@@ -1,63 +0,0 @@
-<template>
-    <uv-steps :current="step" dot>
-        <uv-steps-item v-for="(s, i) in steps" :title="getTitle(s)" :class="{'step-passed': i<=step}"/>
-    </uv-steps>
-</template>
-
-<script setup>
-import {createPropDefine} from "@/utils";
-
-const props = defineProps({
-    steps: createPropDefine([], Array),
-    step: createPropDefine(0, Number),
-    keyName: createPropDefine('')
-})
-
-const getTitle = (item) => props.keyName ? item[props.keyName] : item
-</script>
-
-<style scoped lang="scss">
-::v-deep(.uv-steps) {
-    .uv-steps-item__line--row {
-        top: 12px !important;
-    }
-
-    .uv-steps-item__wrapper--row--dot {
-        width: 25px !important;
-        height: 25px !important;
-        background-color: transparent !important;
-
-        .uv-steps-item__wrapper__dot {
-            width: 15px !important;
-            height: 15px !important;
-        }
-    }
-
-    .uv-steps-item__content {
-        .uv-text__value {
-            font-size: 14px !important;
-        }
-    }
-
-    .step-passed {
-        .uv-steps-item__wrapper__dot {
-            position: relative;
-
-            &::before {
-                content: ' ';
-                position: absolute;
-                background: #FFFFFF;
-                left: 5px;
-                top: 5px;
-                border-radius: 2.5px;
-                width: 5px;
-                height: 5px;
-            }
-        }
-
-        .uv-steps-item__content .uv-text__value {
-            color: var(--primary-color);
-        }
-    }
-}
-</style>

+ 0 - 33
src/components/mx-submit-layout/mx-submit-layout.vue

@@ -1,33 +0,0 @@
-<template>
-    <view class="page-content">
-        <mx-nav-bar :title="title" v-bind="barOptions"/>
-        <slot name="prepend"/>
-        <view class="m-40 p-40 mx-card bg-white">
-            <slot/>
-        </view>
-        <slot name="append"/>
-        <view class="mt-20 mb-40 fx-col fx-cen-cen gap-40">
-            <slot name="prefix"/>
-            <uv-button :loading="loading" type="primary" shape="circle" size="large" :text="buttonText"
-                       color="linear-gradient(to right, var(--primary-light-color), var(--primary-deep-color))"
-                       :custom-style="{width: '55vw', height: '44px'}" @click="$emit('submit')"/>
-            <slot name="suffix"/>
-        </view>
-    </view>
-</template>
-
-<script setup>
-import {createPropDefine} from "@/utils";
-
-defineProps({
-    title: createPropDefine(''),
-    buttonText: createPropDefine('保存'),
-    loading: createPropDefine(false, Boolean),
-    barOptions: createPropDefine({}, Object)
-})
-defineEmits(['submit'])
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 44
src/components/mx-subsection/mx-subsection.vue

@@ -1,44 +0,0 @@
-<template>
-    <uv-subsection v-bind="{...props, ...override}" :style="{width: width}">
-        <template v-for="name in Object.keys($slots)" #[name]="scope">
-            <slot :name="name" v-bind="scope"/>
-        </template>
-    </uv-subsection>
-</template>
-
-<script setup>
-import {useAttrs, ref, watch, computed} from 'vue';
-import {createPropDefine} from "@/utils";
-import {func} from "@/uni_modules/uv-ui-tools/libs/function/test";
-import {subsectionProps} from "@/uni_modules/uv-subsection/components/uv-subsection/uv-subsection.vue";
-
-// 如果不禁止,uv-subsection会自动处理外部onChange,即可能造成两次调用
-defineOptions({inheritAttrs: false})
-
-// 将current响应改为modelValue
-const copyProps = {...subsectionProps}
-delete copyProps.current
-const props = defineProps({
-    ...subsectionProps,
-    modelValue: createPropDefine(0, Number),
-    customStyle: createPropDefine('height:36px;border-radius:18px'),
-    customItemStyle: createPropDefine('border-radius:15px'),
-    width: createPropDefine('')
-})
-const emits = defineEmits(['update:modelValue'])
-const attrs = useAttrs()
-
-const override = computed(() => ({
-    current: props.modelValue, // 内部消化了current
-    onChange: (index) => {
-        emits('update:modelValue', index)
-        // 如果外部绑定了@change,原样调用,不去影响它
-        if (func(attrs.onChange) && index != props.modelValue) attrs.onChange(index)
-    }
-}))
-
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 135
src/components/mx-tabs-swiper/mx-tabs-swiper.vue

@@ -1,135 +0,0 @@
-<template>
-  <!-- NOTE:min-h-1在使用useElementSize时非常重要 -->
-  <view ref="container" class="h-full fx-col flex-1 min-h-1">
-    <uv-tabs :current="current" :list="tabs" :key-name="keyName" v-bind="tabBindings" class="bg-white bd-b-1"
-      @change="handleTabChange">
-      <template v-if="$slots.tab" #default="scope">
-        <slot name="tab" v-bind="scope" />
-      </template>
-    </uv-tabs>
-    <uv-line v-if="border" />
-    <view>
-      <slot name="header"></slot>
-    </view>
-    <view class="flex-1 min-h-1 relative">
-      <!-- :style="{height: swiperHeight+'px'}" -->
-      <view class="absolute inset-0">
-        <swiper :current="current" class="h-full" v-bind="swiperBindings" @change="handleSwiperChange">
-          <swiper-item v-for="t in tabs" :key="t.name">
-            <!-- 延迟渲染 -->
-            <!-- 如果配置相同的template可共享卡槽 -->
-            <!-- 内容如果要支持滚动,请使用css class - .tabs-swiper-content -->
-            <keep-alive v-if="lazy">
-              <slot v-if="t.show" :name="t.template || template || t.name" v-bind="t" />
-            </keep-alive>
-            <slot v-else :name="t.template || template || t.name" v-bind="t" />
-          </swiper-item>
-        </swiper>
-      </view>
-    </view>
-  </view>
-</template>
-
-<script setup>
-import { ref, computed, watch } from 'vue'
-import _ from "lodash";
-import { createPropDefine } from "@/utils";
-import { useElementSize } from "@vueuse/core";
-
-const props = defineProps({
-  // support synchronization
-  modelValue: createPropDefine(0, Number),
-  tabs: createPropDefine([], Array),
-  keyName: createPropDefine('name'),
-  // other options of uv-tabs
-  tabOptions: createPropDefine(null, Object),
-  // other uni-app swiper options
-  swiperOptions: createPropDefine(null, Object),
-  // support lazy rendering, default is true
-  lazy: createPropDefine(true, Boolean),
-  // whether cache all tabs? if not use cacheSize, higher priority than cacheSize
-  cacheAll: createPropDefine(false, Boolean),
-  // cached accessed tab. <=0 means cache all.
-  cacheSize: createPropDefine(5, Number),
-  // tab content will render after delay time, recommended for complex dynamic content.
-  // related to lazy, default is true
-  delay: createPropDefine(true, Boolean),
-  preload: createPropDefine(true, Boolean), // 一般情况下preload会非常平滑,但有大请求时可以禁用
-  // bigger than the animation time。 default 400ms.
-  delayTime: createPropDefine(400, Number),
-  border: createPropDefine(false, Boolean),
-  tabsHeight: createPropDefine(44, Number),
-  // 统一指定模板,优先级低于tab.template,因为有时候让tab指定template会破坏原数据结构
-  template: createPropDefine('')
-})
-const emits = defineEmits(['update:modelValue', 'change'])
-
-// 因为外部可能不会使用v-model:current接收回传,所以这里本地保持住该值
-const current = ref(0)
-const cachedTabs = ref([])
-const container = ref(null)
-const { height } = useElementSize(container)
-const swiperHeight = computed(() => height.value - props.tabsHeight - (props.border ? 1 : 0))
-
-const tabBindings = computed(() => {
-  // make some default options for u-tabs here
-  return {
-    scrollable: props.tabs.length > 4,
-    itemStyle: { height: props.tabsHeight + 'px' },
-    ...props.tabOptions
-  }
-})
-const swiperBindings = computed(() => {
-  // make some default options for uni-app swiper here
-  return {
-    ...props.swiperOptions
-  }
-})
-
-// 保持localCurrent与props.current同步,即props优先级更高
-watch(() => props.modelValue, (val) => current.value = val, { immediate: true })
-
-watch([current, () => props.tabs], ([current]) => {
-  const { cacheAll, cacheSize, tabs, delay, preload, delayTime } = props
-  if (!tabs.length) return // no data, wait for tabs ready.
-
-  // delay control, first tab must render immediately.
-  const effectDelay = cachedTabs.value.length && delay ? delayTime : 0
-
-  // cache tabs, keep accessed sequence.
-  const next = [current]
-  if (preload) {
-    // keep current -1 +1 in cache list while preload=true.
-    // this will make swiper action much-much smooth.
-    if (current + 1 < tabs.length) next.unshift(current + 1)
-    if (current - 1 >= 0) next.unshift(current - 1)
-  }
-  _.pull(cachedTabs.value, ...next)
-  cachedTabs.value.push(...next)
-  while (!cacheAll && cacheSize && cachedTabs.value.length > cacheSize) {
-    cachedTabs.value.shift()
-  }
-
-  // reset show property of all tabs
-  if (effectDelay) {
-    setTimeout(() => {
-      tabs.forEach((t, i) => t.show = cachedTabs.value.includes(i))
-    }, effectDelay)
-  } else {
-    tabs.forEach((t, i) => t.show = cachedTabs.value.includes(i))
-  }
-}, { immediate: true })
-
-const handleTabChange = function ({ index }) {
-  current.value = index
-  emits('update:modelValue', current.value)
-  emits('change', current.value)
-}
-const handleSwiperChange = function (e) {
-  current.value = e.detail.current
-  emits('update:modelValue', current.value)
-  emits('change', current.value)
-}
-</script>
-
-<style></style>

+ 0 - 19
src/components/mx-tag-button/mx-tag-button.vue

@@ -1,19 +0,0 @@
-<template>
-    <uv-tags v-bind="props" @click="$emit('click')"/>
-</template>
-
-<script setup>
-import {tagsProps} from "@/uni_modules/uv-tags/components/uv-tags/uv-tags.vue";
-import {createPropDefine} from "@/utils";
-
-const props = defineProps({
-    ...tagsProps,
-    borderColor: createPropDefine('transparent'),
-    plain: createPropDefine(true, Boolean)
-})
-defineEmits(['click'])
-</script>
-
-<style scoped>
-
-</style>

+ 0 - 95
src/components/mx-video/mx-video.vue

@@ -1,95 +0,0 @@
-<template>
-    <video id="mxVideo" :src="safeUrl" controls :poster="coverUrl" object-fit="contain"
-           @error="handleError" @timeupdate="handlePlaying" @ended="handlePlayEnd"
-           @fullscreenchange="handleFullscreenChange"
-           class="w-full aspect-video" v-bind="$attrs"></video>
-    <view class="bg-white fx-row items-center px-30 gap-30 h-80">
-        <text class="text-tips text-sm">倍速播放</text>
-        <view class="fx-row items-center gap-10">
-            <uv-tags v-for="rate in supportRates" :type="videoCtx?'primary':'info'" :text="`x`+rate"
-                     :plain="currentRate!=rate" size="mini" class="rate-item" @click="handleRate(rate)">
-            </uv-tags>
-        </view>
-    </view>
-    <uv-line/>
-</template>
-
-<script setup>
-import {computed, onUnmounted, ref} from 'vue'
-import {useEnvStore} from "@/hooks/useEnvStore";
-import {useVideoPlay} from "@/components/mx-video/useVideoPlay";
-import {useVideoRecord} from "@/components/mx-video/useVideoRecord";
-import {createPropDefine} from "@/utils";
-
-const props = defineProps({
-    aliIdType: createPropDefine('', [String, Number]),
-    src: createPropDefine(''),
-    // 某些场景下,不需要全屏控制,
-    // 因为视频可能是纯为手机端录制的,不需要旋转
-    disabledFullScreen: createPropDefine(false, Boolean)
-})
-const emits = defineEmits(['change', 'error'])
-
-const videoCtx = ref(null)
-const currentRate = ref(1)
-const {safeUrl, coverUrl, onSrcChange} = useVideoPlay(props)
-const {reset: resetRecord, commit: commitRecord, syncProgress} = useVideoRecord()
-const {isAndroid, isIOS} = useEnvStore()
-const supportRates = computed(() => isAndroid.value ? [0.5, 0.8, 1.0, 1.25, 1.5] : [0.5, 1.0, 1.25, 1.5, 2.0])
-// const rateCtrlStyle = computed(() => isIOS.value
-//     ? {marginTop: '-5px', height: '39.5px',}
-//     : {marginTop: '-5px', height: '40px'})
-
-onSrcChange(() => {
-    // 切换时,创建速度控制
-    videoCtx.value = uni.createVideoContext('mxVideo')
-    currentRate.value = 1
-})
-onSrcChange(async (newVal, oldVal) => {
-    // 切换时,试图提交前一个视频的观看记录
-    await commitRecord(oldVal)
-    // 之后,重置记录功能
-    resetRecord()
-})
-// 预留事件,向外抛出change事件
-onSrcChange((newVal, oldVal) => emits('change', newVal, oldVal))
-
-// 退出时,试图提交观看记录
-onUnmounted(() => commitRecord(props))
-// 播放完毕时,强制提交
-const handlePlayEnd = () => commitRecord(props)
-// 播放过程中,本地同步播放进度
-const handlePlaying = (e) => syncProgress(e.detail)
-
-const handleFullscreenChange = (e) => {
-    if (props.disabledFullScreen) return
-    uni.webView.postMessage({
-        data: {
-            action: 'fullscreen',
-            data: e.detail
-        }
-    });
-}
-
-const handleRate = (rate) => {
-    if (!videoCtx.value) return
-    videoCtx.value.playbackRate(rate)
-    currentRate.value = rate
-}
-
-const handleError = (e) => {
-    if (!safeUrl.value) return // 此时不算异常
-    console.log('mx-video error happens', e)
-    emits("error", e)
-}
-</script>
-
-<style scoped>
-.rate-item {
-    min-width: 42px;
-}
-
-::v-deep .uv-tags {
-    justify-content: center;
-}
-</style>

+ 0 - 38
src/components/mx-video/useVideoPlay.js

@@ -1,38 +0,0 @@
-import {ref, watch} from 'vue'
-import {createEventHook} from "@vueuse/core";
-import {getVideoPlayInfo} from "@/api/webApi/webVideo";
-
-export const useVideoPlay = function (videoProps) {
-    const safeUrl = ref('')
-    const coverUrl = ref('')
-    const srcChange = createEventHook()
-
-    const isPlayUrl = (src) => src?.toLowerCase().startsWith('http')
-
-    watch([() => videoProps.src, () => videoProps.aliIdType], (n, o) => {
-        // TODO: 目前还没有测试切换播放源的问题,以观后效。
-        const oldVal = {src: o[0], aliIdType: o[1]}
-        const newVal = {src: n[0], aliIdType: n[1]}
-
-        if (isPlayUrl(newVal.src)) {
-            safeUrl.value = newVal.src
-            srcChange.trigger(newVal, oldVal).then()
-        } else if (newVal.src) {
-            // 假定这里的src就是aliId
-            getVideoPlayInfo({videoId: newVal.src}).then(res => {
-                const {palyUrl: play, coverUrl: cover} = res.data
-                if (isPlayUrl(play)) {
-                    safeUrl.value = play
-                    coverUrl.value = cover
-                    srcChange.trigger(newVal, oldVal).then()
-                }
-            })
-        }
-    }, {immediate: true})
-
-    return {
-        safeUrl,
-        coverUrl,
-        onSrcChange: srcChange.on
-    }
-}

+ 0 - 39
src/components/mx-video/useVideoRecord.js

@@ -1,39 +0,0 @@
-import {reactive} from 'vue'
-import {saveWatchRecord} from "@/api/webApi/webVideo";
-
-export const useVideoRecord = function () {
-    const state = reactive({
-        committed: false,
-        duration: 0,
-        percent: 0
-    })
-
-    const reset = function () {
-        state.committed = false
-        state.duration = 0
-        state.percent = 0
-    }
-
-    const syncProgress = function ({currentTime, duration}) {
-        state.duration = duration
-        state.percent = 0 // for safe division
-        if (duration > 0) state.percent = currentTime * 100 / duration
-    }
-
-    const commit = async function ({src, aliIdType}) {
-        if (!src) return // 无效数据
-        if (state.committed) return // 已提交
-        if (!state.percent) return // 无效数据
-        if (state.percent < 2) return // <2%播放太短,也不记,防止过度触发提交
-        await saveWatchRecord(src, state.duration, state.percent, aliIdType)
-        state.committed = true
-        console.log('saveWatchRecord committed')
-    }
-
-    return {
-        state,
-        reset,
-        syncProgress,
-        commit
-    }
-}

+ 0 - 20
src/components/vip-guide-more/vip-guide-more.vue

@@ -1,20 +0,0 @@
-<template>
-    <view class="fx-row fx-cen-cen">
-        <uv-image :src="img" @click="goBuyVip"/>
-    </view>
-</template>
-
-<script setup>
-import {combineOssFile} from "@/utils";
-import {useSingletonBuyVip} from "@/hooks/useSingletonComponent";
-
-const img = combineOssFile('/static/guide/nomore_unless_vip.png')
-const goBuyVip = () => {
-    const buyVip = useSingletonBuyVip()
-    buyVip.open()
-}
-</script>
-
-<style scoped>
-
-</style>

文件差异内容过多而无法显示
+ 0 - 42
src/components/vue-svg-icons/SvgRegister.js


+ 0 - 222
src/components/vue-svg-icons/vue-svg-icons.vue

@@ -1,222 +0,0 @@
-<template>
-  <svg version="1.1"
-    :class="klass"
-    :role="label ? 'img' : 'presentation'"
-    :aria-label="label"
-    :viewBox="box"
-    :style="style">
-    <slot v-if="!backgroundImage">
-      <template v-if="icon && icon.paths">
-        <path v-if="icon && icon.paths" v-for="(path, i) in icon.paths" :key="`path-${i}`" v-bind="path"/>
-      </template>
-      <template v-if="icon && icon.polygons">
-        <polygon v-for="(polygon, i) in icon.polygons" :key="`polygon-${i}`" v-bind="polygon"/>
-      </template>
-      <template v-if="icon && icon.raw"><g v-html="raw" v-bind="icon.g"></g></template>
-    </slot>
-  </svg>
-</template>
-
-<style>
-.fa-icon {
-  display: inline-block;
-  fill: currentColor;
-}
-
-.fa-flip-h {
-  transform: scale(-1, 1);
-}
-
-.fa-flip-v {
-  transform: scale(1, -1);
-}
-
-.fa-flip-vh {
-  transform: scale(-1, -1);
-}
-
-.fa-spin {
-  animation: fa-spin 0.5s 0s infinite linear;
-}
-
-.fa-pulse {
-  animation: fa-spin 1s infinite steps(8);
-}
-
-@keyframes fa-spin {
-  0% {
-    transform: rotate(0deg);
-  }
-  100% {
-    transform: rotate(360deg);
-  }
-}
-</style>
-
-<script>
-let icons = {}
-export default {
-  name: 'fa-icon',
-  props: {
-    name: {
-      type: String,
-      validator (val) {
-        if (val && !(val in icons)) {
-          console.warn(`Invalid prop: prop "name" is referring to an unregistered icon "${val}".` +
-            `\nPlease make sure you have imported this icon before using it.`)
-          return false
-        }
-        return true
-      }
-    },
-    width: [Number, String],
-    height: [Number, String],
-    spin: Boolean,
-    pulse: Boolean,
-    flip: {
-      validator (val) {
-        return val === 'h' || val === 'v' || val === 'vh'
-      }
-    },
-    label: String,
-    backgroundImage: Boolean,
-    color: String
-  },
-  data () {
-    return {
-    }
-  },
-  computed: {
-    klass () {
-      return {
-        'fa-icon': true,
-        'fa-spin': this.spin,
-        'fa-flip-h': this.flip === 'h',
-        'fa-flip-v': this.flip === 'v',
-        'fa-flip-vh': this.flip === 'vh',
-        'fa-pulse': this.pulse,
-        [this.$options.name]: true
-      }
-    },
-    icon () {
-      if (this.name) {
-        return icons[this.name]
-      }
-      return null
-    },
-    box () {
-      if (this.icon) {
-        return `0 0 ${this.icon.width} ${this.icon.height}`
-      }
-      return `0 0 ${this.width} ${this.height}`
-    },
-    style () {
-      if (this.backgroundImage) {
-        let content = ''
-        if (this.icon && this.icon.paths) {
-          for (let path of this.icon.paths) {
-            let str = ''
-            for (let k in path) {
-              str += `${k}='${path[k]}' `
-            }
-            content += `<path ${str.trim()}/>`
-          }
-        }
-        if (this.icon && this.icon.polygons) {
-          for (let path of this.icon.polygons) {
-            let str = ''
-            for (let k in path) {
-              str += `${k}='${path[k]}' `
-            }
-            content += `<polygon ${str.trim()}/>`
-          }
-        }
-        if (this.icon && this.icon.raw) {
-          let str = ''
-          for (let k in this.icon.g) {
-            str += `${k}='${this.icon.g[k]}' `
-          }
-          content += `<g ${str.trim()}>${this.raw.replace(/"/g, '\'')}</g>`
-        }
-        let code = {
-          '%': '%25',
-          '!': '%21',
-          '@': '%40',
-          '&': '%26',
-          '#': '%23'
-        }
-        let svg = `<svg viewBox='${this.box}' fill='${this.color}' version='1.1' xmlns='http://www.w3.org/2000/svg'>${content}</svg>`
-        for (let k in code) {
-          svg = svg.replace(new RegExp(k, 'g'), code[k])
-        }
-        let css = {
-          'background-image': `url("data:image/svg+xml,${svg}")`,
-          width: this.width,
-          height: this.height
-        }
-        return css
-      }
-      return { color: this.color, width: this.width, height: this.height }
-    },
-    raw () {
-      // generate unique id for each icon's SVG element with ID
-      if (!this.icon || !this.icon.raw) {
-        return null
-      }
-      let raw = this.icon.raw
-      let ids = {}
-      raw = raw.replace(/\s(?:xml:)?id=(["']?)([^"')\s]+)\1/g, (match, quote, id) => {
-        let uniqueId = getId()
-        ids[id] = uniqueId
-        return ` id="${uniqueId}"`
-      })
-      raw = raw.replace(/#(?:([^'")\s]+)|xpointer\(id\((['"]?)([^')]+)\2\)\))/g, (match, rawId, _, pointerId) => {
-        let id = rawId || pointerId
-        if (!id || !ids[id]) {
-          return match
-        }
-
-        return `#${ids[id]}`
-      })
-
-      return raw
-    }
-  },
-  mounted () {
-    if (!this.name) {
-      console.warn(`Invalid prop: prop "name" is required.`)
-      return
-    }
-    if (!this.icon) {
-      console.warn(`Invalid icon: prop "name" is not registed.`)
-    }
-  },
-  register (data) {
-    for (let name in data) {
-      let icon = data[name]
-
-      if (!icon.paths) {
-        icon.paths = []
-      }
-      if (icon.d) {
-        icon.paths.push({ d: icon.d })
-      }
-
-      if (!icon.polygons) {
-        icon.polygons = []
-      }
-      if (icon.points) {
-        icon.polygons.push({ points: icon.points })
-      }
-
-      icons[name] = icon
-    }
-  },
-  icons
-}
-
-let cursor = 0xd4937
-function getId () {
-  return `fa-${(cursor++).toString(16)}`
-}
-</script>

+ 0 - 92
src/hooks/defineCacheActions.js

@@ -1,92 +0,0 @@
-import {getDicts} from "@/api/dict/data";
-import {list as getSubjectList} from "@/api/webApi/subject";
-import {list as getKnowledgeTree} from "@/api/webApi/knowledge";
-import {ieVideoKnowledge, ieVideoSubjects, videoList} from "@/api/webApi/webVideo";
-import {getEquivalentScore, yfydList, yfydLocations, yfydModes, yfydYears} from "@/api/webApi/webQue";
-import {getVoluntaryData} from "@/api/webApi/volunteer";
-import {getAllMajor, getMajorByName} from "@/api/webApi/collegemajor";
-import {fillTreeChildCount, fillTreeParentBridge} from "@/utils/tree-helper";
-import {getSizeOfObject} from "@/utils";
-
-export const cacheActions = {
-    getDicts: {
-        type: Symbol('GET_DICTS'),
-        handler: async function (type) {
-            const {data} = await getDicts(type)
-            return data
-        }
-    },
-    getSubjectList: {
-        type: Symbol('GET_SUBJECT_LIST'),
-        handler: async () => {
-            const {data} = await getSubjectList()
-            return data
-        }
-    },
-    getKnowledgeTree: {
-        type: Symbol("GET_KNOWLEDGE_TREE"),
-        handler: async (params) => {
-            const {data} = await getKnowledgeTree(params)
-            return data
-        }
-    },
-    getVideoSubjects: {
-        type: Symbol('GET_VIDEO_SUBJECTS'),
-        handler: async () => {
-            const {rows} = await ieVideoSubjects({type: 1})
-            return rows
-        }
-    },
-    getVideoKnowledge: {
-        type: Symbol('GET_VIDEO_KNOWLEDGE'),
-        handler: async (params) => {
-            return await ieVideoKnowledge(params)
-        }
-    },
-    getVideoOfKnowledge: {
-        type: Symbol('GET_VIDEO_OF_KNOWLEDGE'),
-        handler: async (params) => {
-            return await videoList(params)
-        }
-    },
-    // voluntary data
-    getVoluntaryData: async (params) => {
-        const {data} = await getVoluntaryData(params)
-        return data
-    },
-    // 一分一段
-    getSectionLocations: async (params) => {
-        const {rows} = await yfydLocations(params)
-        // 用于mx-condition时,包一层方便控制
-        return rows.map(l => ({text: l, value: l}))
-    },
-    getSectionYears: async (params) => {
-        const {rows} = await yfydYears(params)
-        // 用于mx-condition时,包一层方便控制
-        return rows.map(y => ({text: y, value: y}))
-    },
-    getSectionModes: async (params) => {
-        const {rows} = await yfydModes(params)
-        return rows
-    },
-    getSectionList: async (params) => {
-        return await yfydList(params)
-    },
-    getEquivalentScore: async (params) => {
-        return await getEquivalentScore(params)
-    },
-    // 专业树
-    getMajorTree: {
-        type: Symbol('GET_ALL_MAJORS'),
-        handler: async (payload) => {
-            const api = payload?.name ? getMajorByName : getAllMajor
-            const {data} = await api(payload)
-            const dataSize = getSizeOfObject(data)
-            // add parent bridge to tree nodes
-            fillTreeParentBridge(data)
-            // fill childCount
-            fillTreeChildCount(data)
-            return {data, dataSize}
-        }
-    }
-}

+ 0 - 185
src/hooks/useCacheStore.js

@@ -1,185 +0,0 @@
-import {reactive} from 'vue'
-import {createGlobalState, hasOwn} from "@vueuse/core";
-import {empty, func} from "@/uni_modules/uv-ui-tools/libs/function/test";
-import {sleep} from "@/uni_modules/uv-ui-tools/libs/function";
-import {getSizeOfObject} from "@/utils";
-import _ from 'lodash';
-
-function CacheResult(data, options) {
-    if (data && hasOwn(data, 'dataSize')) {
-        // 如果对象内部有循环钩子,getSizeOfObject无法计算,则预先缓存大小
-        this.data = data.data
-        this.dataSize = data.dataSize
-    } else {
-        this.data = data
-    }
-    this.expires = options?.timeout ? (options.timeout + Date.now()) : null
-    this.obsoleted = () => {
-        return this.expires && this.expires < Date.now()
-    }
-}
-
-export const useCacheStore = createGlobalState(() => {
-    // data
-    const container = reactive(new Map())
-    const defaultOptions = {
-        timeout: 2 * 3600 * 1000, // default cache 2 hours
-        emptyIgnore: false // default cache empty values
-    }
-
-    // hooks
-
-    // functions
-    const checkActionDefine = (action) => {
-        if (!action) throw new Error('action is required')
-        if (func(action)) {
-            return {
-                type: action.toString(),
-                handler: async (...args) => await action(...args)
-            }
-        } else {
-            if (!action.type) throw new Error('action.type is required')
-            if (!action.handler) throw new Error('action.handler is required')
-            if (!func(action.handler)) throw new Error('action.handler must be a function')
-        }
-        return action
-    }
-    const generateCacheKey = function (payload) {
-        return JSON.stringify({payload})
-    }
-    const executeAction = async (action, payload) => {
-        let result = action.handler(payload)
-        if (result instanceof Promise) result = await result
-        return result
-    }
-
-    /*
-    * 获取缓存结果。
-    * 虽然改进后缓存动作传入function也可以,但用defineCacheAction的方式调试时更直观。
-    * @param {Object|Function} action 缓存动作。action是Function时尤其要注意用payload区隔结果!
-    * @param {Object} payload 缓存参数,区隔缓存结果的关键!
-    * @param {Object} options 缓存选项
-    * @returns {Object} 缓存结果
-    * */
-    const dispatchCache = async (action, payload = undefined, options = undefined) => {
-        // TODO:并发锁, 并发机会不高且没有致命性影响,以后再优化
-        const formatAction = checkActionDefine(action)
-        let typeNode = container.get(formatAction.type)
-        if (!typeNode) {
-            typeNode = new Map()
-            container.set(formatAction.type, typeNode)
-        }
-        const key = generateCacheKey(payload)
-        let cacheData = typeNode.get(key)
-
-        // fire cached result
-        if (cacheData instanceof CacheResult && !cacheData.obsoleted()) {
-            // call `sleep` to simulate api reaction.
-            // NOTE: 这个模拟对于mx-condition.config.isImmediate有关键作用,否则需要对缓存和API实时做不同配置
-            await sleep(0)
-            return cacheData.data
-        }
-
-        // cache miss, execute action and cache result
-        const result = await executeAction(formatAction, payload)
-        const cacheOptions = _.merge(defaultOptions, options)
-        cacheData = new CacheResult(result, cacheOptions)
-        if (!(empty(result) && cacheOptions.emptyIgnore)) {
-            typeNode.set(key, cacheData)
-        }
-
-        return cacheData.data
-    }
-
-    /*
-    * 是否存在缓存
-    * @param {Object} action 缓存动作
-    * @param {Object} payload 缓存参数
-    * @returns {Boolean} 是否存在缓存
-    * */
-    const hasCache = (action, payload) => {
-        const formatAction = checkActionDefine(action)
-        const typeNode = container.get(formatAction.type)
-        if (!typeNode) return false
-        const key = generateCacheKey(payload)
-        return typeNode.has(key)
-    }
-
-    /*
-    * 删除缓存
-    * @param {Object} action 缓存动作
-    * @param {Object} payload 缓存参数
-    * */
-    const removeCache = (action, payload = null) => {
-        if (payload === null || payload === undefined) {
-            clearCache(action)
-            return;
-        }
-
-        const formatAction = checkActionDefine(action)
-        const typeNode = container.get(formatAction.type)
-        if (!typeNode) return
-        const key = generateCacheKey(payload)
-        typeNode.delete(key)
-        if (!typeNode.size) container.delete(formatAction.type)
-    }
-
-    /*
-    * 清理缓存
-    * @param {Object} action 缓存动作,action = null时清空所有缓存
-    * */
-    const clearCache = (action = null) => {
-        if (!action) {
-            container.clear()
-            return
-        }
-        const formatAction = checkActionDefine(action)
-        container.delete(formatAction.type)
-    }
-
-    /*
-    * 清理过期缓存
-    * */
-    const gcCache = () => {
-        if (!container.size) return
-        console.log('run useCacheStore: gc')
-        for (const [type, typeNode] of container.entries()) {
-            for (const [key, cacheData] of typeNode.entries()) {
-                if (cacheData.obsoleted()) {
-                    typeNode.delete(key)
-                    console.log('run useCacheStore: gc: remove cache', type, key)
-                }
-            }
-            if (!typeNode.size) {
-                container.delete(type)
-                console.log('run useCacheStore: gc: remove type', type)
-            }
-        }
-    }
-
-    const getSize = function () {
-        // then calculate
-        let total = 0
-        container.forEach(typeNode => {
-            typeNode.forEach(i => {
-                if (hasOwn(i, 'dataSize')) {
-                    total += i.dataSize
-                } else {
-                    total += getSizeOfObject(i)
-                }
-            })
-        })
-        return total
-    }
-
-    // window.cacheContainer = container
-    return {
-        container, // open for test
-        dispatchCache,
-        hasCache,
-        removeCache,
-        clearCache,
-        gcCache,
-        getSize
-    }
-})

+ 0 - 90
src/hooks/useChooseImage.js

@@ -1,90 +0,0 @@
-import config from "@/config";
-import {useUserStore} from "@/hooks/useUserStore";
-
-export const useChooseImage = function () {
-    const {token} = useUserStore()
-
-    /*
-		 * @description 选择图片上传
-		 * @param {successCallback} 上传图片成功后的回调
-		 * @param {type} 可选参数,类型,默认为6
-		 * @value 10	竞赛
-		 * @value 8		临时文件本地路径
-		 * @value 7		试题收藏
-		 * @value 6		答题附件
-		 * @value 5		微课文件库
-		 * @value 4		app上传视频
-		 * @value 3		校本资源库
-		 * @value 2		云库
-		 * @value 1 	个人资源库
-		 * @value 0 	默认路径
-		 * @param {count} 限制数量,默认为1
-		 */
-    const chooseImage = function (successCallback, type = 6, count = 1) {
-        const uploadUrl = config.serverBaseUrl + '/common/uploadFile2OSS/' + type
-        uni.chooseImage({
-            count: count, //默认9
-            sourceType: ['album', 'camera'],
-            success: (res) => {
-                const tempFilePaths = res.tempFilePaths;
-                uni.showLoading();
-                uni.uploadFile({
-                    url: uploadUrl,
-                    header: {
-                        Authorization: 'Bearer ' + token.value,
-                    },
-                    filePath: tempFilePaths[0],
-                    name: 'file',
-                    success: (uploadFileRes) => {
-                        const data = JSON.parse(uploadFileRes.data);
-                        successCallback(data)
-                    },
-                    complete: () => {
-                        uni.hideLoading();
-                    }
-                })
-            }
-        })
-    }
-
-    const chooseAvatar = function (successCallback) {
-        const uploadUrl = config.serverBaseUrl + '/system/user/profile/avatar'
-        uni.chooseImage({
-            count: 1, //默认9
-            sourceType: ['album', 'camera'],
-            success: (res) => {
-                const tempFilePaths = res.tempFilePaths;
-                uni.showLoading();
-                uni.uploadFile({
-                    url: uploadUrl,
-                    header: {
-                        Authorization: 'Bearer ' + token.value,
-                    },
-                    filePath: tempFilePaths[0],
-                    name: 'avatarfile',
-                    success: (uploadFileRes) => {
-                        const data = JSON.parse(uploadFileRes.data);
-                        successCallback(data)
-                    },
-                    complete: () => {
-                        uni.hideLoading();
-                    }
-                })
-            }
-        })
-    }
-
-    const previewImage = function (url, current = 0) {
-        const urls = [].concat(url)
-        uni.previewImage({
-            urls: urls,
-            current: current,
-            indicator: urls.length > 1 ? 'number' : 'none',
-            fail: function (e) {
-                console.log('previewImage failed:', e)
-            }
-        })
-    }
-
-    return {chooseImage, chooseAvatar, previewImage}
-}

+ 0 - 147
src/hooks/useDownload.js

@@ -1,147 +0,0 @@
-import env from '@/config';
-import config from '@/common/mxConfig';
-import {useEnvStore} from "@/hooks/useEnvStore";
-import {useUserStore} from "@/hooks/useUserStore";
-
-export const useDownload = function () {
-    const {token, period} = useUserStore()
-    const {isH5, isWap2App} = useEnvStore()
-
-    const postMessage = (msgType, data) => {
-        uni.webView.postMessage({
-            data: {
-                action: msgType,
-                data: data
-            }
-        });
-    }
-
-    const downloadBlobFile = (response, filename) => {
-        // download blob file
-        const url = window.URL.createObjectURL(new Blob([response.data]))
-
-        // get file suffix from response header
-        // build file name from data.name data.score data.batchName and suffix
-        let disposition = response.header['content-disposition']
-        disposition = disposition.replace('"', '').replace("'", '')
-        const suffix = disposition.substring(disposition.lastIndexOf('.'))
-        const fileName = `${filename}${suffix}`
-
-        // Create a download link and trigger a click event to download the file
-        const link = document.createElement('a')
-        link.href = url
-        link.setAttribute('download', fileName)
-        document.body.appendChild(link)
-        link.click()
-
-        // Remove the link element and release the object URL
-        document.body.removeChild(link)
-        window.URL.revokeObjectURL(url)
-    }
-
-    const downloadBlobFileForWap2app = (reqOption, fileName) => {
-        // reqOption: {url, params}
-        if (isWap2App.value) {
-            postMessage('downloadBlob', {
-                ...reqOption,
-                token: token.value
-            });
-        }
-    }
-
-    const downloadRealPaper = (paperId) => {
-        let path = env.serverBaseUrl + '/front/v2/papers/downloadRealPaper'
-        path += '?paperId=' + paperId + '&period=' + period.value
-        const override = {
-            isOffice: () => true,
-            getFileType: () => 'docx'
-        }
-        downloadFile(path, false, override)
-    }
-
-    const downloadFile = (url, openOnly = false, overrideFileHelper = {}) => {
-        const itemList = (openOnly || isWap2App.value) ? ["打开文档"] : ["打开文档", "下载文档"];
-        uni.showActionSheet({
-            itemList: itemList,
-            success: res => {
-                const fileHelper = {
-                    ...config.fileHelper,
-                    ...overrideFileHelper
-                }
-                switch (res.tapIndex) {
-                    case 0:
-                        if (fileHelper.isFile(url)) {
-                            if (isWap2App.value) {
-                                postMessage('openFile', {url});
-                            } else if (isH5.value) {
-                                window.open(url);
-                            }
-                        } else if (fileHelper.isImage(url)) {
-                            if (isWap2App.value) {
-                                postMessage('openImage', {url});
-                            } else if (isH5.value) {
-                                uni.previewImage({
-                                    urls: [url]
-                                });
-                            }
-                        } else {
-                            if (isWap2App.value) {
-                                postMessage('openFile', {url});
-                            } else if (isH5.value) {
-                                uni.showToast({
-                                    title: "暂不支持直接打开该文档",
-                                    icon: "none"
-                                });
-                            }
-                        }
-
-                        break;
-                    case 1:
-                        const remoteFunc = fileHelper.isImage(url) ? 'openImage' : 'downloadFile';
-                        if (isWap2App.value) {
-                            postMessage(remoteFunc, {url});
-                        } else if (isH5.value) {
-                            // 大文件不适合此方案,最好后台请求头部加上Content-Disposition: attachment; filename=xxx.xxx来触发下载
-                            uni.showLoading();
-                            uni.downloadFile({
-                                url: url,
-                                success: (res) => {
-                                    if (res.statusCode === 200) {
-                                        const fileName = url.split('/').pop();
-                                        const a = document.createElement("a");
-                                        a.target = '_blank';
-                                        a.download = fileName;
-                                        a.href = res.tempFilePath;
-                                        document.body.appendChild(a);
-                                        a.click();
-                                        a.remove();
-                                    }
-                                },
-                                fail: (err) => {
-                                    uni.showToast({
-                                        icon: 'none',
-                                        mask: true,
-                                        title: '失败请重新下载',
-                                    });
-                                },
-                                complete: () => {
-                                    uni.hideLoading();
-                                }
-                            })
-                        }
-                        break;
-                    default:
-                        break;
-                }
-            }
-        });
-    }
-
-    return {
-        postMessage,
-        downloadRealPaper,
-        downloadFile,
-        downloadBlobFile,
-        downloadBlobFileForWap2app
-    }
-}

+ 0 - 73
src/hooks/useEnvStore.js

@@ -1,73 +0,0 @@
-import {createGlobalState, useEventListener, useStorage} from "@vueuse/core";
-import {computed, ref} from "vue";
-import mxConst from "@/common/mxConst";
-import {sys} from "@/uni_modules/uv-ui-tools/libs/function";
-
-export const useEnvStore = createGlobalState(() => {
-    // 平台判断,和调用bridge有关,如视频控制、下载等。
-    const platform = ref(window.platform || "");
-    const isH5 = computed(() => platform.value === "h5");
-    const isWap2App = computed(() => platform.value === "wap2app");
-
-    const systemInfo = computed(() => {
-        const {
-            platform,
-            model,
-            deviceId,
-            deviceModel,
-            deviceType,
-            appId,
-            version,
-            appVersion,
-            appVersionCode,
-        } = sys();
-        return {
-            platform,
-            model,
-            deviceId,
-            deviceModel,
-            deviceType,
-            appId,
-            version,
-            appVersion,
-            appVersionCode,
-        };
-    });
-
-    const isAndroid = computed(() => systemInfo.value.platform.toLowerCase() == 'android')
-    const isIOS = computed(() => systemInfo.value.platform.toLowerCase() == 'ios')
-
-    // TODO: isParent 表示这个应用是从微信公众号启动的。
-    // 但这个判断受限于调用时机,不如上面的 platform 有一定封装来保证和运行环境契合。
-    const isParent = ref(
-        window?.location?.href.includes(mxConst.keyParentIdentifier)
-    );
-
-    // 引导页已读标志。
-    const isGuideRead = useStorage(mxConst.keyGuideRead, false);
-
-    // 访问相册权限初次提示标志
-    const isDisplayedPermission = useStorage(
-        mxConst.keyDisplayPermission,
-        false
-    );
-
-    // hook
-    useEventListener(window, "platformChange", (evt) => {
-        // @/index.html 中定义的 window.platform 会在不同环境下被赋值,此时需要更新 platform 状态
-        // 我们在 setPlatform 中暴露了 platformChange 事件用来让 vue 组件监听平台变化
-        platform.value = window?.platform || "";
-    });
-
-    return {
-        platform,
-        isH5,
-        isWap2App,
-        isParent,
-        isGuideRead,
-        isDisplayedPermission,
-        systemInfo,
-        isAndroid,
-        isIOS
-    };
-});

+ 0 - 42
src/hooks/useMathJaxService.js

@@ -1,42 +0,0 @@
-import {reactive, watch} from 'vue'
-import {createGlobalState} from "@vueuse/core";
-import config from "@/config";
-
-export const useMathJaxService = createGlobalState(() => {
-    const MathJax = {
-        tex: {
-            inlineMath: [["$", "$"], ["\\(", "\\)"]], //行内公式选择符
-            displayMath: [["$$", "$$"], ["\\[", "\\]"]], //段内公式选择符
-            // autoload: {color: [], colorv2: ['color']}, // 3.0开始已经不支持此选项了
-            packages: {'[+]': ['noerrors', 'mathtools', 'ams']}
-        },
-        options: {
-            skipHtmlTags: ["script", "noscript", "style", "textarea", "pre", "code", "a"], //避开某些标签
-            ignoreHtmlClass: 'tex2jax_ignore',
-            processHtmlClass: 'tex2jax_process'
-        },
-        loader: {
-            load: ['input/asciimath', '[tex]/noerrors', '[tex]/mathtools', '[tex]/ams']
-        }
-    }
-    // NOTE:mathjax库在加载完毕后会用新对象直接覆盖window.MathJax, 所以后续都需要使用window.MathJax
-    window.MathJax = MathJax
-    const updateMathJax = function (el) {
-        // 指定了HTML元素即更新指定元素,否则全部更新
-        const elements = []
-        if (el instanceof HTMLElement) elements.push(el)
-        const mathjax = window.MathJax
-        if (!mathjax.version) return
-        mathjax.startup.promise.then(_ => mathjax.typesetPromise(elements))
-    }
-
-    const script = document.createElement('script')
-    script.src = config.mathJaxCDN
-    script.onload = () => updateMathJax()
-    document.head.appendChild(script)
-
-    return {
-        MathJax,
-        updateMathJax
-    }
-})

+ 0 - 14
src/hooks/usePageConfigInjection.js

@@ -1,14 +0,0 @@
-import {useUserStorePageFilter} from "@/hooks/useUserStorePageFilter";
-import {injectLocal, provideLocal} from "@vueuse/core";
-
-const injectKey = Symbol('PAGE_CONFIG')
-
-export const useProvidePageConfig = function (pageName) {
-    const pageConfig = useUserStorePageFilter(pageName)
-    provideLocal(injectKey, pageConfig)
-    return pageConfig
-}
-
-export const useInjectPageConfig = function () {
-    return injectLocal(injectKey)
-}

+ 0 - 27
src/hooks/usePageScrollInjection.js

@@ -1,27 +0,0 @@
-import {onPageScroll} from '@dcloudio/uni-app'
-import {ref, onActivated, onDeactivated, watch} from 'vue'
-import {injectLocal, provideLocal} from "@vueuse/core";
-
-const injectKey = Symbol('PAGE_SCROLL')
-
-export const useProvidePageScroll = function () {
-    const scrollTop = ref(0)
-    const scrollTopCache = ref(0)
-
-    onPageScroll((e) => {
-        scrollTop.value = e.scrollTop
-    })
-
-    // onPageScroll貌似是个全局事件,
-    // 用onActivated,onDeactivated来尝试修复页面跳转导致的scrollTop被重置的问题
-    onDeactivated(() => scrollTopCache.value = scrollTop.value)
-    onActivated(() => scrollTop.value = scrollTopCache.value)
-
-    // watch(scrollTop, val => console.log('scrollTop set', val)) // 调试用
-    provideLocal(injectKey, {scrollTop, scrollTopCache}) // 形成解构模式
-    return scrollTop
-}
-
-export const useInjectPageScroll = function () {
-    return injectLocal(injectKey)
-}

+ 0 - 246
src/hooks/usePayment.js

@@ -1,246 +0,0 @@
-import {ref, onMounted, onUnmounted} from 'vue'
-import {createOrder as createOrderApi, getCardPrice, queryOrder} from "@/api/system/user";
-import {createEventHook, useIntervalFn, useLocalStorage, useTimeout} from "@vueuse/core";
-import mxConst from "@/common/mxConst";
-import _ from "lodash";
-import {func, number} from "@/uni_modules/uv-ui-tools/libs/function/test";
-import {useTransfer} from "@/hooks/useTransfer";
-import {toast} from "@/uni_modules/uv-ui-tools/libs/function";
-import config from "@/config";
-import {useEnvStore} from "@/hooks/useEnvStore";
-import {useUserStore} from "@/hooks/useUserStore";
-
-export const paymentState = {
-    NOTPAY: 'NOTPAY',
-    SUCCESS: 'SUCCESS',
-    CLOSED: 'CLOSED'
-}
-
-export const defaultPaymentOptions = {
-    onSucceed: () => {
-        const {transferTo} = useTransfer()
-        uni.hideLoading()
-        uni.showModal({
-            content: '支付成功, 请查收短信并绑定VIP卡',
-            confirmText: '去绑卡',
-            success: res => {
-                if (res.confirm) {
-                    transferTo(mxConst.routes.activate)
-                }
-            }
-        })
-    },
-    onTimeout: (msg) => {
-      uni.hideLoading()
-        uni.showModal({
-            content: msg,
-            showCancel: false
-        })
-    },
-    onFailed: () => {
-      uni.hideLoading()
-        // only print error info on console.
-        console.error(...arguments)
-    }
-}
-
-export const usePayment = function (options = defaultPaymentOptions) {
-
-    const orderSucceed = createEventHook()
-    const orderFailed = createEventHook()
-    const orderTimeout = createEventHook()
-    const price = ref(798)
-    const outTradeNo = useLocalStorage(mxConst.keyOutTradeNo, '')
-    const interval = ref(1500)
-    let queryTimes = 8;
-    let iframe = null
-    let lock = false
-
-    onMounted(() => loadPrice())
-    onUnmounted(() => {
-        // clear resources
-        outTradeNo.value = null
-        clearResultFrame()
-    })
-
-    const payment = async function () {
-        if (lock) return toast('支付中,请稍候...')
-        uni.showLoading({title:"", mask: true})
-        try {
-            lock = true // will release while trigger func be called
-            const {h5url, outTradeNo: no} = await createOrder()
-            if (h5url && no) {
-                outTradeNo.value = no
-                clearResultFrame()
-                queryTimes = 8;
-                iframe = await createResultFrame(h5url)
-            } else {
-                triggerFailed('Payment: create order failed', h5url, no)
-            }
-        } catch (e) {
-            triggerFailed('Payment: exception', e)
-        }
-    }
-
-    const iosPayment = async function () {
-        const {isWap2App} = useEnvStore();
-        const {token} = useUserStore();
-        if (isWap2App.value && window.webviewBridge) {
-            uni.showLoading({
-                title: "",
-            });
-            const { orderId } = await createOrder('ios');
-            uni.hideLoading();
-            if (orderId) {
-                let eventArgs = []
-                window.webviewBridge.applePay({
-                    orderId,
-                    token: token.value,
-                    baseUrl: config.serverBaseUrl + '/front/ecard/iosVerifyResult'
-                }).then(res => {
-                    if (res === "支付成功") {
-                        eventArgs = ['交易成功', orderId]
-                        triggerSucceed(...eventArgs)
-                    } else {
-                        eventArgs = ['支付失败', orderId]
-                        triggerFailed(...eventArgs)
-                    }
-                });
-            }
-        } else {
-          toast("请在app中操作");
-        }
-    }
-
-
-    const loadPrice = async function () {
-        const res = await getCardPrice()
-        const val = _.get(res, 'data[0].price')
-        if (number(val)) price.value = val / 100
-    }
-
-    const createOrder = async function (type) {
-        const {data: {h5url, outTradeNo, orderId}} = await createOrderApi({totalFee: price.value, type})
-        return {h5url, outTradeNo, orderId}
-    }
-
-    const createResultFrame = async function (h5url) {
-        const { isH5, isWap2App } = useEnvStore();
-        if (isH5.value) {
-          await useTimeout()
-          const iframe = document.createElement('iframe');
-          const style = `width:0px;height:0px;position: absolute;opacity: 0;z-index: -1;outline:none;border:none;`;
-          iframe.setAttribute('style', style);
-          iframe.setAttribute('sandbox', 'allow-scripts allow-top-navigation allow-same-origin');
-          iframe.style.position = 'absolute';
-          iframe.src = h5url;
-          iframe.onload = () => checkOrderStatus()
-          iframe.onerror = () => console.error('Payment: iframe加载失败')
-          document.body.appendChild(iframe);
-          return iframe
-        } else if (isWap2App.value) {
-          const style = {
-            webviewBGTransparent: true,
-            opacity: 0,
-            render: 'always',
-            zindex: -1,
-            width: '1px',
-            height: '1px',
-            top:'0px',
-            left:'0px',
-            additionalHttpHeaders:{
-              Referer: config.paySiteUrl,
-            }
-          };
-          const wv = plus.webview.create(h5url, 'wechatPayWebview', style);
-          wv.addEventListener('loaded', (e) => {
-            checkOrderStatus();
-          }, false);
-          wv.show();
-          return wv;
-        }
-    }
-
-    const checkOrderStatus = async function () {
-        if (!outTradeNo.value) return
-        const {pause} = useIntervalFn(() => {
-            const no = outTradeNo.value // double check
-            if (!no) return pause()
-            if (queryTimes-- <= 0) {
-              triggerTimeout('支付超时')
-              return pause();
-            }
-            // 由于这个请示不是规范返回,request配置为 custom: {toast: false}
-            queryOrder({outTradeNo: no})
-                .then()
-                .catch(({tradeState}) => {
-                    console.log(no, tradeState)
-                    let eventArgs = []
-                    switch (tradeState) {
-                        case paymentState.NOTPAY:
-                            // user payment not complete, do nothing
-                            // waiting for next order check
-                            break;
-                        case paymentState.SUCCESS:
-                            // success
-                            outTradeNo.value = ''
-                            pause()
-                            eventArgs = ['交易成功', no]
-                            triggerSucceed(...eventArgs)
-                            break
-                        case paymentState.CLOSED:
-                            // timeout
-                            outTradeNo.value = ''
-                            pause()
-                            eventArgs = ['支付超时', no]
-                            triggerTimeout(...eventArgs)
-                            break
-                        default:
-                            // unexpected status
-                            outTradeNo.value = ''
-                            pause()
-                            eventArgs = [`未知交易状态:${tradeState}`, no]
-                            triggerFailed(...eventArgs)
-                            break
-                    }
-                });
-        }, interval)
-    }
-
-    const triggerSucceed = function () {
-        lock = false
-        if (func(options?.onSucceed)) options.onSucceed(...arguments)
-        else orderSucceed.trigger(...arguments)
-    }
-    const triggerTimeout = function () {
-        lock = false
-        if (func(options?.onTimeout)) options.onTimeout(...arguments)
-        orderTimeout.trigger(...arguments)
-    }
-    const triggerFailed = function () {
-        lock = false
-        if (func(options?.onFailed)) options.onFailed(...arguments)
-        orderFailed.trigger(...arguments)
-    }
-
-    const clearResultFrame = function () {
-      const { isH5, isWap2App } = useEnvStore();
-        if (iframe) {
-          if (isH5.value) {
-            document.body.removeChild(iframe)
-          } else if (isWap2App.value) {
-            iframe.close();
-          }
-          iframe = null;
-        }
-    }
-
-    return {
-        price,
-        payment,
-        iosPayment,
-        onOrderSucceed: orderSucceed.on,
-        onOrderFailed: orderFailed.on,
-        onOrderTimeout: orderTimeout.on
-    }
-}

部分文件因为文件数量过多而无法显示