From 405e152b386491c338c7d900a1b85bbdb5b79303 Mon Sep 17 00:00:00 2001 From: BBBBB <2747639460@qq.com> Date: Tue, 17 Oct 2023 09:15:30 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E5=88=9D=E5=A7=8B=E5=8C=96=20-?= =?UTF-8?q?=E8=9E=8D=E9=AA=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- editor/.browserslistrc | 4 + editor/.env | 8 + editor/.eslintrc.js | 35 + editor/.gitignore | 23 + editor/.prettierrc.js | 8 + editor/README.md | 24 + editor/babel.config.js | 3 + editor/package.json | 94 + editor/public/favicon.ico | Bin 0 -> 4286 bytes editor/public/index.html | 17 + editor/src/App.vue | 27 + editor/src/apis/manual.ts | 31 + editor/src/assets/iconfont/iconfont.css | 39 + editor/src/assets/iconfont/iconfont.js | 1 + editor/src/assets/iconfont/iconfont.json | 51 + editor/src/assets/iconfont/iconfont.ttf | Bin 0 -> 3188 bytes editor/src/assets/iconfont/iconfont.woff | Bin 0 -> 1992 bytes editor/src/assets/iconfont/iconfont.woff2 | Bin 0 -> 1512 bytes editor/src/assets/styles/custom-theme.less | 5 + editor/src/assets/styles/index.less | 2 + editor/src/components/editor/config.ts | 301 + editor/src/components/editor/loading.vue | 34 + .../src/components/editor/mention-popover.vue | 24 + editor/src/components/editor/outline.vue | 205 + editor/src/main.ts | 48 + editor/src/plugins/audio/component/index.css | 86 + editor/src/plugins/audio/component/index.ts | 375 + editor/src/plugins/audio/index.ts | 138 + editor/src/plugins/audio/locales/en-US.ts | 12 + editor/src/plugins/audio/locales/index.ts | 7 + editor/src/plugins/audio/locales/zh-CN.ts | 12 + editor/src/plugins/audio/uploader.ts | 376 + editor/src/plugins/draw/component/constant.ts | 15 + .../plugins/draw/component/diagram-loader.ts | 11 + .../src/plugins/draw/component/draw-edit.vue | 75 + .../src/plugins/draw/component/draw-view.vue | 58 + editor/src/plugins/draw/component/index.css | 166 + editor/src/plugins/draw/component/index.ts | 143 + editor/src/plugins/draw/component/utils.ts | 59 + editor/src/plugins/draw/index.ts | 73 + editor/src/plugins/draw/types.ts | 15 + .../plugins/image/component/image/index.css | 189 + .../plugins/image/component/image/index.ts | 720 + editor/src/plugins/image/component/index.ts | 354 + .../plugins/image/component/pswp/index.css | 194 + .../src/plugins/image/component/pswp/index.ts | 338 + .../src/plugins/image/component/pswp/zoom.ts | 137 + editor/src/plugins/image/index.ts | 129 + editor/src/plugins/image/locales/en-US.ts | 19 + editor/src/plugins/image/locales/index.ts | 7 + editor/src/plugins/image/locales/zh-CN.ts | 19 + editor/src/plugins/image/types.ts | 56 + editor/src/plugins/image/uploader.ts | 630 + .../plugins/lightblock/component/index.tsx | 180 + .../lightblock/component/lightblock-theme.vue | 99 + .../plugins/lightblock/component/markdown.ts | 21 + .../plugins/lightblock/component/style.css | 93 + .../src/plugins/lightblock/component/types.ts | 23 + editor/src/plugins/lightblock/index.ts | 109 + editor/src/plugins/math/component/constant.ts | 307 + editor/src/plugins/math/component/index.ts | 80 + .../plugins/math/component/math-formula.vue | 225 + editor/src/plugins/math/component/type.ts | 9 + editor/src/plugins/math/index.ts | 79 + editor/src/plugins/tag/component/index.tsx | 66 + editor/src/plugins/tag/component/style.css | 91 + editor/src/plugins/tag/component/tag.tsx | 201 + editor/src/plugins/tag/component/type.ts | 16 + editor/src/plugins/tag/index.ts | 114 + editor/src/plugins/tag/local/index.ts | 28 + editor/src/plugins/test/component/index.ts | 57 + .../src/plugins/test/component/test-com.vue | 7 + editor/src/plugins/test/index.ts | 73 + editor/src/publice-path.ts | 9 + editor/src/router/index.ts | 75 + editor/src/shims-vue.d.ts | 9 + editor/src/store/index.ts | 9 + editor/src/utils/index.ts | 35 + editor/src/utils/request.ts | 120 + editor/src/views/editor/edit.vue | 245 + editor/src/views/editor/index.vue | 38 + editor/src/views/editor/view.vue | 128 + .../manual/components/manual-classify.vue | 210 + .../manual/components/manual-contents.vue | 442 + .../views/manual/components/manual-form.vue | 227 + .../views/manual/components/manual-list.vue | 301 + editor/src/views/manual/edit.vue | 58 + editor/src/views/manual/index.vue | 48 + editor/src/views/manual/view.vue | 116 + editor/tsconfig.json | 41 + editor/vue.config.js | 46 + editor/yarn.lock | 15441 +++++++++++++++ front/.env.development | 3 + front/.env.production | 3 + front/.eslintignore | 3 + front/.eslintrc.cjs | 37 + front/.gitignore | 28 + front/.stylelintignore | 2 + front/.stylelintrc.js | 6 + front/.vscode/settings.json | 13 + front/README.md | 3 + front/bin/publish.sh | 3 + front/index.html | 39 + front/jsconfig.json | 15 + front/package-lock.json | 14476 ++++++++++++++ front/package.json | 43 + front/public/default.svg | 1 + front/public/font/iconfont.css | 485 + front/public/font/iconfont.ttf | Bin 0 -> 31808 bytes front/public/imgs/entry/1.png | Bin 0 -> 6046 bytes front/public/imgs/entry/2.png | Bin 0 -> 6514 bytes front/public/imgs/entry/3.png | Bin 0 -> 6891 bytes front/public/imgs/entry/4.png | Bin 0 -> 6915 bytes front/public/imgs/entry/5.png | Bin 0 -> 6633 bytes front/public/imgs/entry/assess.png | Bin 0 -> 2165 bytes front/public/imgs/entry/course.png | Bin 0 -> 2555 bytes front/public/imgs/entry/evaluate.png | Bin 0 -> 1994 bytes front/public/imgs/entry/resource.png | Bin 0 -> 2344 bytes front/public/imgs/entry/textbook.png | Bin 0 -> 1994 bytes front/public/imgs/login-bg.jpg | Bin 0 -> 567259 bytes front/public/imgs/login-bottom.png | Bin 0 -> 1759 bytes front/public/imgs/login-draw.png | Bin 0 -> 482774 bytes front/public/imgs/login-top.png | Bin 0 -> 117194 bytes front/public/imgs/preview-full-volume.png | Bin 0 -> 63921 bytes .../imgs/preview-question-by-question.png | Bin 0 -> 52153 bytes front/public/js/axios.min.js | 2 + front/public/js/element-ui.min.js | 1 + front/public/js/socket.io.min.js | 7 + front/public/js/vue-router.min.js | 11 + front/public/js/vue.min.js | 6 + front/public/js/vuex.min.js | 6 + front/public/logo.svg | 1 + front/public/svg/404.svg | 1 + front/public/svg/nomore.svg | 1 + front/public/svg/none.svg | 1 + front/public/svg/noseach.svg | 1 + front/public/svg/timeout.svg | 1 + front/src/api/assessment-evaluation/exam.js | 83 + .../api/assessment-evaluation/humanEval.js | 30 + .../src/api/assessment-evaluation/mistake.js | 27 + .../api/assessment-evaluation/onlineTest.js | 49 + front/src/api/assessment-evaluation/paper.js | 99 + .../api/assessment-evaluation/questions.js | 111 + front/src/api/evaluation/index copy.js | 88 + front/src/api/evaluation/index.js | 41 + front/src/api/online-course/course-notes.js | 50 + front/src/api/online-course/my-course.js | 97 + front/src/api/online-course/online-FAQ.js | 72 + front/src/api/online-course/online-course.js | 72 + front/src/api/online-teaching/live-manage.js | 77 + front/src/api/online-teaching/live-student.js | 57 + front/src/api/statistic/index.js | 38 + front/src/api/system/index.js | 49 + front/src/api/system/org.js | 43 + front/src/api/system/resource.js | 150 + front/src/api/system/role.js | 34 + front/src/api/system/user.js | 91 + front/src/api/training/device.js | 96 + front/src/api/training/mqtt.js | 19 + front/src/api/training/operation.js | 41 + front/src/api/training/post.js | 41 + front/src/api/training/subject.js | 123 + front/src/api/training/train-evealuation.js | 72 + front/src/api/training/train.js | 99 + front/src/components/business/Preview.vue | 386 + front/src/components/layout/ActionBar.vue | 47 + front/src/components/layout/DialogLayout.vue | 112 + front/src/components/layout/FormLayout.vue | 57 + .../src/components/layout/SearchTreeMenu.vue | 133 + front/src/components/layout/TableLayout.vue | 129 + front/src/components/layout/index.js | 13 + front/src/components/widget/Pagination.vue | 33 + front/src/components/widget/QueryInput.vue | 61 + front/src/components/widget/SearchInput.vue | 55 + front/src/components/widget/index.js | 9 + front/src/main.js | 26 + front/src/router/feature.js | 223 + front/src/router/index.js | 77 + front/src/router/routes.js | 62 + front/src/store/index.js | 144 + front/src/styles/element-ui.scss | 103 + front/src/styles/index.scss | 238 + front/src/styles/variables.scss | 12 + front/src/utils/capture.js | 19 + front/src/utils/data-report.js | 57 + front/src/utils/fetch.js | 63 + front/src/utils/iframe-message.js | 18 + front/src/utils/index.js | 112 + front/src/utils/prototype.js | 435 + front/src/utils/qiankun-init.js | 51 + front/src/utils/simhei-normal.js | 7 + front/src/utils/storage.js | 52 + front/src/views/404/index.vue | 39 + .../components/ExamClassifySelector.vue | 92 + .../components/ExamTipsForm.vue | 86 + .../components/ModePriviewDialog.vue | 25 + .../components/StudentsSelector.vue | 339 + .../components/TeacherSelector.vue | 74 + .../add-modify-exam/index.vue | 560 + .../exam-arrangement/index.vue | 311 + .../components/FixedQuestionSelectTable.vue | 277 + .../components/PaperClassifySelector.vue | 70 + .../components/PaperSelectQuestionTable.vue | 346 + .../components/QuestionListPreview.vue | 40 + .../components/RandQuestionSelecter.vue | 194 + .../add-modify-paper/index.vue | 289 + .../components/ExamPaperSelector.vue | 137 + .../components/PrintPaper.vue | 654 + .../components/SelectExamPaper.vue | 248 + .../components/SelectJudgePaper.vue | 12 + .../examination-paper-manage/index.vue | 575 + .../human-evaluation/eval-details/index.vue | 338 + .../human-evaluation/index.vue | 287 + .../begin-online-exam/components/AQCard.vue | 233 + .../components/QuestionsList.vue | 322 + .../components/StudentTips.vue | 174 + .../components/TeacherDatas.vue | 248 + .../online-test/begin-online-exam/index.vue | 428 + .../online-test/index.vue | 469 + .../components/ImportQuestions.vue | 451 + .../components/KnowledgeSelector.vue | 99 + .../components/QuestionClassifySelector.vue | 70 + .../QuestionDifficultyLevelSelector.vue | 62 + .../components/QuestionTypeTabBar.vue | 106 + .../components/TagEditer.vue | 112 + .../add-modify-question/index.vue | 433 + .../components/QuestionItem.vue | 594 + .../question-bank-manage/index.vue | 382 + .../add-modify-sim-test/index.vue | 16 + .../simulation-test/index.vue | 403 + .../assessment-evaluation/utils/gy.srs.sdk.js | 260 + .../assessment-evaluation/utils/gyStyle.scss | 271 + .../utils/questionInfoMapJsonTrans.js | 31 + .../wrong-topic-consolidate/index.vue | 232 + .../mistakes-again/index.vue | 134 + front/src/views/electronic-textbook/index.vue | 36 + front/src/views/entry/components/Vcard.vue | 78 + front/src/views/entry/index.vue | 73 + .../components/selfStep.vue | 110 + .../evaluation-setting/index copy.vue | 202 + .../evaluation/evaluation-setting/index.vue | 101 + .../views/evaluation/my-evaluation/index.vue | 80 + .../student-evaluation/index copy.vue | 140 + .../evaluation/student-evaluation/index.vue | 173 + front/src/views/home/Breadcrumb.vue | 17 + front/src/views/home/HeadLayout.vue | 159 + front/src/views/home/MenuLayout.vue | 325 + .../home/global-components/PreviewDialog.vue | 24 + .../UpdatePasswordDialog.vue | 67 + .../home/global-components/UploadDialog.vue | 171 + .../views/home/global-components/index.vue | 20 + front/src/views/home/index.vue | 89 + front/src/views/login/index.vue | 208 + .../views/online-course/class-notes/index.vue | 398 + .../components/FAQ-info copy.vue | 354 + .../online-course/components/FAQ-info.vue | 582 + .../components/add-course/add-course.vue | 536 + .../add-course/course-courseware.vue | 161 + .../components/add-course/course-evaluate.vue | 417 + .../components/add-course/course-info.vue | 296 + .../components/add-course/course-section.vue | 1093 ++ .../add-course/course-statistics.vue | 208 + .../components/add-course/course-study.vue | 242 + .../online-course/components/button-selft.vue | 33 + .../components/course-disscuss.vue | 358 + .../components/my-course-info.vue | 269 + .../components/rich-text-editor.vue | 257 + .../course-exercises/course-exercises.vue | 149 + .../course-manage/add-coures/index.vue | 74 + .../course-manage/course-comment/index.vue | 47 + .../course-manage/course-evaluate/index.vue | 145 + .../online-course/course-manage/index.vue | 433 + .../online-course/course-study/index.vue | 48 + .../views/online-course/my-course/index.vue | 206 + .../views/online-course/online-FAQ/index.vue | 486 + front/src/views/online-course/utils.js | 14 + .../views/online-teaching/add-live/index.vue | 635 + .../online-teaching/components/chat-list.vue | 193 + .../components/live-layout.vue | 252 + .../live-answering-question/index.vue | 142 + .../online-teaching/live-in-class/index.vue | 190 + .../online-teaching/live-lectures/index.vue | 243 + .../views/online-teaching/live-play/index.vue | 251 + .../online-teaching/live-play/mixins/live.js | 113 + .../online-teaching/live-playback/index.vue | 9 + .../online-teaching/live-watch/index.vue | 135 + front/src/views/register/index.vue | 398 + .../charts/admin/charts/ResQuantity.vue | 67 + .../statistic/charts/admin/charts/ResSize.vue | 70 + .../charts/admin/charts/ServicePeriod.vue | 72 + .../charts/admin/charts/SystemOrg.vue | 66 + .../charts/admin/charts/SystemUse.vue | 66 + .../charts/admin/charts/SystemUser.vue | 66 + .../statistic/charts/admin/charts/index.vue | 20 + .../src/views/statistic/charts/admin/index.js | 17 + .../charts/student/course/CourseContent.vue | 101 + .../charts/student/course/CourseList.vue | 17 + .../charts/student/course/LearningState.vue | 79 + .../statistic/charts/student/course/index.vue | 10 + .../views/statistic/charts/student/index.js | 22 + .../student/mistakes/MistakesDetailsTable.vue | 15 + .../mistakes/MistakesIsPassPercent.vue | 75 + .../student/mistakes/MistakesTypePercent.vue | 75 + .../charts/student/mistakes/index.vue | 12 + .../charts/student/note/StudentNoteCount.vue | 25 + .../charts/student/note/StudentNoteInfo.vue | 50 + .../note/StudentNoteUpdateRankings.vue | 33 + .../student/note/StudentNoteViewRanking.vue | 33 + .../student/note/components/NoteTable.vue | 37 + .../statistic/charts/student/note/index.vue | 13 + .../student/paper/PaperDetailsTable.vue | 100 + .../paper/PaperDifficultyByHistoryPercent.vue | 84 + .../student/paper/PaperDifficultyPercent.vue | 75 + .../student/paper/PaperIsFixedPercent.vue | 75 + .../student/paper/PaperMistakesPercent.vue | 75 + .../student/paper/PaperScorePassPercent.vue | 74 + .../statistic/charts/student/paper/index.vue | 18 + .../score/ExamDetailsMistakesPercent.vue | 85 + .../score/ExamDetailsScoreOrderTower.vue | 70 + .../student/score/ExamDetailsScorePercent.vue | 85 + .../charts/student/score/ExamDetailsTable.vue | 104 + .../student/score/ExamDurationTower.vue | 62 + .../student/score/ExamHistroyScoreLine.vue | 57 + .../student/score/ExamMistakesTypePercent.vue | 75 + .../student/score/ExamScorePassPercent.vue | 73 + .../charts/student/score/ExamScoreTower.vue | 69 + .../statistic/charts/student/score/index.vue | 15 + .../student/view-resource/ResDetailsTable.vue | 72 + .../student/view-resource/ResQuantity.vue | 67 + .../charts/student/view-resource/ResSize.vue | 70 + .../charts/student/view-resource/index.vue | 13 + .../charts/teacher/course/DetailAnalysis.vue | 48 + .../teacher/course/SettingCourseHour.vue | 76 + .../teacher/course/StudnetStudyAnalyse.vue | 82 + .../teacher/course/StudyCourseDuration.vue | 76 + .../statistic/charts/teacher/course/index.vue | 12 + .../ExamDetailsJoinStudentCountPercent.vue | 97 + .../teacher/examination/ExamDetailsLayout.vue | 87 + .../ExamDetailsMistakesPercent.vue | 98 + .../ExamDetailsQuestionsCountPercent.vue | 98 + .../ExamDetailsQuestionsScorePercent.vue | 97 + .../ExamDetailsScoreOrderTower.vue | 81 + .../ExamDetailsScorePassPercent.vue | 88 + .../teacher/examination/ExamDurationTower.vue | 60 + .../examination/ExamStudentTimesTower.vue | 68 + .../examination/StudentsScorePassPercent.vue | 74 + .../charts/teacher/examination/index.vue | 22 + .../exercise/ExerciseDurationPercent.vue | 86 + .../ExerciseQuestionIsPassPercent.vue | 87 + .../teacher/exercise/ExercuseTimesPercent.vue | 86 + .../charts/teacher/exercise/index.vue | 11 + .../views/statistic/charts/teacher/index.js | 24 + .../live-broadcast/LiveAllBroadcast.vue | 34 + .../live-broadcast/LiveDetailBroadcast.vue | 69 + .../live-broadcast/LiveDetailScoreTower.vue | 79 + .../live-broadcast/LiveDetailStuCountLine.vue | 69 + .../charts/teacher/live-broadcast/index.vue | 15 + .../teacher/resources/ResDetailsTable.vue | 66 + .../charts/teacher/resources/ResFileData.vue | 32 + .../charts/teacher/resources/ResPerview.vue | 67 + .../charts/teacher/resources/ResQuantity.vue | 67 + .../charts/teacher/resources/ResSize.vue | 70 + .../charts/teacher/resources/ResTypeList.vue | 82 + .../charts/teacher/resources/index.vue | 15 + .../test-question/MistakeOrderTower.vue | 57 + .../test-question/QuestionDetailsLayout.vue | 45 + .../QuestionDifficultyByTypePercent.vue | 89 + ...estionsCountByTypeAndDifficultyPercent.vue | 131 + .../QuestionsCountByTypePercent.vue | 74 + .../charts/teacher/test-question/index.vue | 21 + .../teacher/trainee/basic/TraineeAge.vue | 78 + .../teacher/trainee/basic/TraineeNumber.vue | 25 + .../teacher/trainee/basic/TraineeOrg.vue | 60 + .../charts/teacher/trainee/basic/index.vue | 22 + .../exam/ExamDetailsStudentScoreTable.vue | 86 + .../trainee/exam/ExamStudentScoreTower.vue | 78 + .../trainee/exam/ExamStudentTimesTower.vue | 70 + .../exam/StudentsMistakesTypePercent.vue | 84 + .../trainee/exam/StudentsScorePassPercent.vue | 87 + .../charts/teacher/trainee/exam/index.vue | 41 + .../trainee/exercise/ExerciseDetailsTable.vue | 131 + .../ExerciseQuestionIsPassPercent.vue | 96 + .../exercise/ExerciseQuestionTypePercent.vue | 95 + .../trainee/exercise/ExercuseTimesPercent.vue | 95 + .../charts/teacher/trainee/exercise/index.vue | 43 + .../charts/teacher/trainee/index.vue | 18 + .../teacher/trainee/study/CourseInfoTable.vue | 0 .../charts/teacher/trainee/study/index.vue | 42 + .../trainee/system-use/ServicePeriod.vue | 77 + .../teacher/trainee/system-use/SystemUse.vue | 71 + .../teacher/trainee/system-use/index.vue | 24 + .../components/CircleNumberChart.vue | 68 + .../statistic/components/ExamListChart.vue | 61 + .../components/ExercisePerviewPaper.vue | 51 + .../statistic/components/OrgUserChart.vue | 113 + .../components/QuestionTypeListChart.vue | 92 + .../src/views/statistic/components/VCard.vue | 121 + .../views/statistic/components/VChartTab.vue | 62 + .../views/statistic/components/VListCard.vue | 88 + .../src/views/statistic/components/VTitle.vue | 18 + front/src/views/statistic/index.vue | 137 + front/src/views/system/org/index.vue | 88 + front/src/views/system/resource/index.vue | 443 + .../system/resource/mixins/contextmenu.js | 360 + .../src/views/system/resource/share/index.vue | 451 + .../system/resource/share/share-disscuss.vue | 351 + front/src/views/system/role/index.vue | 326 + .../system/user/batch-add-user/index.vue | 498 + front/src/views/system/user/index.vue | 357 + front/src/views/test/index.vue | 14 + front/src/views/textbook-manage/index.vue | 36 + .../device-create/components/create-part.vue | 207 + .../components/device-ip-table.vue | 238 + .../components/device-part-table.vue | 230 + .../device-create/components/operation.vue | 226 + .../device/device-create/components/post.vue | 220 + .../training/device/device-create/index.vue | 334 + front/src/views/training/device/index.vue | 463 + front/src/views/training/device/index_.vue | 28 + .../src/views/training/evealuation/index.vue | 47 + .../components/PositionStepOperation.vue | 138 + .../components/TeamGroupDuration.vue | 104 + .../components/TeamgroupAverageScores.vue | 121 + .../evealuation/subject-evaluation/index.vue | 133 + .../components/ScoreEvealuation.vue | 69 + .../components/StepClassifyEvealuation.vue | 228 + .../components/StepDetailEvealuation.vue | 260 + .../components/StepDurationEvealuation.vue | 149 + .../components/StepEvealuation.vue | 137 + .../train-evaluation/components/WaterPolo.vue | 163 + .../evealuation/train-evaluation/index.vue | 289 + .../user-evaluation/components/UserInfo.vue | 132 + .../components/UserOpeartionResult.vue | 130 + .../components/UserPositionGrade.vue | 113 + .../components/UserPostion.vue | 121 + .../components/UserSubjectGrade.vue | 117 + .../components/UserTrainDuraion.vue | 107 + .../components/UserTrainSubjectGrade.vue | 110 + .../evealuation/user-evaluation/index.vue | 62 + .../subject/components/editorClassify.vue | 125 + .../training/subject/editor-subject/index.vue | 538 + front/src/views/training/subject/index.vue | 479 + .../training/components/train-chunk-box.vue | 55 + .../training/components/train-info.vue | 488 + .../components/JudgRuleSetting.vue | 149 + .../components/OperationSetting1.vue | 322 + .../training/training/cteate-train/index.scss | 35 + .../training/training/cteate-train/index.vue | 404 + front/src/views/training/training/index.vue | 129 + .../components/train-calculate-score.vue | 149 + .../train-monitor/components/train-step.vue | 250 + .../train-monitor/components/train-video.vue | 152 + .../training/training/train-monitor/index.vue | 574 + .../training/training/utils/train-mqtt.js | 613 + front/sub-base/.eslintrc.cjs | 19 + front/sub-base/.gitignore | 28 + front/sub-base/.prettierrc.json | 8 + front/sub-base/.vscode/extensions.json | 8 + front/sub-base/README.md | 46 + front/sub-base/env.d.ts | 1 + front/sub-base/index.html | 13 + front/sub-base/package-lock.json | 6377 +++++++ front/sub-base/package.json | 36 + front/sub-base/public/favicon.ico | Bin 0 -> 4286 bytes front/sub-base/src/App.vue | 10 + front/sub-base/src/assets/base.css | 0 front/sub-base/src/assets/logo.svg | 1 + front/sub-base/src/assets/main.css | 0 front/sub-base/src/main.ts | 49 + front/sub-base/src/publice-path.ts | 9 + front/sub-base/src/router/index.ts | 24 + front/sub-base/src/views/home/index.vue | 15 + front/sub-base/tsconfig.json | 51 + front/sub-base/tsconfig.node.json | 16 + front/sub-base/vite.config.ts | 25 + front/types/env.d.ts | 7 + front/types/index.d.ts | 2 + front/types/prototype.d.ts | 49 + front/types/vue.d.ts | 145 + front/vite.config.js | 55 + .eslintignore => serve/.eslintignore | 0 .eslintrc.js => serve/.eslintrc.js | 50 +- .gitignore => serve/.gitignore | 796 +- .prettierrc => serve/.prettierrc | 8 +- serve/.vscode/settings.json | 7 + .../192.168.0.32_3000_key | 56 +- serve/192.168.0.32_3000_ssl.crt | 24 + serve/README.md | 92 + serve/bin/publish.sh | 11 + serve/front/assets/AQCard.05076a74.css | 1 + serve/front/assets/AQCard.8a0f8a13.js | 1 + serve/front/assets/ActionBar.1725b1a3.js | 1 + serve/front/assets/ActionBar.f9e04dd2.css | 1 + .../assets/CircleNumberChart.919f2108.js | 1 + .../assets/CircleNumberChart.9fb14e05.css | 1 + serve/front/assets/DialogLayout.11655234.css | 1 + serve/front/assets/DialogLayout.e3af3567.js | 1 + .../ExamDetailsMistakesPercent.7c60de8f.js | 7 + .../assets/ExercisePerviewPaper.8e37b607.css | 1 + .../assets/ExercisePerviewPaper.d6f9cd34.js | 1 + serve/front/assets/FormLayout.c7820bdd.js | 1 + serve/front/assets/HeadLayout.8db1abec.css | 1 + serve/front/assets/HeadLayout.d9a9c266.js | 1 + .../assets/PaperClassifySelector.d254c4c1.js | 1 + .../PaperSelectQuestionTable.8e89d183.css | 1 + .../PaperSelectQuestionTable.9e024c19.js | 1 + serve/front/assets/Preview.0aa9dcef.js | 1 + serve/front/assets/Preview.d24ffafb.css | 1 + serve/front/assets/PreviewDialog.e564b39f.js | 1 + serve/front/assets/QueryInput.7afa399e.js | 1 + serve/front/assets/QueryInput.7bc78197.css | 1 + ...uestionDifficultyLevelSelector.b9a0d127.js | 1 + serve/front/assets/QuestionItem.1944680c.js | 1 + serve/front/assets/QuestionItem.304950a3.css | 1 + .../assets/QuestionListPreview.baa07396.js | 1 + serve/front/assets/QuestionsList.4ae2bac5.js | 1 + serve/front/assets/QuestionsList.54d973dd.css | 1 + serve/front/assets/SearchTreeMenu.cf3d6165.js | 1 + .../front/assets/SearchTreeMenu.db7e3694.css | 1 + .../assets/StudentsSelector.40d0c907.css | 1 + .../front/assets/StudentsSelector.cadc0de6.js | 1 + serve/front/assets/TableLayout.05a39499.js | 1 + serve/front/assets/TableLayout.6296ef0f.css | 1 + .../assets/UpdatePasswordDialog.45f14f6b.js | 1 + serve/front/assets/UploadDialog.0d4280ae.js | 1 + serve/front/assets/UploadDialog.4143fbe8.css | 1 + serve/front/assets/VListCard.47c5661c.js | 1 + serve/front/assets/VListCard.b818974a.css | 1 + serve/front/assets/add-course.4b40ab05.css | 1 + serve/front/assets/add-course.4b5bbdd4.js | 2 + serve/front/assets/button-selft.a87deecf.js | 1 + serve/front/assets/button-selft.ad30105c.css | 1 + serve/front/assets/clickoutside.ffdfe148.js | 5 + .../front/assets/course-disscuss.1394e5b3.js | 1 + .../front/assets/course-disscuss.db9c8ad8.css | 1 + .../assets/course-exercises.67658af8.css | 1 + .../front/assets/course-exercises.98a38110.js | 1 + serve/front/assets/data-report.0c7c73f2.js | 1 + serve/front/assets/element-icons.a30f5b3b.ttf | Bin 0 -> 55956 bytes .../front/assets/element-icons.ab40a589.woff | Bin 0 -> 28200 bytes serve/front/assets/exam.a3ef7d5b.js | 1 + serve/front/assets/gy.srs.a1080737.css | 1 + serve/front/assets/gy.srs.sdk.ab1fd116.js | 1 + serve/front/assets/gyStyle.a4df0706.css | 1 + serve/front/assets/humanEval.e7206f1d.js | 1 + serve/front/assets/index.00fb47d4.css | 1 + serve/front/assets/index.02031892.js | 1 + serve/front/assets/index.088a0e2d.js | 1 + serve/front/assets/index.08f897cf.js | 1 + serve/front/assets/index.094fa61c.js | 9 + serve/front/assets/index.0a0cd168.js | 1 + serve/front/assets/index.0a12cf7f.css | 1 + serve/front/assets/index.0c29f644.css | 1 + serve/front/assets/index.0c6b81d7.js | 1 + serve/front/assets/index.0c8819e3.css | 1 + serve/front/assets/index.10c55d71.css | 1 + serve/front/assets/index.1318b6d4.js | 1 + serve/front/assets/index.1b0182dd.js | 1 + serve/front/assets/index.1d3c6214.css | 1 + serve/front/assets/index.1f9f6991.js | 1 + serve/front/assets/index.24141550.js | 23 + serve/front/assets/index.27767255.js | 1 + serve/front/assets/index.2835fb30.js | 1 + serve/front/assets/index.2adbb9ac.js | 1 + serve/front/assets/index.318f92ba.css | 1 + serve/front/assets/index.31addd92.css | 1 + serve/front/assets/index.31e68358.css | 1 + serve/front/assets/index.33b83220.css | 1 + serve/front/assets/index.372ef4bf.js | 1 + serve/front/assets/index.37445d9f.css | 1 + serve/front/assets/index.394186a5.js | 1 + serve/front/assets/index.39df79ab.js | 1 + serve/front/assets/index.3dbd9863.js | 1 + serve/front/assets/index.3faddd0c.js | 18 + serve/front/assets/index.402d8413.css | 1 + serve/front/assets/index.41e73620.css | 1 + serve/front/assets/index.440732ba.js | 1 + serve/front/assets/index.4e0d39bc.css | 1 + serve/front/assets/index.51534537.css | 1 + serve/front/assets/index.56eac716.css | 1 + serve/front/assets/index.593aa49c.css | 1 + serve/front/assets/index.5ae359fe.js | 1 + serve/front/assets/index.5d347c48.js | 1 + serve/front/assets/index.5e300020.css | 1 + serve/front/assets/index.5f8fbc69.js | 1 + serve/front/assets/index.5fe5be39.js | 14 + serve/front/assets/index.66725098.js | 1 + serve/front/assets/index.6747e3af.js | 1 + serve/front/assets/index.68f0e8bd.css | 1 + serve/front/assets/index.69e06f8c.css | 1 + serve/front/assets/index.6c27c93b.css | 1 + serve/front/assets/index.6ecdb8d4.css | 1 + serve/front/assets/index.7035dba5.js | 1 + serve/front/assets/index.71f43bba.css | 1 + serve/front/assets/index.72c376a6.css | 1 + serve/front/assets/index.743685a6.js | 1 + serve/front/assets/index.74af49c3.js | 1 + serve/front/assets/index.7aa299ba.js | 1 + serve/front/assets/index.7c9780be.css | 1 + serve/front/assets/index.80079b79.js | 1 + serve/front/assets/index.8176c251.js | 1 + serve/front/assets/index.83b38f22.css | 1 + serve/front/assets/index.87f287e8.js | 1 + serve/front/assets/index.8d667c0f.css | 1 + serve/front/assets/index.8fb1167b.js | 1 + serve/front/assets/index.8ff275da.css | 1 + serve/front/assets/index.9145d306.css | 1 + serve/front/assets/index.93eadc68.css | 1 + serve/front/assets/index.9706fae9.css | 1 + serve/front/assets/index.97da6707.js | 1 + serve/front/assets/index.9aa689ff.css | 1 + serve/front/assets/index.9dec3212.css | 1 + serve/front/assets/index.a4f3e52e.css | 1 + serve/front/assets/index.a7ea6e92.css | 1 + serve/front/assets/index.ad169e89.js | 1 + serve/front/assets/index.aeb5e9f3.js | 1 + serve/front/assets/index.b0bfe0f6.js | 1 + serve/front/assets/index.b2106966.js | 1 + serve/front/assets/index.b3c76749.css | 1 + serve/front/assets/index.b461b679.js | 1 + serve/front/assets/index.b4b565bd.css | 1 + serve/front/assets/index.b4df0615.js | 1 + serve/front/assets/index.b6618dcb.js | 1 + serve/front/assets/index.b95816a8.js | 1 + serve/front/assets/index.bb5f1b0a.js | 61 + serve/front/assets/index.bd28e25c.css | 1 + serve/front/assets/index.bd669162.js | 1 + serve/front/assets/index.bd813a75.js | 1 + serve/front/assets/index.bec79140.js | 1 + serve/front/assets/index.bee3c024.js | 1 + serve/front/assets/index.bf24d501.css | 1 + serve/front/assets/index.c021b48b.js | 1 + serve/front/assets/index.c357f30c.css | 1 + serve/front/assets/index.cc7579bf.js | 1 + serve/front/assets/index.cd60bb8b.js | 1 + serve/front/assets/index.ce07e0bd.js | 13 + serve/front/assets/index.cf1c077a.js | 1 + serve/front/assets/index.d7316fc5.js | 1 + serve/front/assets/index.da1f3265.css | 1 + serve/front/assets/index.dc5f5f64.css | 1 + serve/front/assets/index.ddd17e1a.js | 1 + serve/front/assets/index.def88f1f.js | 1 + serve/front/assets/index.e0f63d00.css | 1 + serve/front/assets/index.e13f82bf.js | 1 + serve/front/assets/index.e8f6b657.js | 1 + serve/front/assets/index.ea805aac.js | 7 + serve/front/assets/index.ebb776b9.css | 1 + serve/front/assets/index.efbda710.js | 1 + serve/front/assets/index.f5168bf8.js | 1 + serve/front/assets/index.fb88617a.js | 1 + serve/front/assets/index.fc1a0811.css | 1 + serve/front/assets/index.fc765047.js | 1 + serve/front/assets/index.fcae50d5.css | 1 + serve/front/assets/index.ffffb6ad.js | 1 + serve/front/assets/live-manage.d907c119.js | 1 + serve/front/assets/live-student.e6ad7a61.js | 1 + serve/front/assets/mistake.4e233ae2.js | 1 + serve/front/assets/my-course.d26f38fb.js | 1 + serve/front/assets/online-course.07094acf.js | 1 + serve/front/assets/onlineTest.1d86eb4e.js | 1 + serve/front/assets/org.23184f20.js | 1 + serve/front/assets/package.3367ca0a.js | 1 + serve/front/assets/paper.4b971da3.js | 1 + .../questionInfoMapJsonTrans.58e126b5.js | 1 + serve/front/assets/questions.7b7a7919.js | 1 + .../front/assets/rich-text-editor.702494a8.js | 859 + .../assets/rich-text-editor.79e8f191.css | 1 + serve/front/assets/role.1c66ca6a.js | 1 + serve/front/assets/user.64525a2f.js | 1 + serve/front/assets/utils.068fedb0.js | 1 + .../assets/vueComponentNormalizer.9ef17bb1.js | 1 + serve/front/assets/xlsx.db07aefa.js | 104 + serve/front/default.svg | 1 + serve/front/font/iconfont.css | 485 + serve/front/font/iconfont.ttf | Bin 0 -> 31808 bytes serve/front/imgs/entry/1.png | Bin 0 -> 6046 bytes serve/front/imgs/entry/2.png | Bin 0 -> 6514 bytes serve/front/imgs/entry/3.png | Bin 0 -> 6891 bytes serve/front/imgs/entry/4.png | Bin 0 -> 6915 bytes serve/front/imgs/entry/5.png | Bin 0 -> 6633 bytes serve/front/imgs/entry/assess.png | Bin 0 -> 2165 bytes serve/front/imgs/entry/course.png | Bin 0 -> 2555 bytes serve/front/imgs/entry/evaluate.png | Bin 0 -> 1994 bytes serve/front/imgs/entry/resource.png | Bin 0 -> 2344 bytes serve/front/imgs/entry/textbook.png | Bin 0 -> 1994 bytes serve/front/imgs/login-bg.jpg | Bin 0 -> 567259 bytes serve/front/imgs/login-bottom.png | Bin 0 -> 1759 bytes serve/front/imgs/login-draw.png | Bin 0 -> 482774 bytes serve/front/imgs/login-top.png | Bin 0 -> 117194 bytes serve/front/imgs/preview-full-volume.png | Bin 0 -> 63921 bytes .../imgs/preview-question-by-question.png | Bin 0 -> 52153 bytes serve/front/index.html | 31 + serve/front/js/axios.min.js | 2 + serve/front/js/element-ui.min.js | 1 + serve/front/js/socket.io.min.js | 7 + serve/front/js/vue-router.min.js | 11 + serve/front/js/vue.min.js | 6 + serve/front/js/vuex.min.js | 6 + serve/front/logo.svg | 1 + serve/front/svg/404.svg | 1 + serve/front/svg/nomore.svg | 1 + serve/front/svg/none.svg | 1 + serve/front/svg/noseach.svg | 1 + serve/front/svg/timeout.svg | 1 + serve/nest-cli.json | 8 + serve/package-lock.json | 15761 ++++++++++++++++ serve/package.json | 91 + serve/src/app.module.ts | 32 + .../article_manage.controller.ts | 76 + .../article_manage/article_manage.module.ts | 12 + .../article_manage/article_manage.service.ts | 101 + .../dto/create-article_manage.dto.ts | 60 + .../dto/update-article_manage.dto.ts | 6 + .../dto/update-portion-article.dto.ts | 15 + .../entities/article_manage.entity.ts | 41 + .../assessment-evaluation.module.ts | 32 + .../assessment-evaluation.controller.ts | 37 + .../exam-manager/exam-classify.controller.ts | 54 + .../exam-manager/exam.controller.ts | 122 + .../online-exam-history.controller.ts | 278 + .../student-online-exam.controller.ts | 110 + .../exam-paper-classify.controller.ts | 64 + .../exam-paper.controller.ts | 205 + .../knowledge-point.controller.ts | 64 + .../question-classify.controller.ts | 64 + .../question-difficulty-level.controller.ts | 68 + .../question-type.controller.ts | 61 + .../question-manager/question.controller.ts | 212 + .../sim-test/mistake-again.controller.ts | 192 + .../sim-test/sim-test.controller.ts | 140 + .../dto/create-assessment-evaluation.dto.ts | 1 + .../exam-classify/create-exam-classify.dto.ts | 17 + .../exam-classify/update-exam-classify.dto.ts | 14 + .../dtos-exam-manager/exam/create-exam.dto.ts | 88 + .../dtos-exam-manager/exam/paging-exam.dto.ts | 10 + .../dtos-exam-manager/exam/update-exam.dto.ts | 74 + .../create-student-online-exam.dto.ts | 28 + .../paging-student-online-exam.dto.ts | 7 + .../update-student-online-exam.dto.ts | 22 + .../create-exam-paper-classify.dto.ts | 17 + .../update-exam-paper-classify.dto.ts | 14 + .../exam-paper/create-exam-paper.dto.ts | 53 + .../exam-paper/paging-exam-paper.dto.ts | 18 + .../exam-paper/update-exam-paper.dto.ts | 60 + .../create-question-for-paper.dto.ts | 25 + .../create-knowledge-point.dto.ts | 9 + .../update-knowledge-point.dto.ts | 13 + .../create-question-classify.dto.ts | 17 + .../update-question-classify.dto.ts | 14 + .../create-question-difficulty-level.dto.ts | 9 + .../update-question-difficulty-level.dto.ts | 13 + .../question-type/create-question-type.dto.ts | 12 + .../question-type/update-question-type.dto.ts | 16 + .../question/create-question.dto.ts | 40 + .../question/get-question-count.dto.ts | 16 + .../question/paging-question.dto.ts | 19 + .../question/update-question.dto.ts | 33 + .../dto/pagination.dto.ts | 38 + .../dto/update-assessment-evaluation.dto.ts | 6 + .../entities/assessment-evaluation.entity.ts | 1 + .../exam-classify.entity.ts | 17 + .../exam-questions-result.entity.ts | 29 + .../entities-exam-manager/exam.entity.ts | 94 + .../online-exam-history.entity.ts | 48 + .../student-online-exam.entity.ts | 25 + .../exam-paper-classify.entity.ts | 17 + .../exam-paper.entity.ts | 52 + .../questions-for-paper.entity.ts | 23 + .../knowledge-point.entity.ts | 11 + .../question-classify.entity.ts | 17 + .../question-difficulty-level.entity.ts | 11 + .../question-type.entity.ts | 16 + .../question.entity.ts | 38 + .../modules/exam-manager.modules.ts | 39 + .../modules/exam-paper-manager.modules.ts | 23 + .../modules/question-manager.modules.ts | 40 + .../service/assessment-evaluation.service.ts | 39 + .../exam-classify.service.ts | 52 + .../exam-questions-result.service.ts | 153 + .../service-exam-manager/exam.service.ts | 165 + .../online-exam-history.service.ts | 153 + .../student-online-exam.service.ts | 400 + .../exam-paper-classify.service.ts | 59 + .../exam-paper.service.ts | 357 + .../questions-for-paper.ts | 198 + .../knowledge-point.service.ts | 49 + .../question-classify.service.ts | 66 + .../question-difficulty-level.service.ts | 59 + .../question-type.service.ts | 44 + .../question.service.ts | 322 + .../common/decorator/no-token.decorator.ts | 4 + .../decorator/query-page-info.decorator.ts | 8 + .../common/decorator/token-data.decorator.ts | 8 + serve/src/common/dto/create.dto.ts | 8 + serve/src/common/dto/update.dto.ts | 7 + serve/src/common/entities/base.entity.ts | 37 + .../common/entities/increment-id.entity.ts | 7 + serve/src/common/entities/page-info.entity.ts | 30 + .../src/common/entities/token-data.entity.ts | 15 + .../common/filters/global-exception.filter.ts | 41 + .../interceptor/response.interceptor.ts | 24 + .../common/plugins/custom-type-orm-logger.ts | 133 + serve/src/common/plugins/index.ts | 15 + serve/src/common/plugins/mysql.ts | 9 + serve/src/common/plugins/static-assets.ts | 9 + serve/src/common/plugins/swagger.ts | 12 + serve/src/env.ts | 43 + .../evaluation-indicator.controller.ts | 73 + .../controller/evaluation.controller.ts | 42 + .../dto/evaluation-indicator.dto.ts | 13 + .../src/evaluation copy/dto/evaluation.dto.ts | 1 + .../dto/student-evaluation.dto.ts | 17 + .../entities/evaluation-indicator.entity.ts | 14 + .../entities/evaluation.entity.ts | 14 + .../entities/student-evaluation.entity.ts | 17 + .../src/evaluation copy/evaluation.module.ts | 22 + .../service/evaluation-indicator.service.ts | 52 + .../service/evaluation.service.ts | 43 + .../evaluation-indicator.controller.ts | 36 + .../controller/evaluation.controller.ts | 42 + .../dto/evaluation-indicator.dto.ts | 9 + serve/src/evaluation/dto/evaluation.dto.ts | 1 + .../evaluation/dto/student-evaluation.dto.ts | 13 + .../entities/evaluation-indicator.entity.ts | 11 + .../evaluation/entities/evaluation.entity.ts | 14 + .../entities/student-evaluation.entity.ts | 14 + serve/src/evaluation/evaluation.module.ts | 22 + .../service/evaluation-indicator.service.ts | 25 + .../evaluation/service/evaluation.service.ts | 51 + serve/src/initialize/data-source.ts | 123 + serve/src/initialize/initialize.controller.ts | 29 + serve/src/initialize/initialize.module.ts | 7 + serve/src/main.ts | 47 + .../dto/create-manuals_manage.dto.ts | 40 + .../dto/create-manuals_manage_classify.dto.ts | 12 + .../dto/update-manuals_manage.dto.ts | 7 + ...update-manuals_manage_classify.dto copy.ts | 6 + .../entities/manuals_manage.entity.ts | 37 + .../manuals_manage_classify.entity.ts | 10 + .../manuals_manage.controller.ts | 110 + .../manuals_manage/manuals_manage.module.ts | 22 + .../manuals_manage/manuals_manage.service.ts | 114 + serve/src/online-course/common/delete-dto.ts | 11 + .../online-course/common/pagination.dto.ts | 38 + .../controller/course-manage.controller.ts | 135 + .../controller/course-my.controller.ts | 110 + .../controller/course-notes.controller.ts | 59 + .../controller/online-answer.controller.ts | 76 + .../dto/course-manage/course-classify.dto.ts | 18 + .../course-manage/create-online-course.dto.ts | 156 + .../dto/course-manage/get-course-list-dto.ts | 22 + .../update-course-classify.dto.ts | 9 + .../course-manage/update-online-course.dto.ts | 11 + .../course-my/course-study-courseware.dto.ts | 44 + .../course-my/course-study-discussion.dto.ts | 35 + .../course-my/course-study-evaluate.dto.ts | 38 + .../course-my/course-study-exercise.dto.ts | 39 + .../dto/course-my/course-study-record.dto.ts | 61 + .../dto/course-notes/course-nots.dto.ts | 31 + .../dto/online-issues/online-answer.dto.ts | 21 + .../dto/online-issues/online-issues.dto.ts | 35 + .../course-manage/course-classify.entity.ts | 27 + .../course-manage/course-courseware.entity.ts | 41 + .../course-manage/course-evaluate.entity.ts | 26 + .../course-manage/course-list.entity.ts | 86 + .../course-manage/course-questions.entity.ts | 28 + .../course-section-content.entity.ts | 31 + .../course-study-courseware.entity.ts | 20 + .../course-study-discussion-kudos.entity.ts | 17 + .../course-study-discussion.entity.ts | 48 + .../course-my/course-study-evaluate.entity.ts | 20 + .../course-my/course-study-exercise.entity.ts | 29 + .../course-my/course-study-record.entity.ts | 41 + .../course-notes/course-notes.entity.ts | 17 + .../online-issues/online-answer.entity.ts | 17 + .../online-issues/online-issues.entity.ts | 19 + .../src/online-course/online-course.module.ts | 66 + .../service/course-notes.service.ts | 65 + .../service/my-course.service.ts | 504 + .../service/online-course.service.ts | 376 + .../service/online-issues.service.ts | 221 + .../controller/live-class.controller.ts | 73 + .../controller/live-teaching.controller.ts | 116 + .../online-teaching/dto/live-classify.dto.ts | 18 + .../online-teaching/dto/live-create.dto.ts | 102 + .../online-teaching/dto/live-msg-data.dto.ts | 22 + .../dto/live-question-answer.dto.ts | 36 + .../online-teaching/dto/live-update.dto.ts | 9 + .../entities/live-classify.entity.ts | 28 + .../entities/live-courseware.entity.ts | 25 + .../entities/live-evaluate.entity.ts | 27 + .../entities/live-msg-data.entity.ts | 45 + .../entities/live-question-answer.entity.ts | 29 + .../entities/live-question.entity.ts | 34 + .../entities/live-student.entity.ts | 25 + .../entities/live-teaching.entity.ts | 95 + .../gateway/online-teaching.gateway.ts | 109 + .../online-teaching/online-teaching.module.ts | 51 + .../service/live-class.service.ts | 242 + .../service/live-teaching.service.ts | 240 + serve/src/online-teaching/ws-jwt-guard.ts | 24 + serve/src/prototype.ts | 27 + .../resource-classify.controller.ts | 52 + .../controller/resource.controller.ts | 246 + .../src/resource/dto/resource-classify.dto.ts | 13 + .../entities/resource-classify.entity.ts | 14 + .../entities/resource-discussion.entity.ts | 43 + .../src/resource/entities/resource.entity.ts | 40 + serve/src/resource/resource.module.ts | 23 + .../service/resource-classify.service.ts | 33 + .../service/resource-discussion.service.ts | 105 + .../src/resource/service/resource.service.ts | 327 + .../statistic/entities/report-data.entity.ts | 40 + .../service/admin/charts/res-quantity.ts | 5 + .../service/admin/charts/res-size.ts | 5 + .../service/admin/charts/system-org.ts | 14 + .../admin/charts/system-service-period.ts | 7 + .../service/admin/charts/system-use.ts | 5 + .../service/admin/charts/system-user.ts | 12 + .../statistic/service/admin/index/index.ts | 33 + .../service/student/course/course-content.ts | 38 + .../service/student/course/learning-state.ts | 121 + .../statistic/service/student/index/index.ts | 99 + .../mistakes/mistakes-is-pass-percent.ts | 35 + .../mistakes/paper-mistakes-percent.ts | 1 + .../student/note/student-note-count.ts | 7 + .../service/student/note/student-note-info.ts | 6 + .../note/student-note-update-rankings.ts | 6 + .../note/student-note-view-rankings.ts | 6 + .../paper/exam-details-mistakes-percent.ts | 1 + .../paper/exam-details-score-percent.ts | 1 + .../student/paper/exam-duration-tower.ts | 1 + .../student/paper/exam-history-score-line.ts | 1 + .../paper/exam-mistakes-type-percent.ts | 1 + .../student/paper/exam-score-pass-percent.ts | 1 + .../service/student/paper/exam-score-tower.ts | 1 + .../student/paper/paper-details-table.ts | 82 + .../paper/paper-difficulty-by-history.ts | 49 + .../student/paper/paper-difficulty-percent.ts | 69 + .../student/paper/paper-is-fixed-percent.ts | 57 + .../student/paper/paper-mistakes-percent.ts | 32 + .../student/paper/paper-score-pass-percent.ts | 57 + .../score/exam-details-mistakes-percent.ts | 45 + .../score/exam-details-score-order-tower.ts | 31 + .../score/exam-details-score-percent.ts | 44 + .../student/score/exam-details-table.ts | 65 + .../student/score/exam-duration-tower.ts | 22 + .../student/score/exam-history-score-line.ts | 37 + .../score/exam-mistakes-type-percent.ts | 32 + .../student/score/exam-score-pass-percent.ts | 59 + .../service/student/score/exam-score-tower.ts | 56 + .../view-resource/res-details-table.ts | 19 + .../student/view-resource/res-quantity.ts | 12 + .../service/student/view-resource/res-size.ts | 12 + .../course/detail-analysis-live-list.ts | 14 + .../service/teacher/course/detail-analysis.ts | 41 + .../teacher/course/setting-course-hour.ts | 26 + .../teacher/course/studnet-study-analyse.ts | 64 + .../teacher/course/study-course-hour.ts | 38 + ...exam-details-join-student-count-percent.ts | 47 + .../exam-details-mistakes-percent.ts | 51 + ...am-details-question-score-count-percent.ts | 41 + .../exam-details-score-ispass-percent.ts | 65 + .../exam-details-score-order-tower.ts | 48 + .../examination/exam-duration-tower.ts | 23 + .../examination/exam-score-pass-percent.ts | 65 + .../examination/exam-student-times-tower.ts | 22 + .../exercise/exercise-duration-percent.ts | 69 + .../exercise-question-is-pass-percent.ts | 176 + .../exercise/exercise-times-percent.ts | 32 + .../statistic/service/teacher/index/index.ts | 174 + .../teacher/live-broadcast/all-broadcast.ts | 35 + .../live-broadcast/detail-broadcast.ts | 16 + .../live-broadcast/detail-score-tower.ts | 40 + .../live-broadcast/detail-stu-count-line.ts | 36 + .../teacher/resources/res-details-table.ts | 29 + .../teacher/resources/res-file-data.ts | 10 + .../service/teacher/resources/res-perview.ts | 15 + .../service/teacher/resources/res-quantity.ts | 12 + .../service/teacher/resources/res-size.ts | 12 + .../test-question/mistakes-order-tower.ts | 82 + .../question-difficulty-by-type-percent.ts | 28 + ...ns-count-by-type-and-difficulty-percent.ts | 29 + .../questions-count-by-type-percent.ts | 31 + .../trainee/basic-system-service-period.ts | 17 + .../teacher/trainee/basic-system-use.ts | 15 + .../teacher/trainee/basic-trainee-age.ts | 11 + .../teacher/trainee/basic-trainee-number.ts | 11 + .../teacher/trainee/basic-trainee-org.ts | 16 + .../exam-details-student-score-table.ts | 91 + .../teacher/trainee/exam-duration-tower.ts | 23 + .../trainee/exam-mistakes-type-percent.ts | 34 + .../trainee/exam-score-pass-percent.ts | 67 + .../trainee/exam-student-score-tower.ts | 56 + .../exercise-details-questions-get-paper.ts | 164 + .../teacher/trainee/exercise-details-table.ts | 199 + .../trainee/exercise-qus-type-percent.ts | 59 + .../exercise-student-qus-is-pass-percent.ts | 175 + .../trainee/exercise-student-times-percent.ts | 81 + .../service/teacher/trainee/study-count.ts | 38 + serve/src/statistic/statistic.controller.ts | 49 + serve/src/statistic/statistic.module.ts | 10 + .../system/controller/feature.controller.ts | 17 + serve/src/system/controller/org.controller.ts | 54 + .../src/system/controller/role.controller.ts | 42 + .../system/controller/system.controller.ts | 22 + .../src/system/controller/user.controller.ts | 104 + serve/src/system/dto/create-org.dto.ts | 16 + serve/src/system/dto/login-user.dto.ts | 13 + serve/src/system/dto/role.dto.ts | 21 + serve/src/system/dto/update-org.dto.ts | 16 + serve/src/system/dto/update-password.dto.ts | 17 + serve/src/system/dto/user.dto.ts | 52 + serve/src/system/entities/feature.entity.ts | 14 + serve/src/system/entities/org.entity.ts | 14 + serve/src/system/entities/role.entity.ts | 14 + .../system/entities/teacher-student.entity.ts | 11 + serve/src/system/entities/user.entity.ts | 47 + serve/src/system/jwt/jwt-auth.guard.ts | 20 + serve/src/system/jwt/jwt.strategy.ts | 23 + serve/src/system/service/feature.service.ts | 19 + serve/src/system/service/org.service.ts | 50 + serve/src/system/service/role.service.ts | 81 + serve/src/system/service/system.service.ts | 62 + serve/src/system/service/user.service.ts | 238 + serve/src/system/system.module.ts | 56 + .../device/device-classify.controller.ts | 67 + .../device-number-position.controller.ts | 65 + .../device-part-operation.controller.ts | 67 + .../device/device-part.controller.ts | 70 + .../controller/device/device.controller.ts | 109 + .../subject-classify.controller copy.ts | 69 + .../subject/subject-classify.controller.ts | 69 + .../controller/subject/subject.controller.ts | 94 + .../subject/train-type.controller.ts | 67 + .../train/train-module.controller.ts | 706 + .../entities/device/device-classify.entity.ts | 17 + .../train/entities/device/device-ip.entity.ts | 31 + .../device/device-number-position.entity.ts | 19 + .../device-part-has-operation.entity.ts | 15 + .../device/device-part-operation.entity.ts | 15 + .../entities/device/device-parts.entity.ts | 26 + .../train/entities/device/device.entity.ts | 21 + .../subject/subject-classify.entity.ts | 20 + .../subject/subject-has-device.entity.ts | 13 + .../entities/subject/subject-step.entity.ts | 26 + .../train/entities/subject/subject.entity.ts | 26 + .../entities/subject/train-type.entity.ts | 14 + .../train/train-operation-log.entity.ts | 30 + .../entities/train/train-score-log.entity.ts | 16 + .../entities/train/train-settings.entity.ts | 26 + .../src/train/entities/train/train.entity.ts | 37 + serve/src/train/modules/device.modules.ts | 43 + serve/src/train/modules/subject.modules.ts | 21 + .../src/train/modules/train-module.modules.ts | 12 + .../service/device/device-classify.service.ts | 86 + .../device/device-number-position.service.ts | 109 + .../device/device-part-operation.service.ts | 115 + .../service/device/device-parts.service.ts | 192 + .../train/service/device/device.service.ts | 349 + .../subject/subject-classify.service.ts | 94 + .../service/subject/subject.service copy.ts | 319 + .../train/service/subject/subject.service.ts | 348 + .../service/subject/train-type.service.ts | 95 + .../src/train/service/train/train.service.ts | 489 + serve/src/train/train.module.ts | 28 + serve/test/app.e2e-spec.ts | 24 + serve/test/jest-e2e.json | 9 + serve/tf/css/theme-colors-c2200869.css | 1371 ++ serve/tf/css/theme-colors-c2200869.css.gz | Bin 0 -> 15131 bytes serve/tf/e5d0f237850f4ec00dfa.worker.js | 10 + serve/tf/e5d0f237850f4ec00dfa.worker.js.gz | Bin 0 -> 255515 bytes serve/tf/favicon.ico | Bin 0 -> 2913 bytes serve/tf/font/demo.css | 539 + serve/tf/font/demo_index.html | 2900 +++ serve/tf/font/iconfont.css | 485 + serve/tf/font/iconfont.js | 1 + serve/tf/font/iconfont.js.gz | Bin 0 -> 48256 bytes serve/tf/font/iconfont.json | 835 + serve/tf/font/iconfont.ttf | Bin 0 -> 31808 bytes serve/tf/index.html | 2 + serve/tf/static/css/app.79cf2c9e.css | 1 + serve/tf/static/css/app.79cf2c9e.css.gz | Bin 0 -> 65894 bytes .../tf/static/css/chunk-19400648.7ce87d8b.css | 1 + .../tf/static/css/chunk-3b4e252e.2db9e46c.css | 1 + .../tf/static/css/chunk-488247af.7ce87d8b.css | 1 + .../tf/static/css/chunk-580ca977.06a65142.css | 1 + .../tf/static/css/chunk-6a5b4dbe.f777d6be.css | 24 + .../static/css/chunk-6a5b4dbe.f777d6be.css.gz | Bin 0 -> 41158 bytes .../tf/static/css/chunk-75e685f5.7ce87d8b.css | 1 + .../tf/static/css/chunk-7a26dadf.ae432fdf.css | 1 + .../tf/static/css/chunk-98971e2c.a23f5c8d.css | 1 + .../static/css/chunk-98971e2c.a23f5c8d.css.gz | Bin 0 -> 1783 bytes .../tf/static/css/chunk-vendors.dbcdfda1.css | 11 + .../static/css/chunk-vendors.dbcdfda1.css.gz | Bin 0 -> 7941 bytes .../fonts/KaTeX_AMS-Regular.10824af7.woff | Bin 0 -> 33516 bytes .../fonts/KaTeX_AMS-Regular.56573229.ttf | Bin 0 -> 63632 bytes .../fonts/KaTeX_AMS-Regular.66c67820.woff2 | Bin 0 -> 28076 bytes .../fonts/KaTeX_Caligraphic-Bold.497bf407.ttf | Bin 0 -> 12368 bytes .../KaTeX_Caligraphic-Bold.a9e9b095.woff2 | Bin 0 -> 6912 bytes .../KaTeX_Caligraphic-Bold.de2ba279.woff | Bin 0 -> 7716 bytes .../KaTeX_Caligraphic-Regular.08d95d99.woff2 | Bin 0 -> 6908 bytes .../KaTeX_Caligraphic-Regular.a25140fb.woff | Bin 0 -> 7656 bytes .../KaTeX_Caligraphic-Regular.e6fb499f.ttf | Bin 0 -> 12344 bytes .../fonts/KaTeX_Fraktur-Bold.40934fc0.woff | Bin 0 -> 13296 bytes .../fonts/KaTeX_Fraktur-Bold.796f3797.woff2 | Bin 0 -> 11348 bytes .../fonts/KaTeX_Fraktur-Bold.b9d7c449.ttf | Bin 0 -> 19584 bytes .../fonts/KaTeX_Fraktur-Regular.97a699d8.ttf | Bin 0 -> 19572 bytes .../fonts/KaTeX_Fraktur-Regular.e435cda5.woff | Bin 0 -> 13208 bytes .../KaTeX_Fraktur-Regular.f9e6a99f.woff2 | Bin 0 -> 11316 bytes .../fonts/KaTeX_Main-Bold.4cdba646.woff | Bin 0 -> 29912 bytes .../static/fonts/KaTeX_Main-Bold.8e431f7e.ttf | Bin 0 -> 51336 bytes .../fonts/KaTeX_Main-Bold.a9382e25.woff2 | Bin 0 -> 25324 bytes .../fonts/KaTeX_Main-BoldItalic.52fb39b0.ttf | Bin 0 -> 32968 bytes .../fonts/KaTeX_Main-BoldItalic.5f875f98.woff | Bin 0 -> 19412 bytes .../KaTeX_Main-BoldItalic.d8737343.woff2 | Bin 0 -> 16780 bytes .../fonts/KaTeX_Main-Italic.39349e0a.ttf | Bin 0 -> 33580 bytes .../fonts/KaTeX_Main-Italic.65297062.woff2 | Bin 0 -> 16988 bytes .../fonts/KaTeX_Main-Italic.8ffd28f6.woff | Bin 0 -> 19676 bytes .../fonts/KaTeX_Main-Regular.818582da.ttf | Bin 0 -> 53580 bytes .../fonts/KaTeX_Main-Regular.f1cdb692.woff | Bin 0 -> 30772 bytes .../fonts/KaTeX_Main-Regular.f8a7f19f.woff2 | Bin 0 -> 26272 bytes .../KaTeX_Math-BoldItalic.1320454d.woff2 | Bin 0 -> 16400 bytes .../fonts/KaTeX_Math-BoldItalic.48155e43.woff | Bin 0 -> 18668 bytes .../fonts/KaTeX_Math-BoldItalic.6589c4f1.ttf | Bin 0 -> 31196 bytes .../fonts/KaTeX_Math-Italic.d8b7a801.woff2 | Bin 0 -> 16440 bytes .../fonts/KaTeX_Math-Italic.ed7aea12.woff | Bin 0 -> 18748 bytes .../fonts/KaTeX_Math-Italic.fe5ed587.ttf | Bin 0 -> 31308 bytes .../fonts/KaTeX_SansSerif-Bold.0e897d27.woff | Bin 0 -> 14408 bytes .../fonts/KaTeX_SansSerif-Bold.ad546b47.woff2 | Bin 0 -> 12216 bytes .../fonts/KaTeX_SansSerif-Bold.f2ac7312.ttf | Bin 0 -> 24504 bytes .../KaTeX_SansSerif-Italic.e934cbc8.woff2 | Bin 0 -> 12028 bytes .../KaTeX_SansSerif-Italic.ef725de5.woff | Bin 0 -> 14112 bytes .../fonts/KaTeX_SansSerif-Italic.f60b4a34.ttf | Bin 0 -> 22364 bytes .../KaTeX_SansSerif-Regular.1ac3ed6e.woff2 | Bin 0 -> 10344 bytes .../KaTeX_SansSerif-Regular.3243452e.ttf | Bin 0 -> 19436 bytes .../KaTeX_SansSerif-Regular.5f8637ee.woff | Bin 0 -> 12316 bytes .../fonts/KaTeX_Script-Regular.1b3161eb.woff2 | Bin 0 -> 9644 bytes .../fonts/KaTeX_Script-Regular.a189c37d.ttf | Bin 0 -> 16648 bytes .../fonts/KaTeX_Script-Regular.a82fa2a7.woff | Bin 0 -> 10588 bytes .../fonts/KaTeX_Size1-Regular.0d8d9204.ttf | Bin 0 -> 12228 bytes .../fonts/KaTeX_Size1-Regular.4788ba5b.woff | Bin 0 -> 6496 bytes .../fonts/KaTeX_Size1-Regular.82ef26dc.woff2 | Bin 0 -> 5468 bytes .../fonts/KaTeX_Size2-Regular.1fdda0e5.ttf | Bin 0 -> 11508 bytes .../fonts/KaTeX_Size2-Regular.95a1da91.woff2 | Bin 0 -> 5208 bytes .../fonts/KaTeX_Size2-Regular.b0628bfd.woff | Bin 0 -> 6188 bytes .../fonts/KaTeX_Size3-Regular.4de844d4.woff | Bin 0 -> 4420 bytes .../fonts/KaTeX_Size3-Regular.963af864.ttf | Bin 0 -> 7588 bytes .../fonts/KaTeX_Size4-Regular.27a23ee6.ttf | Bin 0 -> 10364 bytes .../fonts/KaTeX_Size4-Regular.3045a61f.woff | Bin 0 -> 5980 bytes .../fonts/KaTeX_Size4-Regular.61522cd3.woff2 | Bin 0 -> 4928 bytes .../KaTeX_Typewriter-Regular.0e046058.woff | Bin 0 -> 16028 bytes .../KaTeX_Typewriter-Regular.6bf42875.ttf | Bin 0 -> 27556 bytes .../KaTeX_Typewriter-Regular.b8b8393d.woff2 | Bin 0 -> 13568 bytes .../font_1456030_mvh913k905.4697711c.ttf | Bin 0 -> 30456 bytes .../font_1456030_mvh913k905.59b43a90.woff | Bin 0 -> 16080 bytes .../font_1456030_mvh913k905.854b82bf.woff2 | Bin 0 -> 13212 bytes serve/tf/static/fonts/icomoon.068c5807.eot | Bin 0 -> 10808 bytes serve/tf/static/fonts/icomoon.9613532e.woff | Bin 0 -> 10720 bytes serve/tf/static/fonts/icomoon.a5ecb97c.ttf | Bin 0 -> 10644 bytes serve/tf/static/fonts/iconfont.52434ae6.woff2 | Bin 0 -> 6300 bytes serve/tf/static/fonts/iconfont.96dd6179.woff | Bin 0 -> 7572 bytes serve/tf/static/fonts/iconfont.f348f3f2.ttf | Bin 0 -> 12392 bytes serve/tf/static/img/icomoon.60880589.svg | 103 + serve/tf/static/img/logo.9652507e.png | Bin 0 -> 26705 bytes serve/tf/static/js/app.dee09f7c.js | 1 + serve/tf/static/js/app.dee09f7c.js.gz | Bin 0 -> 24855 bytes serve/tf/static/js/chunk-19400648.05b336fb.js | 1 + serve/tf/static/js/chunk-2d0f0f76.678638ec.js | 1 + serve/tf/static/js/chunk-3b4e252e.63a4c246.js | 1 + serve/tf/static/js/chunk-488247af.833d2082.js | 1 + serve/tf/static/js/chunk-580ca977.fadf558e.js | 1 + .../static/js/chunk-580ca977.fadf558e.js.gz | Bin 0 -> 6018 bytes serve/tf/static/js/chunk-6a5b4dbe.21157920.js | 116 + .../static/js/chunk-6a5b4dbe.21157920.js.gz | Bin 0 -> 1024031 bytes serve/tf/static/js/chunk-75e685f5.72a01e0a.js | 1 + serve/tf/static/js/chunk-7a26dadf.e80e6a6f.js | 1 + .../static/js/chunk-7a26dadf.e80e6a6f.js.gz | Bin 0 -> 5816 bytes serve/tf/static/js/chunk-83a57bd6.1aba2685.js | 19 + .../static/js/chunk-83a57bd6.1aba2685.js.gz | Bin 0 -> 42436 bytes serve/tf/static/js/chunk-98971e2c.256bbe91.js | 1 + .../static/js/chunk-98971e2c.256bbe91.js.gz | Bin 0 -> 4631 bytes serve/tf/static/js/chunk-vendors.ac44f027.js | 90 + .../tf/static/js/chunk-vendors.ac44f027.js.gz | Bin 0 -> 903078 bytes serve/tools/data.js | 12 + serve/tools/trans-class.js | 58 + serve/tsconfig.build.json | 4 + serve/tsconfig.json | 16 + serve/types/index.d.ts | 8 + serve/types/prototype.d.ts | 8 + 1190 files changed, 138344 insertions(+), 455 deletions(-) create mode 100644 editor/.browserslistrc create mode 100644 editor/.env create mode 100644 editor/.eslintrc.js create mode 100644 editor/.gitignore create mode 100644 editor/.prettierrc.js create mode 100644 editor/README.md create mode 100644 editor/babel.config.js create mode 100644 editor/package.json create mode 100644 editor/public/favicon.ico create mode 100644 editor/public/index.html create mode 100644 editor/src/App.vue create mode 100644 editor/src/apis/manual.ts create mode 100644 editor/src/assets/iconfont/iconfont.css create mode 100644 editor/src/assets/iconfont/iconfont.js create mode 100644 editor/src/assets/iconfont/iconfont.json create mode 100644 editor/src/assets/iconfont/iconfont.ttf create mode 100644 editor/src/assets/iconfont/iconfont.woff create mode 100644 editor/src/assets/iconfont/iconfont.woff2 create mode 100644 editor/src/assets/styles/custom-theme.less create mode 100644 editor/src/assets/styles/index.less create mode 100644 editor/src/components/editor/config.ts create mode 100644 editor/src/components/editor/loading.vue create mode 100644 editor/src/components/editor/mention-popover.vue create mode 100644 editor/src/components/editor/outline.vue create mode 100644 editor/src/main.ts create mode 100644 editor/src/plugins/audio/component/index.css create mode 100644 editor/src/plugins/audio/component/index.ts create mode 100644 editor/src/plugins/audio/index.ts create mode 100644 editor/src/plugins/audio/locales/en-US.ts create mode 100644 editor/src/plugins/audio/locales/index.ts create mode 100644 editor/src/plugins/audio/locales/zh-CN.ts create mode 100644 editor/src/plugins/audio/uploader.ts create mode 100644 editor/src/plugins/draw/component/constant.ts create mode 100644 editor/src/plugins/draw/component/diagram-loader.ts create mode 100644 editor/src/plugins/draw/component/draw-edit.vue create mode 100644 editor/src/plugins/draw/component/draw-view.vue create mode 100644 editor/src/plugins/draw/component/index.css create mode 100644 editor/src/plugins/draw/component/index.ts create mode 100644 editor/src/plugins/draw/component/utils.ts create mode 100644 editor/src/plugins/draw/index.ts create mode 100644 editor/src/plugins/draw/types.ts create mode 100644 editor/src/plugins/image/component/image/index.css create mode 100644 editor/src/plugins/image/component/image/index.ts create mode 100644 editor/src/plugins/image/component/index.ts create mode 100644 editor/src/plugins/image/component/pswp/index.css create mode 100644 editor/src/plugins/image/component/pswp/index.ts create mode 100644 editor/src/plugins/image/component/pswp/zoom.ts create mode 100644 editor/src/plugins/image/index.ts create mode 100644 editor/src/plugins/image/locales/en-US.ts create mode 100644 editor/src/plugins/image/locales/index.ts create mode 100644 editor/src/plugins/image/locales/zh-CN.ts create mode 100644 editor/src/plugins/image/types.ts create mode 100644 editor/src/plugins/image/uploader.ts create mode 100644 editor/src/plugins/lightblock/component/index.tsx create mode 100644 editor/src/plugins/lightblock/component/lightblock-theme.vue create mode 100644 editor/src/plugins/lightblock/component/markdown.ts create mode 100644 editor/src/plugins/lightblock/component/style.css create mode 100644 editor/src/plugins/lightblock/component/types.ts create mode 100644 editor/src/plugins/lightblock/index.ts create mode 100644 editor/src/plugins/math/component/constant.ts create mode 100644 editor/src/plugins/math/component/index.ts create mode 100644 editor/src/plugins/math/component/math-formula.vue create mode 100644 editor/src/plugins/math/component/type.ts create mode 100644 editor/src/plugins/math/index.ts create mode 100644 editor/src/plugins/tag/component/index.tsx create mode 100644 editor/src/plugins/tag/component/style.css create mode 100644 editor/src/plugins/tag/component/tag.tsx create mode 100644 editor/src/plugins/tag/component/type.ts create mode 100644 editor/src/plugins/tag/index.ts create mode 100644 editor/src/plugins/tag/local/index.ts create mode 100644 editor/src/plugins/test/component/index.ts create mode 100644 editor/src/plugins/test/component/test-com.vue create mode 100644 editor/src/plugins/test/index.ts create mode 100644 editor/src/publice-path.ts create mode 100644 editor/src/router/index.ts create mode 100644 editor/src/shims-vue.d.ts create mode 100644 editor/src/store/index.ts create mode 100644 editor/src/utils/index.ts create mode 100644 editor/src/utils/request.ts create mode 100644 editor/src/views/editor/edit.vue create mode 100644 editor/src/views/editor/index.vue create mode 100644 editor/src/views/editor/view.vue create mode 100644 editor/src/views/manual/components/manual-classify.vue create mode 100644 editor/src/views/manual/components/manual-contents.vue create mode 100644 editor/src/views/manual/components/manual-form.vue create mode 100644 editor/src/views/manual/components/manual-list.vue create mode 100644 editor/src/views/manual/edit.vue create mode 100644 editor/src/views/manual/index.vue create mode 100644 editor/src/views/manual/view.vue create mode 100644 editor/tsconfig.json create mode 100644 editor/vue.config.js create mode 100644 editor/yarn.lock create mode 100644 front/.env.development create mode 100644 front/.env.production create mode 100644 front/.eslintignore create mode 100644 front/.eslintrc.cjs create mode 100644 front/.gitignore create mode 100644 front/.stylelintignore create mode 100644 front/.stylelintrc.js create mode 100644 front/.vscode/settings.json create mode 100644 front/README.md create mode 100644 front/bin/publish.sh create mode 100644 front/index.html create mode 100644 front/jsconfig.json create mode 100644 front/package-lock.json create mode 100644 front/package.json create mode 100644 front/public/default.svg create mode 100644 front/public/font/iconfont.css create mode 100644 front/public/font/iconfont.ttf create mode 100644 front/public/imgs/entry/1.png create mode 100644 front/public/imgs/entry/2.png create mode 100644 front/public/imgs/entry/3.png create mode 100644 front/public/imgs/entry/4.png create mode 100644 front/public/imgs/entry/5.png create mode 100644 front/public/imgs/entry/assess.png create mode 100644 front/public/imgs/entry/course.png create mode 100644 front/public/imgs/entry/evaluate.png create mode 100644 front/public/imgs/entry/resource.png create mode 100644 front/public/imgs/entry/textbook.png create mode 100644 front/public/imgs/login-bg.jpg create mode 100644 front/public/imgs/login-bottom.png create mode 100644 front/public/imgs/login-draw.png create mode 100644 front/public/imgs/login-top.png create mode 100644 front/public/imgs/preview-full-volume.png create mode 100644 front/public/imgs/preview-question-by-question.png create mode 100644 front/public/js/axios.min.js create mode 100644 front/public/js/element-ui.min.js create mode 100644 front/public/js/socket.io.min.js create mode 100644 front/public/js/vue-router.min.js create mode 100644 front/public/js/vue.min.js create mode 100644 front/public/js/vuex.min.js create mode 100644 front/public/logo.svg create mode 100644 front/public/svg/404.svg create mode 100644 front/public/svg/nomore.svg create mode 100644 front/public/svg/none.svg create mode 100644 front/public/svg/noseach.svg create mode 100644 front/public/svg/timeout.svg create mode 100644 front/src/api/assessment-evaluation/exam.js create mode 100644 front/src/api/assessment-evaluation/humanEval.js create mode 100644 front/src/api/assessment-evaluation/mistake.js create mode 100644 front/src/api/assessment-evaluation/onlineTest.js create mode 100644 front/src/api/assessment-evaluation/paper.js create mode 100644 front/src/api/assessment-evaluation/questions.js create mode 100644 front/src/api/evaluation/index copy.js create mode 100644 front/src/api/evaluation/index.js create mode 100644 front/src/api/online-course/course-notes.js create mode 100644 front/src/api/online-course/my-course.js create mode 100644 front/src/api/online-course/online-FAQ.js create mode 100644 front/src/api/online-course/online-course.js create mode 100644 front/src/api/online-teaching/live-manage.js create mode 100644 front/src/api/online-teaching/live-student.js create mode 100644 front/src/api/statistic/index.js create mode 100644 front/src/api/system/index.js create mode 100644 front/src/api/system/org.js create mode 100644 front/src/api/system/resource.js create mode 100644 front/src/api/system/role.js create mode 100644 front/src/api/system/user.js create mode 100644 front/src/api/training/device.js create mode 100644 front/src/api/training/mqtt.js create mode 100644 front/src/api/training/operation.js create mode 100644 front/src/api/training/post.js create mode 100644 front/src/api/training/subject.js create mode 100644 front/src/api/training/train-evealuation.js create mode 100644 front/src/api/training/train.js create mode 100644 front/src/components/business/Preview.vue create mode 100644 front/src/components/layout/ActionBar.vue create mode 100644 front/src/components/layout/DialogLayout.vue create mode 100644 front/src/components/layout/FormLayout.vue create mode 100644 front/src/components/layout/SearchTreeMenu.vue create mode 100644 front/src/components/layout/TableLayout.vue create mode 100644 front/src/components/layout/index.js create mode 100644 front/src/components/widget/Pagination.vue create mode 100644 front/src/components/widget/QueryInput.vue create mode 100644 front/src/components/widget/SearchInput.vue create mode 100644 front/src/components/widget/index.js create mode 100644 front/src/main.js create mode 100644 front/src/router/feature.js create mode 100644 front/src/router/index.js create mode 100644 front/src/router/routes.js create mode 100644 front/src/store/index.js create mode 100644 front/src/styles/element-ui.scss create mode 100644 front/src/styles/index.scss create mode 100644 front/src/styles/variables.scss create mode 100644 front/src/utils/capture.js create mode 100644 front/src/utils/data-report.js create mode 100644 front/src/utils/fetch.js create mode 100644 front/src/utils/iframe-message.js create mode 100644 front/src/utils/index.js create mode 100644 front/src/utils/prototype.js create mode 100644 front/src/utils/qiankun-init.js create mode 100644 front/src/utils/simhei-normal.js create mode 100644 front/src/utils/storage.js create mode 100644 front/src/views/404/index.vue create mode 100644 front/src/views/assessment-evaluation/exam-arrangement/add-modify-exam/components/ExamClassifySelector.vue create mode 100644 front/src/views/assessment-evaluation/exam-arrangement/add-modify-exam/components/ExamTipsForm.vue create mode 100644 front/src/views/assessment-evaluation/exam-arrangement/add-modify-exam/components/ModePriviewDialog.vue create mode 100644 front/src/views/assessment-evaluation/exam-arrangement/add-modify-exam/components/StudentsSelector.vue create mode 100644 front/src/views/assessment-evaluation/exam-arrangement/add-modify-exam/components/TeacherSelector.vue create mode 100644 front/src/views/assessment-evaluation/exam-arrangement/add-modify-exam/index.vue create mode 100644 front/src/views/assessment-evaluation/exam-arrangement/index.vue create mode 100644 front/src/views/assessment-evaluation/examination-paper-manage/add-modify-paper/components/FixedQuestionSelectTable.vue create mode 100644 front/src/views/assessment-evaluation/examination-paper-manage/add-modify-paper/components/PaperClassifySelector.vue create mode 100644 front/src/views/assessment-evaluation/examination-paper-manage/add-modify-paper/components/PaperSelectQuestionTable.vue create mode 100644 front/src/views/assessment-evaluation/examination-paper-manage/add-modify-paper/components/QuestionListPreview.vue create mode 100644 front/src/views/assessment-evaluation/examination-paper-manage/add-modify-paper/components/RandQuestionSelecter.vue create mode 100644 front/src/views/assessment-evaluation/examination-paper-manage/add-modify-paper/index.vue create mode 100644 front/src/views/assessment-evaluation/examination-paper-manage/components/ExamPaperSelector.vue create mode 100644 front/src/views/assessment-evaluation/examination-paper-manage/components/PrintPaper.vue create mode 100644 front/src/views/assessment-evaluation/examination-paper-manage/components/SelectExamPaper.vue create mode 100644 front/src/views/assessment-evaluation/examination-paper-manage/components/SelectJudgePaper.vue create mode 100644 front/src/views/assessment-evaluation/examination-paper-manage/index.vue create mode 100644 front/src/views/assessment-evaluation/human-evaluation/eval-details/index.vue create mode 100644 front/src/views/assessment-evaluation/human-evaluation/index.vue create mode 100644 front/src/views/assessment-evaluation/online-test/begin-online-exam/components/AQCard.vue create mode 100644 front/src/views/assessment-evaluation/online-test/begin-online-exam/components/QuestionsList.vue create mode 100644 front/src/views/assessment-evaluation/online-test/begin-online-exam/components/StudentTips.vue create mode 100644 front/src/views/assessment-evaluation/online-test/begin-online-exam/components/TeacherDatas.vue create mode 100644 front/src/views/assessment-evaluation/online-test/begin-online-exam/index.vue create mode 100644 front/src/views/assessment-evaluation/online-test/index.vue create mode 100644 front/src/views/assessment-evaluation/question-bank-manage/add-modify-question/components/ImportQuestions.vue create mode 100644 front/src/views/assessment-evaluation/question-bank-manage/add-modify-question/components/KnowledgeSelector.vue create mode 100644 front/src/views/assessment-evaluation/question-bank-manage/add-modify-question/components/QuestionClassifySelector.vue create mode 100644 front/src/views/assessment-evaluation/question-bank-manage/add-modify-question/components/QuestionDifficultyLevelSelector.vue create mode 100644 front/src/views/assessment-evaluation/question-bank-manage/add-modify-question/components/QuestionTypeTabBar.vue create mode 100644 front/src/views/assessment-evaluation/question-bank-manage/add-modify-question/components/TagEditer.vue create mode 100644 front/src/views/assessment-evaluation/question-bank-manage/add-modify-question/index.vue create mode 100644 front/src/views/assessment-evaluation/question-bank-manage/components/QuestionItem.vue create mode 100644 front/src/views/assessment-evaluation/question-bank-manage/index.vue create mode 100644 front/src/views/assessment-evaluation/simulation-test/add-modify-sim-test/index.vue create mode 100644 front/src/views/assessment-evaluation/simulation-test/index.vue create mode 100644 front/src/views/assessment-evaluation/utils/gy.srs.sdk.js create mode 100644 front/src/views/assessment-evaluation/utils/gyStyle.scss create mode 100644 front/src/views/assessment-evaluation/utils/questionInfoMapJsonTrans.js create mode 100644 front/src/views/assessment-evaluation/wrong-topic-consolidate/index.vue create mode 100644 front/src/views/assessment-evaluation/wrong-topic-consolidate/mistakes-again/index.vue create mode 100644 front/src/views/electronic-textbook/index.vue create mode 100644 front/src/views/entry/components/Vcard.vue create mode 100644 front/src/views/entry/index.vue create mode 100644 front/src/views/evaluation/evaluation-setting/components/selfStep.vue create mode 100644 front/src/views/evaluation/evaluation-setting/index copy.vue create mode 100644 front/src/views/evaluation/evaluation-setting/index.vue create mode 100644 front/src/views/evaluation/my-evaluation/index.vue create mode 100644 front/src/views/evaluation/student-evaluation/index copy.vue create mode 100644 front/src/views/evaluation/student-evaluation/index.vue create mode 100644 front/src/views/home/Breadcrumb.vue create mode 100644 front/src/views/home/HeadLayout.vue create mode 100644 front/src/views/home/MenuLayout.vue create mode 100644 front/src/views/home/global-components/PreviewDialog.vue create mode 100644 front/src/views/home/global-components/UpdatePasswordDialog.vue create mode 100644 front/src/views/home/global-components/UploadDialog.vue create mode 100644 front/src/views/home/global-components/index.vue create mode 100644 front/src/views/home/index.vue create mode 100644 front/src/views/login/index.vue create mode 100644 front/src/views/online-course/class-notes/index.vue create mode 100644 front/src/views/online-course/components/FAQ-info copy.vue create mode 100644 front/src/views/online-course/components/FAQ-info.vue create mode 100644 front/src/views/online-course/components/add-course/add-course.vue create mode 100644 front/src/views/online-course/components/add-course/course-courseware.vue create mode 100644 front/src/views/online-course/components/add-course/course-evaluate.vue create mode 100644 front/src/views/online-course/components/add-course/course-info.vue create mode 100644 front/src/views/online-course/components/add-course/course-section.vue create mode 100644 front/src/views/online-course/components/add-course/course-statistics.vue create mode 100644 front/src/views/online-course/components/add-course/course-study.vue create mode 100644 front/src/views/online-course/components/button-selft.vue create mode 100644 front/src/views/online-course/components/course-disscuss.vue create mode 100644 front/src/views/online-course/components/my-course-info.vue create mode 100644 front/src/views/online-course/components/rich-text-editor.vue create mode 100644 front/src/views/online-course/course-exercises/course-exercises.vue create mode 100644 front/src/views/online-course/course-manage/add-coures/index.vue create mode 100644 front/src/views/online-course/course-manage/course-comment/index.vue create mode 100644 front/src/views/online-course/course-manage/course-evaluate/index.vue create mode 100644 front/src/views/online-course/course-manage/index.vue create mode 100644 front/src/views/online-course/course-study/index.vue create mode 100644 front/src/views/online-course/my-course/index.vue create mode 100644 front/src/views/online-course/online-FAQ/index.vue create mode 100644 front/src/views/online-course/utils.js create mode 100644 front/src/views/online-teaching/add-live/index.vue create mode 100644 front/src/views/online-teaching/components/chat-list.vue create mode 100644 front/src/views/online-teaching/components/live-layout.vue create mode 100644 front/src/views/online-teaching/live-answering-question/index.vue create mode 100644 front/src/views/online-teaching/live-in-class/index.vue create mode 100644 front/src/views/online-teaching/live-lectures/index.vue create mode 100644 front/src/views/online-teaching/live-play/index.vue create mode 100644 front/src/views/online-teaching/live-play/mixins/live.js create mode 100644 front/src/views/online-teaching/live-playback/index.vue create mode 100644 front/src/views/online-teaching/live-watch/index.vue create mode 100644 front/src/views/register/index.vue create mode 100644 front/src/views/statistic/charts/admin/charts/ResQuantity.vue create mode 100644 front/src/views/statistic/charts/admin/charts/ResSize.vue create mode 100644 front/src/views/statistic/charts/admin/charts/ServicePeriod.vue create mode 100644 front/src/views/statistic/charts/admin/charts/SystemOrg.vue create mode 100644 front/src/views/statistic/charts/admin/charts/SystemUse.vue create mode 100644 front/src/views/statistic/charts/admin/charts/SystemUser.vue create mode 100644 front/src/views/statistic/charts/admin/charts/index.vue create mode 100644 front/src/views/statistic/charts/admin/index.js create mode 100644 front/src/views/statistic/charts/student/course/CourseContent.vue create mode 100644 front/src/views/statistic/charts/student/course/CourseList.vue create mode 100644 front/src/views/statistic/charts/student/course/LearningState.vue create mode 100644 front/src/views/statistic/charts/student/course/index.vue create mode 100644 front/src/views/statistic/charts/student/index.js create mode 100644 front/src/views/statistic/charts/student/mistakes/MistakesDetailsTable.vue create mode 100644 front/src/views/statistic/charts/student/mistakes/MistakesIsPassPercent.vue create mode 100644 front/src/views/statistic/charts/student/mistakes/MistakesTypePercent.vue create mode 100644 front/src/views/statistic/charts/student/mistakes/index.vue create mode 100644 front/src/views/statistic/charts/student/note/StudentNoteCount.vue create mode 100644 front/src/views/statistic/charts/student/note/StudentNoteInfo.vue create mode 100644 front/src/views/statistic/charts/student/note/StudentNoteUpdateRankings.vue create mode 100644 front/src/views/statistic/charts/student/note/StudentNoteViewRanking.vue create mode 100644 front/src/views/statistic/charts/student/note/components/NoteTable.vue create mode 100644 front/src/views/statistic/charts/student/note/index.vue create mode 100644 front/src/views/statistic/charts/student/paper/PaperDetailsTable.vue create mode 100644 front/src/views/statistic/charts/student/paper/PaperDifficultyByHistoryPercent.vue create mode 100644 front/src/views/statistic/charts/student/paper/PaperDifficultyPercent.vue create mode 100644 front/src/views/statistic/charts/student/paper/PaperIsFixedPercent.vue create mode 100644 front/src/views/statistic/charts/student/paper/PaperMistakesPercent.vue create mode 100644 front/src/views/statistic/charts/student/paper/PaperScorePassPercent.vue create mode 100644 front/src/views/statistic/charts/student/paper/index.vue create mode 100644 front/src/views/statistic/charts/student/score/ExamDetailsMistakesPercent.vue create mode 100644 front/src/views/statistic/charts/student/score/ExamDetailsScoreOrderTower.vue create mode 100644 front/src/views/statistic/charts/student/score/ExamDetailsScorePercent.vue create mode 100644 front/src/views/statistic/charts/student/score/ExamDetailsTable.vue create mode 100644 front/src/views/statistic/charts/student/score/ExamDurationTower.vue create mode 100644 front/src/views/statistic/charts/student/score/ExamHistroyScoreLine.vue create mode 100644 front/src/views/statistic/charts/student/score/ExamMistakesTypePercent.vue create mode 100644 front/src/views/statistic/charts/student/score/ExamScorePassPercent.vue create mode 100644 front/src/views/statistic/charts/student/score/ExamScoreTower.vue create mode 100644 front/src/views/statistic/charts/student/score/index.vue create mode 100644 front/src/views/statistic/charts/student/view-resource/ResDetailsTable.vue create mode 100644 front/src/views/statistic/charts/student/view-resource/ResQuantity.vue create mode 100644 front/src/views/statistic/charts/student/view-resource/ResSize.vue create mode 100644 front/src/views/statistic/charts/student/view-resource/index.vue create mode 100644 front/src/views/statistic/charts/teacher/course/DetailAnalysis.vue create mode 100644 front/src/views/statistic/charts/teacher/course/SettingCourseHour.vue create mode 100644 front/src/views/statistic/charts/teacher/course/StudnetStudyAnalyse.vue create mode 100644 front/src/views/statistic/charts/teacher/course/StudyCourseDuration.vue create mode 100644 front/src/views/statistic/charts/teacher/course/index.vue create mode 100644 front/src/views/statistic/charts/teacher/examination/ExamDetailsJoinStudentCountPercent.vue create mode 100644 front/src/views/statistic/charts/teacher/examination/ExamDetailsLayout.vue create mode 100644 front/src/views/statistic/charts/teacher/examination/ExamDetailsMistakesPercent.vue create mode 100644 front/src/views/statistic/charts/teacher/examination/ExamDetailsQuestionsCountPercent.vue create mode 100644 front/src/views/statistic/charts/teacher/examination/ExamDetailsQuestionsScorePercent.vue create mode 100644 front/src/views/statistic/charts/teacher/examination/ExamDetailsScoreOrderTower.vue create mode 100644 front/src/views/statistic/charts/teacher/examination/ExamDetailsScorePassPercent.vue create mode 100644 front/src/views/statistic/charts/teacher/examination/ExamDurationTower.vue create mode 100644 front/src/views/statistic/charts/teacher/examination/ExamStudentTimesTower.vue create mode 100644 front/src/views/statistic/charts/teacher/examination/StudentsScorePassPercent.vue create mode 100644 front/src/views/statistic/charts/teacher/examination/index.vue create mode 100644 front/src/views/statistic/charts/teacher/exercise/ExerciseDurationPercent.vue create mode 100644 front/src/views/statistic/charts/teacher/exercise/ExerciseQuestionIsPassPercent.vue create mode 100644 front/src/views/statistic/charts/teacher/exercise/ExercuseTimesPercent.vue create mode 100644 front/src/views/statistic/charts/teacher/exercise/index.vue create mode 100644 front/src/views/statistic/charts/teacher/index.js create mode 100644 front/src/views/statistic/charts/teacher/live-broadcast/LiveAllBroadcast.vue create mode 100644 front/src/views/statistic/charts/teacher/live-broadcast/LiveDetailBroadcast.vue create mode 100644 front/src/views/statistic/charts/teacher/live-broadcast/LiveDetailScoreTower.vue create mode 100644 front/src/views/statistic/charts/teacher/live-broadcast/LiveDetailStuCountLine.vue create mode 100644 front/src/views/statistic/charts/teacher/live-broadcast/index.vue create mode 100644 front/src/views/statistic/charts/teacher/resources/ResDetailsTable.vue create mode 100644 front/src/views/statistic/charts/teacher/resources/ResFileData.vue create mode 100644 front/src/views/statistic/charts/teacher/resources/ResPerview.vue create mode 100644 front/src/views/statistic/charts/teacher/resources/ResQuantity.vue create mode 100644 front/src/views/statistic/charts/teacher/resources/ResSize.vue create mode 100644 front/src/views/statistic/charts/teacher/resources/ResTypeList.vue create mode 100644 front/src/views/statistic/charts/teacher/resources/index.vue create mode 100644 front/src/views/statistic/charts/teacher/test-question/MistakeOrderTower.vue create mode 100644 front/src/views/statistic/charts/teacher/test-question/QuestionDetailsLayout.vue create mode 100644 front/src/views/statistic/charts/teacher/test-question/QuestionDifficultyByTypePercent.vue create mode 100644 front/src/views/statistic/charts/teacher/test-question/QuestionsCountByTypeAndDifficultyPercent.vue create mode 100644 front/src/views/statistic/charts/teacher/test-question/QuestionsCountByTypePercent.vue create mode 100644 front/src/views/statistic/charts/teacher/test-question/index.vue create mode 100644 front/src/views/statistic/charts/teacher/trainee/basic/TraineeAge.vue create mode 100644 front/src/views/statistic/charts/teacher/trainee/basic/TraineeNumber.vue create mode 100644 front/src/views/statistic/charts/teacher/trainee/basic/TraineeOrg.vue create mode 100644 front/src/views/statistic/charts/teacher/trainee/basic/index.vue create mode 100644 front/src/views/statistic/charts/teacher/trainee/exam/ExamDetailsStudentScoreTable.vue create mode 100644 front/src/views/statistic/charts/teacher/trainee/exam/ExamStudentScoreTower.vue create mode 100644 front/src/views/statistic/charts/teacher/trainee/exam/ExamStudentTimesTower.vue create mode 100644 front/src/views/statistic/charts/teacher/trainee/exam/StudentsMistakesTypePercent.vue create mode 100644 front/src/views/statistic/charts/teacher/trainee/exam/StudentsScorePassPercent.vue create mode 100644 front/src/views/statistic/charts/teacher/trainee/exam/index.vue create mode 100644 front/src/views/statistic/charts/teacher/trainee/exercise/ExerciseDetailsTable.vue create mode 100644 front/src/views/statistic/charts/teacher/trainee/exercise/ExerciseQuestionIsPassPercent.vue create mode 100644 front/src/views/statistic/charts/teacher/trainee/exercise/ExerciseQuestionTypePercent.vue create mode 100644 front/src/views/statistic/charts/teacher/trainee/exercise/ExercuseTimesPercent.vue create mode 100644 front/src/views/statistic/charts/teacher/trainee/exercise/index.vue create mode 100644 front/src/views/statistic/charts/teacher/trainee/index.vue create mode 100644 front/src/views/statistic/charts/teacher/trainee/study/CourseInfoTable.vue create mode 100644 front/src/views/statistic/charts/teacher/trainee/study/index.vue create mode 100644 front/src/views/statistic/charts/teacher/trainee/system-use/ServicePeriod.vue create mode 100644 front/src/views/statistic/charts/teacher/trainee/system-use/SystemUse.vue create mode 100644 front/src/views/statistic/charts/teacher/trainee/system-use/index.vue create mode 100644 front/src/views/statistic/components/CircleNumberChart.vue create mode 100644 front/src/views/statistic/components/ExamListChart.vue create mode 100644 front/src/views/statistic/components/ExercisePerviewPaper.vue create mode 100644 front/src/views/statistic/components/OrgUserChart.vue create mode 100644 front/src/views/statistic/components/QuestionTypeListChart.vue create mode 100644 front/src/views/statistic/components/VCard.vue create mode 100644 front/src/views/statistic/components/VChartTab.vue create mode 100644 front/src/views/statistic/components/VListCard.vue create mode 100644 front/src/views/statistic/components/VTitle.vue create mode 100644 front/src/views/statistic/index.vue create mode 100644 front/src/views/system/org/index.vue create mode 100644 front/src/views/system/resource/index.vue create mode 100644 front/src/views/system/resource/mixins/contextmenu.js create mode 100644 front/src/views/system/resource/share/index.vue create mode 100644 front/src/views/system/resource/share/share-disscuss.vue create mode 100644 front/src/views/system/role/index.vue create mode 100644 front/src/views/system/user/batch-add-user/index.vue create mode 100644 front/src/views/system/user/index.vue create mode 100644 front/src/views/test/index.vue create mode 100644 front/src/views/textbook-manage/index.vue create mode 100644 front/src/views/training/device/device-create/components/create-part.vue create mode 100644 front/src/views/training/device/device-create/components/device-ip-table.vue create mode 100644 front/src/views/training/device/device-create/components/device-part-table.vue create mode 100644 front/src/views/training/device/device-create/components/operation.vue create mode 100644 front/src/views/training/device/device-create/components/post.vue create mode 100644 front/src/views/training/device/device-create/index.vue create mode 100644 front/src/views/training/device/index.vue create mode 100644 front/src/views/training/device/index_.vue create mode 100644 front/src/views/training/evealuation/index.vue create mode 100644 front/src/views/training/evealuation/subject-evaluation/components/PositionStepOperation.vue create mode 100644 front/src/views/training/evealuation/subject-evaluation/components/TeamGroupDuration.vue create mode 100644 front/src/views/training/evealuation/subject-evaluation/components/TeamgroupAverageScores.vue create mode 100644 front/src/views/training/evealuation/subject-evaluation/index.vue create mode 100644 front/src/views/training/evealuation/train-evaluation/components/ScoreEvealuation.vue create mode 100644 front/src/views/training/evealuation/train-evaluation/components/StepClassifyEvealuation.vue create mode 100644 front/src/views/training/evealuation/train-evaluation/components/StepDetailEvealuation.vue create mode 100644 front/src/views/training/evealuation/train-evaluation/components/StepDurationEvealuation.vue create mode 100644 front/src/views/training/evealuation/train-evaluation/components/StepEvealuation.vue create mode 100644 front/src/views/training/evealuation/train-evaluation/components/WaterPolo.vue create mode 100644 front/src/views/training/evealuation/train-evaluation/index.vue create mode 100644 front/src/views/training/evealuation/user-evaluation/components/UserInfo.vue create mode 100644 front/src/views/training/evealuation/user-evaluation/components/UserOpeartionResult.vue create mode 100644 front/src/views/training/evealuation/user-evaluation/components/UserPositionGrade.vue create mode 100644 front/src/views/training/evealuation/user-evaluation/components/UserPostion.vue create mode 100644 front/src/views/training/evealuation/user-evaluation/components/UserSubjectGrade.vue create mode 100644 front/src/views/training/evealuation/user-evaluation/components/UserTrainDuraion.vue create mode 100644 front/src/views/training/evealuation/user-evaluation/components/UserTrainSubjectGrade.vue create mode 100644 front/src/views/training/evealuation/user-evaluation/index.vue create mode 100644 front/src/views/training/subject/components/editorClassify.vue create mode 100644 front/src/views/training/subject/editor-subject/index.vue create mode 100644 front/src/views/training/subject/index.vue create mode 100644 front/src/views/training/training/components/train-chunk-box.vue create mode 100644 front/src/views/training/training/components/train-info.vue create mode 100644 front/src/views/training/training/cteate-train/components/JudgRuleSetting.vue create mode 100644 front/src/views/training/training/cteate-train/components/OperationSetting1.vue create mode 100644 front/src/views/training/training/cteate-train/index.scss create mode 100644 front/src/views/training/training/cteate-train/index.vue create mode 100644 front/src/views/training/training/index.vue create mode 100644 front/src/views/training/training/train-monitor/components/train-calculate-score.vue create mode 100644 front/src/views/training/training/train-monitor/components/train-step.vue create mode 100644 front/src/views/training/training/train-monitor/components/train-video.vue create mode 100644 front/src/views/training/training/train-monitor/index.vue create mode 100644 front/src/views/training/training/utils/train-mqtt.js create mode 100644 front/sub-base/.eslintrc.cjs create mode 100644 front/sub-base/.gitignore create mode 100644 front/sub-base/.prettierrc.json create mode 100644 front/sub-base/.vscode/extensions.json create mode 100644 front/sub-base/README.md create mode 100644 front/sub-base/env.d.ts create mode 100644 front/sub-base/index.html create mode 100644 front/sub-base/package-lock.json create mode 100644 front/sub-base/package.json create mode 100644 front/sub-base/public/favicon.ico create mode 100644 front/sub-base/src/App.vue create mode 100644 front/sub-base/src/assets/base.css create mode 100644 front/sub-base/src/assets/logo.svg create mode 100644 front/sub-base/src/assets/main.css create mode 100644 front/sub-base/src/main.ts create mode 100644 front/sub-base/src/publice-path.ts create mode 100644 front/sub-base/src/router/index.ts create mode 100644 front/sub-base/src/views/home/index.vue create mode 100644 front/sub-base/tsconfig.json create mode 100644 front/sub-base/tsconfig.node.json create mode 100644 front/sub-base/vite.config.ts create mode 100644 front/types/env.d.ts create mode 100644 front/types/index.d.ts create mode 100644 front/types/prototype.d.ts create mode 100644 front/types/vue.d.ts create mode 100644 front/vite.config.js rename .eslintignore => serve/.eslintignore (100%) rename .eslintrc.js => serve/.eslintrc.js (96%) rename .gitignore => serve/.gitignore (94%) rename .prettierrc => serve/.prettierrc (94%) create mode 100644 serve/.vscode/settings.json rename 192.168.0.32_3000_key => serve/192.168.0.32_3000_key (98%) create mode 100644 serve/192.168.0.32_3000_ssl.crt create mode 100644 serve/README.md create mode 100644 serve/bin/publish.sh create mode 100644 serve/front/assets/AQCard.05076a74.css create mode 100644 serve/front/assets/AQCard.8a0f8a13.js create mode 100644 serve/front/assets/ActionBar.1725b1a3.js create mode 100644 serve/front/assets/ActionBar.f9e04dd2.css create mode 100644 serve/front/assets/CircleNumberChart.919f2108.js create mode 100644 serve/front/assets/CircleNumberChart.9fb14e05.css create mode 100644 serve/front/assets/DialogLayout.11655234.css create mode 100644 serve/front/assets/DialogLayout.e3af3567.js create mode 100644 serve/front/assets/ExamDetailsMistakesPercent.7c60de8f.js create mode 100644 serve/front/assets/ExercisePerviewPaper.8e37b607.css create mode 100644 serve/front/assets/ExercisePerviewPaper.d6f9cd34.js create mode 100644 serve/front/assets/FormLayout.c7820bdd.js create mode 100644 serve/front/assets/HeadLayout.8db1abec.css create mode 100644 serve/front/assets/HeadLayout.d9a9c266.js create mode 100644 serve/front/assets/PaperClassifySelector.d254c4c1.js create mode 100644 serve/front/assets/PaperSelectQuestionTable.8e89d183.css create mode 100644 serve/front/assets/PaperSelectQuestionTable.9e024c19.js create mode 100644 serve/front/assets/Preview.0aa9dcef.js create mode 100644 serve/front/assets/Preview.d24ffafb.css create mode 100644 serve/front/assets/PreviewDialog.e564b39f.js create mode 100644 serve/front/assets/QueryInput.7afa399e.js create mode 100644 serve/front/assets/QueryInput.7bc78197.css create mode 100644 serve/front/assets/QuestionDifficultyLevelSelector.b9a0d127.js create mode 100644 serve/front/assets/QuestionItem.1944680c.js create mode 100644 serve/front/assets/QuestionItem.304950a3.css create mode 100644 serve/front/assets/QuestionListPreview.baa07396.js create mode 100644 serve/front/assets/QuestionsList.4ae2bac5.js create mode 100644 serve/front/assets/QuestionsList.54d973dd.css create mode 100644 serve/front/assets/SearchTreeMenu.cf3d6165.js create mode 100644 serve/front/assets/SearchTreeMenu.db7e3694.css create mode 100644 serve/front/assets/StudentsSelector.40d0c907.css create mode 100644 serve/front/assets/StudentsSelector.cadc0de6.js create mode 100644 serve/front/assets/TableLayout.05a39499.js create mode 100644 serve/front/assets/TableLayout.6296ef0f.css create mode 100644 serve/front/assets/UpdatePasswordDialog.45f14f6b.js create mode 100644 serve/front/assets/UploadDialog.0d4280ae.js create mode 100644 serve/front/assets/UploadDialog.4143fbe8.css create mode 100644 serve/front/assets/VListCard.47c5661c.js create mode 100644 serve/front/assets/VListCard.b818974a.css create mode 100644 serve/front/assets/add-course.4b40ab05.css create mode 100644 serve/front/assets/add-course.4b5bbdd4.js create mode 100644 serve/front/assets/button-selft.a87deecf.js create mode 100644 serve/front/assets/button-selft.ad30105c.css create mode 100644 serve/front/assets/clickoutside.ffdfe148.js create mode 100644 serve/front/assets/course-disscuss.1394e5b3.js create mode 100644 serve/front/assets/course-disscuss.db9c8ad8.css create mode 100644 serve/front/assets/course-exercises.67658af8.css create mode 100644 serve/front/assets/course-exercises.98a38110.js create mode 100644 serve/front/assets/data-report.0c7c73f2.js create mode 100644 serve/front/assets/element-icons.a30f5b3b.ttf create mode 100644 serve/front/assets/element-icons.ab40a589.woff create mode 100644 serve/front/assets/exam.a3ef7d5b.js create mode 100644 serve/front/assets/gy.srs.a1080737.css create mode 100644 serve/front/assets/gy.srs.sdk.ab1fd116.js create mode 100644 serve/front/assets/gyStyle.a4df0706.css create mode 100644 serve/front/assets/humanEval.e7206f1d.js create mode 100644 serve/front/assets/index.00fb47d4.css create mode 100644 serve/front/assets/index.02031892.js create mode 100644 serve/front/assets/index.088a0e2d.js create mode 100644 serve/front/assets/index.08f897cf.js create mode 100644 serve/front/assets/index.094fa61c.js create mode 100644 serve/front/assets/index.0a0cd168.js create mode 100644 serve/front/assets/index.0a12cf7f.css create mode 100644 serve/front/assets/index.0c29f644.css create mode 100644 serve/front/assets/index.0c6b81d7.js create mode 100644 serve/front/assets/index.0c8819e3.css create mode 100644 serve/front/assets/index.10c55d71.css create mode 100644 serve/front/assets/index.1318b6d4.js create mode 100644 serve/front/assets/index.1b0182dd.js create mode 100644 serve/front/assets/index.1d3c6214.css create mode 100644 serve/front/assets/index.1f9f6991.js create mode 100644 serve/front/assets/index.24141550.js create mode 100644 serve/front/assets/index.27767255.js create mode 100644 serve/front/assets/index.2835fb30.js create mode 100644 serve/front/assets/index.2adbb9ac.js create mode 100644 serve/front/assets/index.318f92ba.css create mode 100644 serve/front/assets/index.31addd92.css create mode 100644 serve/front/assets/index.31e68358.css create mode 100644 serve/front/assets/index.33b83220.css create mode 100644 serve/front/assets/index.372ef4bf.js create mode 100644 serve/front/assets/index.37445d9f.css create mode 100644 serve/front/assets/index.394186a5.js create mode 100644 serve/front/assets/index.39df79ab.js create mode 100644 serve/front/assets/index.3dbd9863.js create mode 100644 serve/front/assets/index.3faddd0c.js create mode 100644 serve/front/assets/index.402d8413.css create mode 100644 serve/front/assets/index.41e73620.css create mode 100644 serve/front/assets/index.440732ba.js create mode 100644 serve/front/assets/index.4e0d39bc.css create mode 100644 serve/front/assets/index.51534537.css create mode 100644 serve/front/assets/index.56eac716.css create mode 100644 serve/front/assets/index.593aa49c.css create mode 100644 serve/front/assets/index.5ae359fe.js create mode 100644 serve/front/assets/index.5d347c48.js create mode 100644 serve/front/assets/index.5e300020.css create mode 100644 serve/front/assets/index.5f8fbc69.js create mode 100644 serve/front/assets/index.5fe5be39.js create mode 100644 serve/front/assets/index.66725098.js create mode 100644 serve/front/assets/index.6747e3af.js create mode 100644 serve/front/assets/index.68f0e8bd.css create mode 100644 serve/front/assets/index.69e06f8c.css create mode 100644 serve/front/assets/index.6c27c93b.css create mode 100644 serve/front/assets/index.6ecdb8d4.css create mode 100644 serve/front/assets/index.7035dba5.js create mode 100644 serve/front/assets/index.71f43bba.css create mode 100644 serve/front/assets/index.72c376a6.css create mode 100644 serve/front/assets/index.743685a6.js create mode 100644 serve/front/assets/index.74af49c3.js create mode 100644 serve/front/assets/index.7aa299ba.js create mode 100644 serve/front/assets/index.7c9780be.css create mode 100644 serve/front/assets/index.80079b79.js create mode 100644 serve/front/assets/index.8176c251.js create mode 100644 serve/front/assets/index.83b38f22.css create mode 100644 serve/front/assets/index.87f287e8.js create mode 100644 serve/front/assets/index.8d667c0f.css create mode 100644 serve/front/assets/index.8fb1167b.js create mode 100644 serve/front/assets/index.8ff275da.css create mode 100644 serve/front/assets/index.9145d306.css create mode 100644 serve/front/assets/index.93eadc68.css create mode 100644 serve/front/assets/index.9706fae9.css create mode 100644 serve/front/assets/index.97da6707.js create mode 100644 serve/front/assets/index.9aa689ff.css create mode 100644 serve/front/assets/index.9dec3212.css create mode 100644 serve/front/assets/index.a4f3e52e.css create mode 100644 serve/front/assets/index.a7ea6e92.css create mode 100644 serve/front/assets/index.ad169e89.js create mode 100644 serve/front/assets/index.aeb5e9f3.js create mode 100644 serve/front/assets/index.b0bfe0f6.js create mode 100644 serve/front/assets/index.b2106966.js create mode 100644 serve/front/assets/index.b3c76749.css create mode 100644 serve/front/assets/index.b461b679.js create mode 100644 serve/front/assets/index.b4b565bd.css create mode 100644 serve/front/assets/index.b4df0615.js create mode 100644 serve/front/assets/index.b6618dcb.js create mode 100644 serve/front/assets/index.b95816a8.js create mode 100644 serve/front/assets/index.bb5f1b0a.js create mode 100644 serve/front/assets/index.bd28e25c.css create mode 100644 serve/front/assets/index.bd669162.js create mode 100644 serve/front/assets/index.bd813a75.js create mode 100644 serve/front/assets/index.bec79140.js create mode 100644 serve/front/assets/index.bee3c024.js create mode 100644 serve/front/assets/index.bf24d501.css create mode 100644 serve/front/assets/index.c021b48b.js create mode 100644 serve/front/assets/index.c357f30c.css create mode 100644 serve/front/assets/index.cc7579bf.js create mode 100644 serve/front/assets/index.cd60bb8b.js create mode 100644 serve/front/assets/index.ce07e0bd.js create mode 100644 serve/front/assets/index.cf1c077a.js create mode 100644 serve/front/assets/index.d7316fc5.js create mode 100644 serve/front/assets/index.da1f3265.css create mode 100644 serve/front/assets/index.dc5f5f64.css create mode 100644 serve/front/assets/index.ddd17e1a.js create mode 100644 serve/front/assets/index.def88f1f.js create mode 100644 serve/front/assets/index.e0f63d00.css create mode 100644 serve/front/assets/index.e13f82bf.js create mode 100644 serve/front/assets/index.e8f6b657.js create mode 100644 serve/front/assets/index.ea805aac.js create mode 100644 serve/front/assets/index.ebb776b9.css create mode 100644 serve/front/assets/index.efbda710.js create mode 100644 serve/front/assets/index.f5168bf8.js create mode 100644 serve/front/assets/index.fb88617a.js create mode 100644 serve/front/assets/index.fc1a0811.css create mode 100644 serve/front/assets/index.fc765047.js create mode 100644 serve/front/assets/index.fcae50d5.css create mode 100644 serve/front/assets/index.ffffb6ad.js create mode 100644 serve/front/assets/live-manage.d907c119.js create mode 100644 serve/front/assets/live-student.e6ad7a61.js create mode 100644 serve/front/assets/mistake.4e233ae2.js create mode 100644 serve/front/assets/my-course.d26f38fb.js create mode 100644 serve/front/assets/online-course.07094acf.js create mode 100644 serve/front/assets/onlineTest.1d86eb4e.js create mode 100644 serve/front/assets/org.23184f20.js create mode 100644 serve/front/assets/package.3367ca0a.js create mode 100644 serve/front/assets/paper.4b971da3.js create mode 100644 serve/front/assets/questionInfoMapJsonTrans.58e126b5.js create mode 100644 serve/front/assets/questions.7b7a7919.js create mode 100644 serve/front/assets/rich-text-editor.702494a8.js create mode 100644 serve/front/assets/rich-text-editor.79e8f191.css create mode 100644 serve/front/assets/role.1c66ca6a.js create mode 100644 serve/front/assets/user.64525a2f.js create mode 100644 serve/front/assets/utils.068fedb0.js create mode 100644 serve/front/assets/vueComponentNormalizer.9ef17bb1.js create mode 100644 serve/front/assets/xlsx.db07aefa.js create mode 100644 serve/front/default.svg create mode 100644 serve/front/font/iconfont.css create mode 100644 serve/front/font/iconfont.ttf create mode 100644 serve/front/imgs/entry/1.png create mode 100644 serve/front/imgs/entry/2.png create mode 100644 serve/front/imgs/entry/3.png create mode 100644 serve/front/imgs/entry/4.png create mode 100644 serve/front/imgs/entry/5.png create mode 100644 serve/front/imgs/entry/assess.png create mode 100644 serve/front/imgs/entry/course.png create mode 100644 serve/front/imgs/entry/evaluate.png create mode 100644 serve/front/imgs/entry/resource.png create mode 100644 serve/front/imgs/entry/textbook.png create mode 100644 serve/front/imgs/login-bg.jpg create mode 100644 serve/front/imgs/login-bottom.png create mode 100644 serve/front/imgs/login-draw.png create mode 100644 serve/front/imgs/login-top.png create mode 100644 serve/front/imgs/preview-full-volume.png create mode 100644 serve/front/imgs/preview-question-by-question.png create mode 100644 serve/front/index.html create mode 100644 serve/front/js/axios.min.js create mode 100644 serve/front/js/element-ui.min.js create mode 100644 serve/front/js/socket.io.min.js create mode 100644 serve/front/js/vue-router.min.js create mode 100644 serve/front/js/vue.min.js create mode 100644 serve/front/js/vuex.min.js create mode 100644 serve/front/logo.svg create mode 100644 serve/front/svg/404.svg create mode 100644 serve/front/svg/nomore.svg create mode 100644 serve/front/svg/none.svg create mode 100644 serve/front/svg/noseach.svg create mode 100644 serve/front/svg/timeout.svg create mode 100644 serve/nest-cli.json create mode 100644 serve/package-lock.json create mode 100644 serve/package.json create mode 100644 serve/src/app.module.ts create mode 100644 serve/src/article_manage/article_manage.controller.ts create mode 100644 serve/src/article_manage/article_manage.module.ts create mode 100644 serve/src/article_manage/article_manage.service.ts create mode 100644 serve/src/article_manage/dto/create-article_manage.dto.ts create mode 100644 serve/src/article_manage/dto/update-article_manage.dto.ts create mode 100644 serve/src/article_manage/dto/update-portion-article.dto.ts create mode 100644 serve/src/article_manage/entities/article_manage.entity.ts create mode 100644 serve/src/assessment-evaluation/assessment-evaluation.module.ts create mode 100644 serve/src/assessment-evaluation/controller/assessment-evaluation.controller.ts create mode 100644 serve/src/assessment-evaluation/controller/exam-manager/exam-classify.controller.ts create mode 100644 serve/src/assessment-evaluation/controller/exam-manager/exam.controller.ts create mode 100644 serve/src/assessment-evaluation/controller/exam-manager/online-exam-history.controller.ts create mode 100644 serve/src/assessment-evaluation/controller/exam-manager/student-online-exam.controller.ts create mode 100644 serve/src/assessment-evaluation/controller/exam-paper-manager/exam-paper-classify.controller.ts create mode 100644 serve/src/assessment-evaluation/controller/exam-paper-manager/exam-paper.controller.ts create mode 100644 serve/src/assessment-evaluation/controller/question-manager/knowledge-point.controller.ts create mode 100644 serve/src/assessment-evaluation/controller/question-manager/question-classify.controller.ts create mode 100644 serve/src/assessment-evaluation/controller/question-manager/question-difficulty-level.controller.ts create mode 100644 serve/src/assessment-evaluation/controller/question-manager/question-type.controller.ts create mode 100644 serve/src/assessment-evaluation/controller/question-manager/question.controller.ts create mode 100644 serve/src/assessment-evaluation/controller/sim-test/mistake-again.controller.ts create mode 100644 serve/src/assessment-evaluation/controller/sim-test/sim-test.controller.ts create mode 100644 serve/src/assessment-evaluation/dto/create-assessment-evaluation.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-exam-manager/exam-classify/create-exam-classify.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-exam-manager/exam-classify/update-exam-classify.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-exam-manager/exam/create-exam.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-exam-manager/exam/paging-exam.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-exam-manager/exam/update-exam.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-exam-manager/student-online-exam/create-student-online-exam.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-exam-manager/student-online-exam/paging-student-online-exam.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-exam-manager/student-online-exam/update-student-online-exam.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-exam-paper-manager/exam-paper-classify/create-exam-paper-classify.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-exam-paper-manager/exam-paper-classify/update-exam-paper-classify.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-exam-paper-manager/exam-paper/create-exam-paper.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-exam-paper-manager/exam-paper/paging-exam-paper.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-exam-paper-manager/exam-paper/update-exam-paper.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-exam-paper-manager/question-for-paper/create-question-for-paper.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-question-manager/knowledge-point/create-knowledge-point.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-question-manager/knowledge-point/update-knowledge-point.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-question-manager/question-classify/create-question-classify.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-question-manager/question-classify/update-question-classify.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-question-manager/question-difficulty-level/create-question-difficulty-level.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-question-manager/question-difficulty-level/update-question-difficulty-level.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-question-manager/question-type/create-question-type.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-question-manager/question-type/update-question-type.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-question-manager/question/create-question.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-question-manager/question/get-question-count.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-question-manager/question/paging-question.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/dtos-question-manager/question/update-question.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/pagination.dto.ts create mode 100644 serve/src/assessment-evaluation/dto/update-assessment-evaluation.dto.ts create mode 100644 serve/src/assessment-evaluation/entities/assessment-evaluation.entity.ts create mode 100644 serve/src/assessment-evaluation/entities/entities-exam-manager/exam-classify.entity.ts create mode 100644 serve/src/assessment-evaluation/entities/entities-exam-manager/exam-questions-result.entity.ts create mode 100644 serve/src/assessment-evaluation/entities/entities-exam-manager/exam.entity.ts create mode 100644 serve/src/assessment-evaluation/entities/entities-exam-manager/online-exam-history.entity.ts create mode 100644 serve/src/assessment-evaluation/entities/entities-exam-manager/student-online-exam.entity.ts create mode 100644 serve/src/assessment-evaluation/entities/entities-exam-paper-manager/exam-paper-classify.entity.ts create mode 100644 serve/src/assessment-evaluation/entities/entities-exam-paper-manager/exam-paper.entity.ts create mode 100644 serve/src/assessment-evaluation/entities/entities-exam-paper-manager/questions-for-paper.entity.ts create mode 100644 serve/src/assessment-evaluation/entities/entities-question-manager/knowledge-point.entity.ts create mode 100644 serve/src/assessment-evaluation/entities/entities-question-manager/question-classify.entity.ts create mode 100644 serve/src/assessment-evaluation/entities/entities-question-manager/question-difficulty-level.entity.ts create mode 100644 serve/src/assessment-evaluation/entities/entities-question-manager/question-type.entity.ts create mode 100644 serve/src/assessment-evaluation/entities/entities-question-manager/question.entity.ts create mode 100644 serve/src/assessment-evaluation/modules/exam-manager.modules.ts create mode 100644 serve/src/assessment-evaluation/modules/exam-paper-manager.modules.ts create mode 100644 serve/src/assessment-evaluation/modules/question-manager.modules.ts create mode 100644 serve/src/assessment-evaluation/service/assessment-evaluation.service.ts create mode 100644 serve/src/assessment-evaluation/service/service-exam-manager/exam-classify.service.ts create mode 100644 serve/src/assessment-evaluation/service/service-exam-manager/exam-questions-result.service.ts create mode 100644 serve/src/assessment-evaluation/service/service-exam-manager/exam.service.ts create mode 100644 serve/src/assessment-evaluation/service/service-exam-manager/online-exam-history.service.ts create mode 100644 serve/src/assessment-evaluation/service/service-exam-manager/student-online-exam.service.ts create mode 100644 serve/src/assessment-evaluation/service/service-exam-paper-manager/exam-paper-classify.service.ts create mode 100644 serve/src/assessment-evaluation/service/service-exam-paper-manager/exam-paper.service.ts create mode 100644 serve/src/assessment-evaluation/service/service-exam-paper-manager/questions-for-paper.ts create mode 100644 serve/src/assessment-evaluation/service/service-question-manager/knowledge-point.service.ts create mode 100644 serve/src/assessment-evaluation/service/service-question-manager/question-classify.service.ts create mode 100644 serve/src/assessment-evaluation/service/service-question-manager/question-difficulty-level.service.ts create mode 100644 serve/src/assessment-evaluation/service/service-question-manager/question-type.service.ts create mode 100644 serve/src/assessment-evaluation/service/service-question-manager/question.service.ts create mode 100644 serve/src/common/decorator/no-token.decorator.ts create mode 100644 serve/src/common/decorator/query-page-info.decorator.ts create mode 100644 serve/src/common/decorator/token-data.decorator.ts create mode 100644 serve/src/common/dto/create.dto.ts create mode 100644 serve/src/common/dto/update.dto.ts create mode 100644 serve/src/common/entities/base.entity.ts create mode 100644 serve/src/common/entities/increment-id.entity.ts create mode 100644 serve/src/common/entities/page-info.entity.ts create mode 100644 serve/src/common/entities/token-data.entity.ts create mode 100644 serve/src/common/filters/global-exception.filter.ts create mode 100644 serve/src/common/interceptor/response.interceptor.ts create mode 100644 serve/src/common/plugins/custom-type-orm-logger.ts create mode 100644 serve/src/common/plugins/index.ts create mode 100644 serve/src/common/plugins/mysql.ts create mode 100644 serve/src/common/plugins/static-assets.ts create mode 100644 serve/src/common/plugins/swagger.ts create mode 100644 serve/src/env.ts create mode 100644 serve/src/evaluation copy/controller/evaluation-indicator.controller.ts create mode 100644 serve/src/evaluation copy/controller/evaluation.controller.ts create mode 100644 serve/src/evaluation copy/dto/evaluation-indicator.dto.ts create mode 100644 serve/src/evaluation copy/dto/evaluation.dto.ts create mode 100644 serve/src/evaluation copy/dto/student-evaluation.dto.ts create mode 100644 serve/src/evaluation copy/entities/evaluation-indicator.entity.ts create mode 100644 serve/src/evaluation copy/entities/evaluation.entity.ts create mode 100644 serve/src/evaluation copy/entities/student-evaluation.entity.ts create mode 100644 serve/src/evaluation copy/evaluation.module.ts create mode 100644 serve/src/evaluation copy/service/evaluation-indicator.service.ts create mode 100644 serve/src/evaluation copy/service/evaluation.service.ts create mode 100644 serve/src/evaluation/controller/evaluation-indicator.controller.ts create mode 100644 serve/src/evaluation/controller/evaluation.controller.ts create mode 100644 serve/src/evaluation/dto/evaluation-indicator.dto.ts create mode 100644 serve/src/evaluation/dto/evaluation.dto.ts create mode 100644 serve/src/evaluation/dto/student-evaluation.dto.ts create mode 100644 serve/src/evaluation/entities/evaluation-indicator.entity.ts create mode 100644 serve/src/evaluation/entities/evaluation.entity.ts create mode 100644 serve/src/evaluation/entities/student-evaluation.entity.ts create mode 100644 serve/src/evaluation/evaluation.module.ts create mode 100644 serve/src/evaluation/service/evaluation-indicator.service.ts create mode 100644 serve/src/evaluation/service/evaluation.service.ts create mode 100644 serve/src/initialize/data-source.ts create mode 100644 serve/src/initialize/initialize.controller.ts create mode 100644 serve/src/initialize/initialize.module.ts create mode 100644 serve/src/main.ts create mode 100644 serve/src/manuals_manage/dto/create-manuals_manage.dto.ts create mode 100644 serve/src/manuals_manage/dto/create-manuals_manage_classify.dto.ts create mode 100644 serve/src/manuals_manage/dto/update-manuals_manage.dto.ts create mode 100644 serve/src/manuals_manage/dto/update-manuals_manage_classify.dto copy.ts create mode 100644 serve/src/manuals_manage/entities/manuals_manage.entity.ts create mode 100644 serve/src/manuals_manage/entities/manuals_manage_classify.entity.ts create mode 100644 serve/src/manuals_manage/manuals_manage.controller.ts create mode 100644 serve/src/manuals_manage/manuals_manage.module.ts create mode 100644 serve/src/manuals_manage/manuals_manage.service.ts create mode 100644 serve/src/online-course/common/delete-dto.ts create mode 100644 serve/src/online-course/common/pagination.dto.ts create mode 100644 serve/src/online-course/controller/course-manage.controller.ts create mode 100644 serve/src/online-course/controller/course-my.controller.ts create mode 100644 serve/src/online-course/controller/course-notes.controller.ts create mode 100644 serve/src/online-course/controller/online-answer.controller.ts create mode 100644 serve/src/online-course/dto/course-manage/course-classify.dto.ts create mode 100644 serve/src/online-course/dto/course-manage/create-online-course.dto.ts create mode 100644 serve/src/online-course/dto/course-manage/get-course-list-dto.ts create mode 100644 serve/src/online-course/dto/course-manage/update-course-classify.dto.ts create mode 100644 serve/src/online-course/dto/course-manage/update-online-course.dto.ts create mode 100644 serve/src/online-course/dto/course-my/course-study-courseware.dto.ts create mode 100644 serve/src/online-course/dto/course-my/course-study-discussion.dto.ts create mode 100644 serve/src/online-course/dto/course-my/course-study-evaluate.dto.ts create mode 100644 serve/src/online-course/dto/course-my/course-study-exercise.dto.ts create mode 100644 serve/src/online-course/dto/course-my/course-study-record.dto.ts create mode 100644 serve/src/online-course/dto/course-notes/course-nots.dto.ts create mode 100644 serve/src/online-course/dto/online-issues/online-answer.dto.ts create mode 100644 serve/src/online-course/dto/online-issues/online-issues.dto.ts create mode 100644 serve/src/online-course/entities/course-manage/course-classify.entity.ts create mode 100644 serve/src/online-course/entities/course-manage/course-courseware.entity.ts create mode 100644 serve/src/online-course/entities/course-manage/course-evaluate.entity.ts create mode 100644 serve/src/online-course/entities/course-manage/course-list.entity.ts create mode 100644 serve/src/online-course/entities/course-manage/course-questions.entity.ts create mode 100644 serve/src/online-course/entities/course-manage/course-section-content.entity.ts create mode 100644 serve/src/online-course/entities/course-my/course-study-courseware.entity.ts create mode 100644 serve/src/online-course/entities/course-my/course-study-discussion-kudos.entity.ts create mode 100644 serve/src/online-course/entities/course-my/course-study-discussion.entity.ts create mode 100644 serve/src/online-course/entities/course-my/course-study-evaluate.entity.ts create mode 100644 serve/src/online-course/entities/course-my/course-study-exercise.entity.ts create mode 100644 serve/src/online-course/entities/course-my/course-study-record.entity.ts create mode 100644 serve/src/online-course/entities/course-notes/course-notes.entity.ts create mode 100644 serve/src/online-course/entities/online-issues/online-answer.entity.ts create mode 100644 serve/src/online-course/entities/online-issues/online-issues.entity.ts create mode 100644 serve/src/online-course/online-course.module.ts create mode 100644 serve/src/online-course/service/course-notes.service.ts create mode 100644 serve/src/online-course/service/my-course.service.ts create mode 100644 serve/src/online-course/service/online-course.service.ts create mode 100644 serve/src/online-course/service/online-issues.service.ts create mode 100644 serve/src/online-teaching/controller/live-class.controller.ts create mode 100644 serve/src/online-teaching/controller/live-teaching.controller.ts create mode 100644 serve/src/online-teaching/dto/live-classify.dto.ts create mode 100644 serve/src/online-teaching/dto/live-create.dto.ts create mode 100644 serve/src/online-teaching/dto/live-msg-data.dto.ts create mode 100644 serve/src/online-teaching/dto/live-question-answer.dto.ts create mode 100644 serve/src/online-teaching/dto/live-update.dto.ts create mode 100644 serve/src/online-teaching/entities/live-classify.entity.ts create mode 100644 serve/src/online-teaching/entities/live-courseware.entity.ts create mode 100644 serve/src/online-teaching/entities/live-evaluate.entity.ts create mode 100644 serve/src/online-teaching/entities/live-msg-data.entity.ts create mode 100644 serve/src/online-teaching/entities/live-question-answer.entity.ts create mode 100644 serve/src/online-teaching/entities/live-question.entity.ts create mode 100644 serve/src/online-teaching/entities/live-student.entity.ts create mode 100644 serve/src/online-teaching/entities/live-teaching.entity.ts create mode 100644 serve/src/online-teaching/gateway/online-teaching.gateway.ts create mode 100644 serve/src/online-teaching/online-teaching.module.ts create mode 100644 serve/src/online-teaching/service/live-class.service.ts create mode 100644 serve/src/online-teaching/service/live-teaching.service.ts create mode 100644 serve/src/online-teaching/ws-jwt-guard.ts create mode 100644 serve/src/prototype.ts create mode 100644 serve/src/resource/controller/resource-classify.controller.ts create mode 100644 serve/src/resource/controller/resource.controller.ts create mode 100644 serve/src/resource/dto/resource-classify.dto.ts create mode 100644 serve/src/resource/entities/resource-classify.entity.ts create mode 100644 serve/src/resource/entities/resource-discussion.entity.ts create mode 100644 serve/src/resource/entities/resource.entity.ts create mode 100644 serve/src/resource/resource.module.ts create mode 100644 serve/src/resource/service/resource-classify.service.ts create mode 100644 serve/src/resource/service/resource-discussion.service.ts create mode 100644 serve/src/resource/service/resource.service.ts create mode 100644 serve/src/statistic/entities/report-data.entity.ts create mode 100644 serve/src/statistic/service/admin/charts/res-quantity.ts create mode 100644 serve/src/statistic/service/admin/charts/res-size.ts create mode 100644 serve/src/statistic/service/admin/charts/system-org.ts create mode 100644 serve/src/statistic/service/admin/charts/system-service-period.ts create mode 100644 serve/src/statistic/service/admin/charts/system-use.ts create mode 100644 serve/src/statistic/service/admin/charts/system-user.ts create mode 100644 serve/src/statistic/service/admin/index/index.ts create mode 100644 serve/src/statistic/service/student/course/course-content.ts create mode 100644 serve/src/statistic/service/student/course/learning-state.ts create mode 100644 serve/src/statistic/service/student/index/index.ts create mode 100644 serve/src/statistic/service/student/mistakes/mistakes-is-pass-percent.ts create mode 100644 serve/src/statistic/service/student/mistakes/paper-mistakes-percent.ts create mode 100644 serve/src/statistic/service/student/note/student-note-count.ts create mode 100644 serve/src/statistic/service/student/note/student-note-info.ts create mode 100644 serve/src/statistic/service/student/note/student-note-update-rankings.ts create mode 100644 serve/src/statistic/service/student/note/student-note-view-rankings.ts create mode 100644 serve/src/statistic/service/student/paper/exam-details-mistakes-percent.ts create mode 100644 serve/src/statistic/service/student/paper/exam-details-score-percent.ts create mode 100644 serve/src/statistic/service/student/paper/exam-duration-tower.ts create mode 100644 serve/src/statistic/service/student/paper/exam-history-score-line.ts create mode 100644 serve/src/statistic/service/student/paper/exam-mistakes-type-percent.ts create mode 100644 serve/src/statistic/service/student/paper/exam-score-pass-percent.ts create mode 100644 serve/src/statistic/service/student/paper/exam-score-tower.ts create mode 100644 serve/src/statistic/service/student/paper/paper-details-table.ts create mode 100644 serve/src/statistic/service/student/paper/paper-difficulty-by-history.ts create mode 100644 serve/src/statistic/service/student/paper/paper-difficulty-percent.ts create mode 100644 serve/src/statistic/service/student/paper/paper-is-fixed-percent.ts create mode 100644 serve/src/statistic/service/student/paper/paper-mistakes-percent.ts create mode 100644 serve/src/statistic/service/student/paper/paper-score-pass-percent.ts create mode 100644 serve/src/statistic/service/student/score/exam-details-mistakes-percent.ts create mode 100644 serve/src/statistic/service/student/score/exam-details-score-order-tower.ts create mode 100644 serve/src/statistic/service/student/score/exam-details-score-percent.ts create mode 100644 serve/src/statistic/service/student/score/exam-details-table.ts create mode 100644 serve/src/statistic/service/student/score/exam-duration-tower.ts create mode 100644 serve/src/statistic/service/student/score/exam-history-score-line.ts create mode 100644 serve/src/statistic/service/student/score/exam-mistakes-type-percent.ts create mode 100644 serve/src/statistic/service/student/score/exam-score-pass-percent.ts create mode 100644 serve/src/statistic/service/student/score/exam-score-tower.ts create mode 100644 serve/src/statistic/service/student/view-resource/res-details-table.ts create mode 100644 serve/src/statistic/service/student/view-resource/res-quantity.ts create mode 100644 serve/src/statistic/service/student/view-resource/res-size.ts create mode 100644 serve/src/statistic/service/teacher/course/detail-analysis-live-list.ts create mode 100644 serve/src/statistic/service/teacher/course/detail-analysis.ts create mode 100644 serve/src/statistic/service/teacher/course/setting-course-hour.ts create mode 100644 serve/src/statistic/service/teacher/course/studnet-study-analyse.ts create mode 100644 serve/src/statistic/service/teacher/course/study-course-hour.ts create mode 100644 serve/src/statistic/service/teacher/examination/exam-details-join-student-count-percent.ts create mode 100644 serve/src/statistic/service/teacher/examination/exam-details-mistakes-percent.ts create mode 100644 serve/src/statistic/service/teacher/examination/exam-details-question-score-count-percent.ts create mode 100644 serve/src/statistic/service/teacher/examination/exam-details-score-ispass-percent.ts create mode 100644 serve/src/statistic/service/teacher/examination/exam-details-score-order-tower.ts create mode 100644 serve/src/statistic/service/teacher/examination/exam-duration-tower.ts create mode 100644 serve/src/statistic/service/teacher/examination/exam-score-pass-percent.ts create mode 100644 serve/src/statistic/service/teacher/examination/exam-student-times-tower.ts create mode 100644 serve/src/statistic/service/teacher/exercise/exercise-duration-percent.ts create mode 100644 serve/src/statistic/service/teacher/exercise/exercise-question-is-pass-percent.ts create mode 100644 serve/src/statistic/service/teacher/exercise/exercise-times-percent.ts create mode 100644 serve/src/statistic/service/teacher/index/index.ts create mode 100644 serve/src/statistic/service/teacher/live-broadcast/all-broadcast.ts create mode 100644 serve/src/statistic/service/teacher/live-broadcast/detail-broadcast.ts create mode 100644 serve/src/statistic/service/teacher/live-broadcast/detail-score-tower.ts create mode 100644 serve/src/statistic/service/teacher/live-broadcast/detail-stu-count-line.ts create mode 100644 serve/src/statistic/service/teacher/resources/res-details-table.ts create mode 100644 serve/src/statistic/service/teacher/resources/res-file-data.ts create mode 100644 serve/src/statistic/service/teacher/resources/res-perview.ts create mode 100644 serve/src/statistic/service/teacher/resources/res-quantity.ts create mode 100644 serve/src/statistic/service/teacher/resources/res-size.ts create mode 100644 serve/src/statistic/service/teacher/test-question/mistakes-order-tower.ts create mode 100644 serve/src/statistic/service/teacher/test-question/question-difficulty-by-type-percent.ts create mode 100644 serve/src/statistic/service/teacher/test-question/questions-count-by-type-and-difficulty-percent.ts create mode 100644 serve/src/statistic/service/teacher/test-question/questions-count-by-type-percent.ts create mode 100644 serve/src/statistic/service/teacher/trainee/basic-system-service-period.ts create mode 100644 serve/src/statistic/service/teacher/trainee/basic-system-use.ts create mode 100644 serve/src/statistic/service/teacher/trainee/basic-trainee-age.ts create mode 100644 serve/src/statistic/service/teacher/trainee/basic-trainee-number.ts create mode 100644 serve/src/statistic/service/teacher/trainee/basic-trainee-org.ts create mode 100644 serve/src/statistic/service/teacher/trainee/exam-details-student-score-table.ts create mode 100644 serve/src/statistic/service/teacher/trainee/exam-duration-tower.ts create mode 100644 serve/src/statistic/service/teacher/trainee/exam-mistakes-type-percent.ts create mode 100644 serve/src/statistic/service/teacher/trainee/exam-score-pass-percent.ts create mode 100644 serve/src/statistic/service/teacher/trainee/exam-student-score-tower.ts create mode 100644 serve/src/statistic/service/teacher/trainee/exercise-details-questions-get-paper.ts create mode 100644 serve/src/statistic/service/teacher/trainee/exercise-details-table.ts create mode 100644 serve/src/statistic/service/teacher/trainee/exercise-qus-type-percent.ts create mode 100644 serve/src/statistic/service/teacher/trainee/exercise-student-qus-is-pass-percent.ts create mode 100644 serve/src/statistic/service/teacher/trainee/exercise-student-times-percent.ts create mode 100644 serve/src/statistic/service/teacher/trainee/study-count.ts create mode 100644 serve/src/statistic/statistic.controller.ts create mode 100644 serve/src/statistic/statistic.module.ts create mode 100644 serve/src/system/controller/feature.controller.ts create mode 100644 serve/src/system/controller/org.controller.ts create mode 100644 serve/src/system/controller/role.controller.ts create mode 100644 serve/src/system/controller/system.controller.ts create mode 100644 serve/src/system/controller/user.controller.ts create mode 100644 serve/src/system/dto/create-org.dto.ts create mode 100644 serve/src/system/dto/login-user.dto.ts create mode 100644 serve/src/system/dto/role.dto.ts create mode 100644 serve/src/system/dto/update-org.dto.ts create mode 100644 serve/src/system/dto/update-password.dto.ts create mode 100644 serve/src/system/dto/user.dto.ts create mode 100644 serve/src/system/entities/feature.entity.ts create mode 100644 serve/src/system/entities/org.entity.ts create mode 100644 serve/src/system/entities/role.entity.ts create mode 100644 serve/src/system/entities/teacher-student.entity.ts create mode 100644 serve/src/system/entities/user.entity.ts create mode 100644 serve/src/system/jwt/jwt-auth.guard.ts create mode 100644 serve/src/system/jwt/jwt.strategy.ts create mode 100644 serve/src/system/service/feature.service.ts create mode 100644 serve/src/system/service/org.service.ts create mode 100644 serve/src/system/service/role.service.ts create mode 100644 serve/src/system/service/system.service.ts create mode 100644 serve/src/system/service/user.service.ts create mode 100644 serve/src/system/system.module.ts create mode 100644 serve/src/train/controller/device/device-classify.controller.ts create mode 100644 serve/src/train/controller/device/device-number-position.controller.ts create mode 100644 serve/src/train/controller/device/device-part-operation.controller.ts create mode 100644 serve/src/train/controller/device/device-part.controller.ts create mode 100644 serve/src/train/controller/device/device.controller.ts create mode 100644 serve/src/train/controller/subject/subject-classify.controller copy.ts create mode 100644 serve/src/train/controller/subject/subject-classify.controller.ts create mode 100644 serve/src/train/controller/subject/subject.controller.ts create mode 100644 serve/src/train/controller/subject/train-type.controller.ts create mode 100644 serve/src/train/controller/train/train-module.controller.ts create mode 100644 serve/src/train/entities/device/device-classify.entity.ts create mode 100644 serve/src/train/entities/device/device-ip.entity.ts create mode 100644 serve/src/train/entities/device/device-number-position.entity.ts create mode 100644 serve/src/train/entities/device/device-part-has-operation.entity.ts create mode 100644 serve/src/train/entities/device/device-part-operation.entity.ts create mode 100644 serve/src/train/entities/device/device-parts.entity.ts create mode 100644 serve/src/train/entities/device/device.entity.ts create mode 100644 serve/src/train/entities/subject/subject-classify.entity.ts create mode 100644 serve/src/train/entities/subject/subject-has-device.entity.ts create mode 100644 serve/src/train/entities/subject/subject-step.entity.ts create mode 100644 serve/src/train/entities/subject/subject.entity.ts create mode 100644 serve/src/train/entities/subject/train-type.entity.ts create mode 100644 serve/src/train/entities/train/train-operation-log.entity.ts create mode 100644 serve/src/train/entities/train/train-score-log.entity.ts create mode 100644 serve/src/train/entities/train/train-settings.entity.ts create mode 100644 serve/src/train/entities/train/train.entity.ts create mode 100644 serve/src/train/modules/device.modules.ts create mode 100644 serve/src/train/modules/subject.modules.ts create mode 100644 serve/src/train/modules/train-module.modules.ts create mode 100644 serve/src/train/service/device/device-classify.service.ts create mode 100644 serve/src/train/service/device/device-number-position.service.ts create mode 100644 serve/src/train/service/device/device-part-operation.service.ts create mode 100644 serve/src/train/service/device/device-parts.service.ts create mode 100644 serve/src/train/service/device/device.service.ts create mode 100644 serve/src/train/service/subject/subject-classify.service.ts create mode 100644 serve/src/train/service/subject/subject.service copy.ts create mode 100644 serve/src/train/service/subject/subject.service.ts create mode 100644 serve/src/train/service/subject/train-type.service.ts create mode 100644 serve/src/train/service/train/train.service.ts create mode 100644 serve/src/train/train.module.ts create mode 100644 serve/test/app.e2e-spec.ts create mode 100644 serve/test/jest-e2e.json create mode 100644 serve/tf/css/theme-colors-c2200869.css create mode 100644 serve/tf/css/theme-colors-c2200869.css.gz create mode 100644 serve/tf/e5d0f237850f4ec00dfa.worker.js create mode 100644 serve/tf/e5d0f237850f4ec00dfa.worker.js.gz create mode 100644 serve/tf/favicon.ico create mode 100644 serve/tf/font/demo.css create mode 100644 serve/tf/font/demo_index.html create mode 100644 serve/tf/font/iconfont.css create mode 100644 serve/tf/font/iconfont.js create mode 100644 serve/tf/font/iconfont.js.gz create mode 100644 serve/tf/font/iconfont.json create mode 100644 serve/tf/font/iconfont.ttf create mode 100644 serve/tf/index.html create mode 100644 serve/tf/static/css/app.79cf2c9e.css create mode 100644 serve/tf/static/css/app.79cf2c9e.css.gz create mode 100644 serve/tf/static/css/chunk-19400648.7ce87d8b.css create mode 100644 serve/tf/static/css/chunk-3b4e252e.2db9e46c.css create mode 100644 serve/tf/static/css/chunk-488247af.7ce87d8b.css create mode 100644 serve/tf/static/css/chunk-580ca977.06a65142.css create mode 100644 serve/tf/static/css/chunk-6a5b4dbe.f777d6be.css create mode 100644 serve/tf/static/css/chunk-6a5b4dbe.f777d6be.css.gz create mode 100644 serve/tf/static/css/chunk-75e685f5.7ce87d8b.css create mode 100644 serve/tf/static/css/chunk-7a26dadf.ae432fdf.css create mode 100644 serve/tf/static/css/chunk-98971e2c.a23f5c8d.css create mode 100644 serve/tf/static/css/chunk-98971e2c.a23f5c8d.css.gz create mode 100644 serve/tf/static/css/chunk-vendors.dbcdfda1.css create mode 100644 serve/tf/static/css/chunk-vendors.dbcdfda1.css.gz create mode 100644 serve/tf/static/fonts/KaTeX_AMS-Regular.10824af7.woff create mode 100644 serve/tf/static/fonts/KaTeX_AMS-Regular.56573229.ttf create mode 100644 serve/tf/static/fonts/KaTeX_AMS-Regular.66c67820.woff2 create mode 100644 serve/tf/static/fonts/KaTeX_Caligraphic-Bold.497bf407.ttf create mode 100644 serve/tf/static/fonts/KaTeX_Caligraphic-Bold.a9e9b095.woff2 create mode 100644 serve/tf/static/fonts/KaTeX_Caligraphic-Bold.de2ba279.woff create mode 100644 serve/tf/static/fonts/KaTeX_Caligraphic-Regular.08d95d99.woff2 create mode 100644 serve/tf/static/fonts/KaTeX_Caligraphic-Regular.a25140fb.woff create mode 100644 serve/tf/static/fonts/KaTeX_Caligraphic-Regular.e6fb499f.ttf create mode 100644 serve/tf/static/fonts/KaTeX_Fraktur-Bold.40934fc0.woff create mode 100644 serve/tf/static/fonts/KaTeX_Fraktur-Bold.796f3797.woff2 create mode 100644 serve/tf/static/fonts/KaTeX_Fraktur-Bold.b9d7c449.ttf create mode 100644 serve/tf/static/fonts/KaTeX_Fraktur-Regular.97a699d8.ttf create mode 100644 serve/tf/static/fonts/KaTeX_Fraktur-Regular.e435cda5.woff create mode 100644 serve/tf/static/fonts/KaTeX_Fraktur-Regular.f9e6a99f.woff2 create mode 100644 serve/tf/static/fonts/KaTeX_Main-Bold.4cdba646.woff create mode 100644 serve/tf/static/fonts/KaTeX_Main-Bold.8e431f7e.ttf create mode 100644 serve/tf/static/fonts/KaTeX_Main-Bold.a9382e25.woff2 create mode 100644 serve/tf/static/fonts/KaTeX_Main-BoldItalic.52fb39b0.ttf create mode 100644 serve/tf/static/fonts/KaTeX_Main-BoldItalic.5f875f98.woff create mode 100644 serve/tf/static/fonts/KaTeX_Main-BoldItalic.d8737343.woff2 create mode 100644 serve/tf/static/fonts/KaTeX_Main-Italic.39349e0a.ttf create mode 100644 serve/tf/static/fonts/KaTeX_Main-Italic.65297062.woff2 create mode 100644 serve/tf/static/fonts/KaTeX_Main-Italic.8ffd28f6.woff create mode 100644 serve/tf/static/fonts/KaTeX_Main-Regular.818582da.ttf create mode 100644 serve/tf/static/fonts/KaTeX_Main-Regular.f1cdb692.woff create mode 100644 serve/tf/static/fonts/KaTeX_Main-Regular.f8a7f19f.woff2 create mode 100644 serve/tf/static/fonts/KaTeX_Math-BoldItalic.1320454d.woff2 create mode 100644 serve/tf/static/fonts/KaTeX_Math-BoldItalic.48155e43.woff create mode 100644 serve/tf/static/fonts/KaTeX_Math-BoldItalic.6589c4f1.ttf create mode 100644 serve/tf/static/fonts/KaTeX_Math-Italic.d8b7a801.woff2 create mode 100644 serve/tf/static/fonts/KaTeX_Math-Italic.ed7aea12.woff create mode 100644 serve/tf/static/fonts/KaTeX_Math-Italic.fe5ed587.ttf create mode 100644 serve/tf/static/fonts/KaTeX_SansSerif-Bold.0e897d27.woff create mode 100644 serve/tf/static/fonts/KaTeX_SansSerif-Bold.ad546b47.woff2 create mode 100644 serve/tf/static/fonts/KaTeX_SansSerif-Bold.f2ac7312.ttf create mode 100644 serve/tf/static/fonts/KaTeX_SansSerif-Italic.e934cbc8.woff2 create mode 100644 serve/tf/static/fonts/KaTeX_SansSerif-Italic.ef725de5.woff create mode 100644 serve/tf/static/fonts/KaTeX_SansSerif-Italic.f60b4a34.ttf create mode 100644 serve/tf/static/fonts/KaTeX_SansSerif-Regular.1ac3ed6e.woff2 create mode 100644 serve/tf/static/fonts/KaTeX_SansSerif-Regular.3243452e.ttf create mode 100644 serve/tf/static/fonts/KaTeX_SansSerif-Regular.5f8637ee.woff create mode 100644 serve/tf/static/fonts/KaTeX_Script-Regular.1b3161eb.woff2 create mode 100644 serve/tf/static/fonts/KaTeX_Script-Regular.a189c37d.ttf create mode 100644 serve/tf/static/fonts/KaTeX_Script-Regular.a82fa2a7.woff create mode 100644 serve/tf/static/fonts/KaTeX_Size1-Regular.0d8d9204.ttf create mode 100644 serve/tf/static/fonts/KaTeX_Size1-Regular.4788ba5b.woff create mode 100644 serve/tf/static/fonts/KaTeX_Size1-Regular.82ef26dc.woff2 create mode 100644 serve/tf/static/fonts/KaTeX_Size2-Regular.1fdda0e5.ttf create mode 100644 serve/tf/static/fonts/KaTeX_Size2-Regular.95a1da91.woff2 create mode 100644 serve/tf/static/fonts/KaTeX_Size2-Regular.b0628bfd.woff create mode 100644 serve/tf/static/fonts/KaTeX_Size3-Regular.4de844d4.woff create mode 100644 serve/tf/static/fonts/KaTeX_Size3-Regular.963af864.ttf create mode 100644 serve/tf/static/fonts/KaTeX_Size4-Regular.27a23ee6.ttf create mode 100644 serve/tf/static/fonts/KaTeX_Size4-Regular.3045a61f.woff create mode 100644 serve/tf/static/fonts/KaTeX_Size4-Regular.61522cd3.woff2 create mode 100644 serve/tf/static/fonts/KaTeX_Typewriter-Regular.0e046058.woff create mode 100644 serve/tf/static/fonts/KaTeX_Typewriter-Regular.6bf42875.ttf create mode 100644 serve/tf/static/fonts/KaTeX_Typewriter-Regular.b8b8393d.woff2 create mode 100644 serve/tf/static/fonts/font_1456030_mvh913k905.4697711c.ttf create mode 100644 serve/tf/static/fonts/font_1456030_mvh913k905.59b43a90.woff create mode 100644 serve/tf/static/fonts/font_1456030_mvh913k905.854b82bf.woff2 create mode 100644 serve/tf/static/fonts/icomoon.068c5807.eot create mode 100644 serve/tf/static/fonts/icomoon.9613532e.woff create mode 100644 serve/tf/static/fonts/icomoon.a5ecb97c.ttf create mode 100644 serve/tf/static/fonts/iconfont.52434ae6.woff2 create mode 100644 serve/tf/static/fonts/iconfont.96dd6179.woff create mode 100644 serve/tf/static/fonts/iconfont.f348f3f2.ttf create mode 100644 serve/tf/static/img/icomoon.60880589.svg create mode 100644 serve/tf/static/img/logo.9652507e.png create mode 100644 serve/tf/static/js/app.dee09f7c.js create mode 100644 serve/tf/static/js/app.dee09f7c.js.gz create mode 100644 serve/tf/static/js/chunk-19400648.05b336fb.js create mode 100644 serve/tf/static/js/chunk-2d0f0f76.678638ec.js create mode 100644 serve/tf/static/js/chunk-3b4e252e.63a4c246.js create mode 100644 serve/tf/static/js/chunk-488247af.833d2082.js create mode 100644 serve/tf/static/js/chunk-580ca977.fadf558e.js create mode 100644 serve/tf/static/js/chunk-580ca977.fadf558e.js.gz create mode 100644 serve/tf/static/js/chunk-6a5b4dbe.21157920.js create mode 100644 serve/tf/static/js/chunk-6a5b4dbe.21157920.js.gz create mode 100644 serve/tf/static/js/chunk-75e685f5.72a01e0a.js create mode 100644 serve/tf/static/js/chunk-7a26dadf.e80e6a6f.js create mode 100644 serve/tf/static/js/chunk-7a26dadf.e80e6a6f.js.gz create mode 100644 serve/tf/static/js/chunk-83a57bd6.1aba2685.js create mode 100644 serve/tf/static/js/chunk-83a57bd6.1aba2685.js.gz create mode 100644 serve/tf/static/js/chunk-98971e2c.256bbe91.js create mode 100644 serve/tf/static/js/chunk-98971e2c.256bbe91.js.gz create mode 100644 serve/tf/static/js/chunk-vendors.ac44f027.js create mode 100644 serve/tf/static/js/chunk-vendors.ac44f027.js.gz create mode 100644 serve/tools/data.js create mode 100644 serve/tools/trans-class.js create mode 100644 serve/tsconfig.build.json create mode 100644 serve/tsconfig.json create mode 100644 serve/types/index.d.ts create mode 100644 serve/types/prototype.d.ts diff --git a/editor/.browserslistrc b/editor/.browserslistrc new file mode 100644 index 0000000..dc3bc09 --- /dev/null +++ b/editor/.browserslistrc @@ -0,0 +1,4 @@ +> 1% +last 2 versions +not dead +not ie 11 diff --git a/editor/.env b/editor/.env new file mode 100644 index 0000000..7fbda51 --- /dev/null +++ b/editor/.env @@ -0,0 +1,8 @@ +# 页面标题 +VUE_APP_TITLE = 'haoque' + +# 开发环境配置 +ENV = 'development' + +# 开发环境 +VUE_APP_BASE_URL = '/api' diff --git a/editor/.eslintrc.js b/editor/.eslintrc.js new file mode 100644 index 0000000..244c371 --- /dev/null +++ b/editor/.eslintrc.js @@ -0,0 +1,35 @@ +module.exports = { + root: true, + env: { + node: true, + }, + extends: ['plugin:vue/vue3-essential', 'eslint:recommended', '@vue/typescript/recommended', 'plugin:prettier/recommended'], + parserOptions: { + ecmaVersion: 2020, + }, + rules: { + 'prettier/prettier': 'off', + '@typescript-eslint/no-namespace': 'off', + 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', + 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', + 'generator-star-spacing': 'off', + 'no-mixed-operators': 0, + 'no-irregular-whitespace': 0, + 'no-empty': 0, + 'space-before-function-paren': 0, + 'no-multi-spaces': 0, + 'no-unused-vars': 0, + 'vue/attribute-hyphenation': 0, + 'vue/html-self-closing': 0, + 'vue/component-name-in-template-casing': 0, + 'vue/html-closing-bracket-spacing': 0, + 'vue/singleline-html-element-content-newline': 0, + 'vue/no-unused-components': 0, + 'vue/multiline-html-element-content-newline': 0, + 'vue/html-closing-bracket-newline': 0, + 'vue/no-parsing-error': 0, + 'no-mixed-spaces-and-tabs': [0], + 'vue/no-unused-vars': 0, + 'vue/multi-word-component-names': 'off', + }, +}; diff --git a/editor/.gitignore b/editor/.gitignore new file mode 100644 index 0000000..403adbc --- /dev/null +++ b/editor/.gitignore @@ -0,0 +1,23 @@ +.DS_Store +node_modules +/dist + + +# local env files +.env.local +.env.*.local + +# Log files +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/editor/.prettierrc.js b/editor/.prettierrc.js new file mode 100644 index 0000000..00bdd3b --- /dev/null +++ b/editor/.prettierrc.js @@ -0,0 +1,8 @@ +module.exports = { + printWidth: 200, + tabWidth: 2, + semi: true, + singleQuote: true, + bracketSpacing: true, + arrowParens: 'avoid', +}; diff --git a/editor/README.md b/editor/README.md new file mode 100644 index 0000000..1e77040 --- /dev/null +++ b/editor/README.md @@ -0,0 +1,24 @@ +# am-editor-vue3 + +## Project setup +``` +yarn install +``` + +### Compiles and hot-reloads for development +``` +yarn serve +``` + +### Compiles and minifies for production +``` +yarn build +``` + +### Lints and fixes files +``` +yarn lint +``` + +### Customize configuration +See [Configuration Reference](https://cli.vuejs.org/config/). diff --git a/editor/babel.config.js b/editor/babel.config.js new file mode 100644 index 0000000..078c005 --- /dev/null +++ b/editor/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: ['@vue/cli-plugin-babel/preset'], +}; diff --git a/editor/package.json b/editor/package.json new file mode 100644 index 0000000..900d41f --- /dev/null +++ b/editor/package.json @@ -0,0 +1,94 @@ +{ + "name": "Text-Editor", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "vue-cli-service serve", + "build": "vue-cli-service build", + "lint": "vue-cli-service lint" + }, + "dependencies": { + "@ant-design/icons-vue": "^6.1.0", + "@aomao/engine": "^2.10.21", + "@aomao/plugin-alignment": "^2.10.0", + "@aomao/plugin-backcolor": "^2.10.0", + "@aomao/plugin-bold": "^2.10.0", + "@aomao/plugin-code": "^2.10.0", + "@aomao/plugin-codeblock-vue": "^2.10.0", + "@aomao/plugin-embed": "^2.10.1", + "@aomao/plugin-file": "^2.10.0", + "@aomao/plugin-fontcolor": "^2.10.0", + "@aomao/plugin-fontfamily": "^2.10.0", + "@aomao/plugin-fontsize": "^2.10.0", + "@aomao/plugin-heading": "^2.10.0", + "@aomao/plugin-hr": "^2.10.1", + "@aomao/plugin-image": "^2.10.0", + "@aomao/plugin-indent": "^2.10.0", + "@aomao/plugin-italic": "^2.10.0", + "@aomao/plugin-line-height": "^2.10.1", + "@aomao/plugin-link-vue": "^2.10.1", + "@aomao/plugin-mark": "^2.10.0", + "@aomao/plugin-mark-range": "^2.10.3", + "@aomao/plugin-math": "^2.10.0", + "@aomao/plugin-mention": "^2.10.3", + "@aomao/plugin-orderedlist": "^2.10.0", + "@aomao/plugin-paintformat": "^2.10.0", + "@aomao/plugin-quote": "^2.10.0", + "@aomao/plugin-redo": "^2.10.0", + "@aomao/plugin-removeformat": "^2.10.0", + "@aomao/plugin-selectall": "^2.10.0", + "@aomao/plugin-status": "^2.10.0", + "@aomao/plugin-strikethrough": "^2.10.0", + "@aomao/plugin-sub": "^2.10.1", + "@aomao/plugin-sup": "^2.10.1", + "@aomao/plugin-table": "^2.10.8", + "@aomao/plugin-tasklist": "^2.10.0", + "@aomao/plugin-underline": "^2.10.0", + "@aomao/plugin-undo": "^2.10.0", + "@aomao/plugin-unorderedlist": "^2.10.0", + "@aomao/plugin-video": "^2.10.0", + "@aomao/toolbar-vue": "^2.10.3", + "ant-design-vue": "^3.2.12", + "axios": "^1.5.0", + "core-js": "^3.8.3", + "cropperjs": "^1.6.0", + "embed-drawio": "^0.0.15", + "html2canvas": "^1.4.1", + "katex": "^0.16.8", + "lodash": "^4.17.21", + "markdown-it": "^13.0.1", + "markdown-it-container": "^3.0.0", + "moment": "^2.29.4", + "photoswipe": "4.1.3", + "qiankun": "^3.0.0-alpha.1", + "uuid": "^9.0.0", + "vue": "^3.2.13", + "vue-class-component": "^8.0.0-0", + "vue-router": "^4.0.3", + "vuex": "^4.0.0" + }, + "devDependencies": { + "@types/katex": "^0.16.2", + "@types/lodash": "^4.14.197", + "@types/markdown-it": "^13.0.0", + "@types/markdown-it-container": "^2.0.6", + "@types/photoswipe": "4.1.1", + "@typescript-eslint/eslint-plugin": "^5.4.0", + "@typescript-eslint/parser": "^5.4.0", + "@vue/cli-plugin-babel": "~5.0.0", + "@vue/cli-plugin-eslint": "~5.0.0", + "@vue/cli-plugin-router": "~5.0.0", + "@vue/cli-plugin-typescript": "~5.0.0", + "@vue/cli-plugin-vuex": "~5.0.0", + "@vue/cli-service": "~5.0.0", + "@vue/eslint-config-typescript": "^9.1.0", + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-vue": "^8.0.3", + "less": "^3.0.4", + "less-loader": "^5.0.0", + "prettier": "^2.4.1", + "typescript": "~4.5.5" + } +} diff --git a/editor/public/favicon.ico b/editor/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..df36fcfb72584e00488330b560ebcf34a41c64c2 GIT binary patch literal 4286 zcmds*O-Phc6o&64GDVCEQHxsW(p4>LW*W<827=Unuo8sGpRux(DN@jWP-e29Wl%wj zY84_aq9}^Am9-cWTD5GGEo#+5Fi2wX_P*bo+xO!)p*7B;iKlbFd(U~_d(U?#hLj56 zPhFkj-|A6~Qk#@g^#D^U0XT1cu=c-vu1+SElX9NR;kzAUV(q0|dl0|%h|dI$%VICy zJnu2^L*Te9JrJMGh%-P79CL0}dq92RGU6gI{v2~|)p}sG5x0U*z<8U;Ij*hB9z?ei z@g6Xq-pDoPl=MANPiR7%172VA%r)kevtV-_5H*QJKFmd;8yA$98zCxBZYXTNZ#QFk2(TX0;Y2dt&WitL#$96|gJY=3xX zpCoi|YNzgO3R`f@IiEeSmKrPSf#h#Qd<$%Ej^RIeeYfsxhPMOG`S`Pz8q``=511zm zAm)MX5AV^5xIWPyEu7u>qYs?pn$I4nL9J!=K=SGlKLXpE<5x+2cDTXq?brj?n6sp= zphe9;_JHf40^9~}9i08r{XM$7HB!`{Ys~TK0kx<}ZQng`UPvH*11|q7&l9?@FQz;8 zx!=3<4seY*%=OlbCbcae?5^V_}*K>Uo6ZWV8mTyE^B=DKy7-sdLYkR5Z?paTgK-zyIkKjIcpyO z{+uIt&YSa_$QnN_@t~L014dyK(fOOo+W*MIxbA6Ndgr=Y!f#Tokqv}n<7-9qfHkc3 z=>a|HWqcX8fzQCT=dqVbogRq!-S>H%yA{1w#2Pn;=e>JiEj7Hl;zdt-2f+j2%DeVD zsW0Ab)ZK@0cIW%W7z}H{&~yGhn~D;aiP4=;m-HCo`BEI+Kd6 z={Xwx{TKxD#iCLfl2vQGDitKtN>z|-AdCN|$jTFDg0m3O`WLD4_s#$S literal 0 HcmV?d00001 diff --git a/editor/public/index.html b/editor/public/index.html new file mode 100644 index 0000000..3e5a139 --- /dev/null +++ b/editor/public/index.html @@ -0,0 +1,17 @@ + + + + + + + + <%= htmlWebpackPlugin.options.title %> + + + +
+ + + diff --git a/editor/src/App.vue b/editor/src/App.vue new file mode 100644 index 0000000..60a5b67 --- /dev/null +++ b/editor/src/App.vue @@ -0,0 +1,27 @@ + + + + + diff --git a/editor/src/apis/manual.ts b/editor/src/apis/manual.ts new file mode 100644 index 0000000..bc8bb1c --- /dev/null +++ b/editor/src/apis/manual.ts @@ -0,0 +1,31 @@ +import httpRequest from "@/utils/request"; + +// 手册相关 +const manualUrl = '/manuals-manage'; +export const getManualListApi = (classify?: number) => { + if (classify) { + return httpRequest.get(`${manualUrl}/bycla/${classify}`); + } else { + return httpRequest.get(manualUrl); + } +}; +export const getManualInfoApi = (id: string) => httpRequest.get(`${manualUrl}/${id}`); +export const addManualApi = (data: any) => httpRequest.post(manualUrl, data); +export const editManualApi = (id: string, data: any) => httpRequest.patch(`${manualUrl}/${id}`, data); +export const deleteManualApi = (data: any) => httpRequest.post(`${manualUrl}/portion`, data); + +// 手册分类相关 +const manualClassifyUrl = '/manuals-manage/classify'; +export const getManualClassifyListApi = (params?: any) => httpRequest.get(manualClassifyUrl, params); +export const getManualClassifyInfoApi = (params: any) => httpRequest.get(manualClassifyUrl, params); +export const addManualClassifyApi = (data: any) => httpRequest.post(manualClassifyUrl, data); +export const editManualClassifyApi = (data: any) => httpRequest.post(`${manualClassifyUrl}/editor`, data); +export const deleteManualClassifyApi = (id: number) => httpRequest.delete(`${manualClassifyUrl}/${id}`); + +// 手册文章 +const manualArticleUrl = '/article-manage'; +export const getManualArticleListApi = (id: number) => httpRequest.get(`${manualArticleUrl}/manuals/${id}`); +export const getManualArticleInfoApi = (id: number) => httpRequest.get(`${manualArticleUrl}/${id}`); +export const addManualArticleApi = (data: any) => httpRequest.post(manualArticleUrl, data); +export const editManualArticleApi = (data: any) => httpRequest.patch(manualArticleUrl, data); +export const deleteManualArticleApi = (data: any) => httpRequest.post(`${manualArticleUrl}/portion`, data); diff --git a/editor/src/assets/iconfont/iconfont.css b/editor/src/assets/iconfont/iconfont.css new file mode 100644 index 0000000..b4a8b8c --- /dev/null +++ b/editor/src/assets/iconfont/iconfont.css @@ -0,0 +1,39 @@ +@font-face { + font-family: "iconfont"; /* Project id 4246922 */ + src: url('iconfont.woff2?t=1694426600917') format('woff2'), + url('iconfont.woff?t=1694426600917') format('woff'), + url('iconfont.ttf?t=1694426600917') format('truetype'); +} + +.iconfont { + font-family: "iconfont" !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-rili:before { + content: "\e600"; +} + +.icon-shangpinpaixu:before { + content: "\e62a"; +} + +.icon-date-asce:before { + content: "\e62b"; +} + +.icon-date-desc:before { + content: "\e618"; +} + +.icon-tubiao:before { + content: "\e60c"; +} + +.icon-list:before { + content: "\e613"; +} + diff --git a/editor/src/assets/iconfont/iconfont.js b/editor/src/assets/iconfont/iconfont.js new file mode 100644 index 0000000..93b9e33 --- /dev/null +++ b/editor/src/assets/iconfont/iconfont.js @@ -0,0 +1 @@ +window._iconfont_svg_string_4246922='',function(e){var t=(t=document.getElementsByTagName("script"))[t.length-1],a=t.getAttribute("data-injectcss"),t=t.getAttribute("data-disable-injectsvg");if(!t){var n,o,i,c,l,d=function(t,a){a.parentNode.insertBefore(t,a)};if(a&&!e.__iconfont__svg__cssinject__){e.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(t){console&&console.log(t)}}n=function(){var t,a=document.createElement("div");a.innerHTML=e._iconfont_svg_string_4246922,(a=a.getElementsByTagName("svg")[0])&&(a.setAttribute("aria-hidden","true"),a.style.position="absolute",a.style.width=0,a.style.height=0,a.style.overflow="hidden",a=a,(t=document.body).firstChild?d(a,t.firstChild):t.appendChild(a))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(n,0):(o=function(){document.removeEventListener("DOMContentLoaded",o,!1),n()},document.addEventListener("DOMContentLoaded",o,!1)):document.attachEvent&&(i=n,c=e.document,l=!1,s(),c.onreadystatechange=function(){"complete"==c.readyState&&(c.onreadystatechange=null,h())})}function h(){l||(l=!0,i())}function s(){try{c.documentElement.doScroll("left")}catch(t){return void setTimeout(s,50)}h()}}(window); \ No newline at end of file diff --git a/editor/src/assets/iconfont/iconfont.json b/editor/src/assets/iconfont/iconfont.json new file mode 100644 index 0000000..aa3ed53 --- /dev/null +++ b/editor/src/assets/iconfont/iconfont.json @@ -0,0 +1,51 @@ +{ + "id": "4246922", + "name": "haoque", + "font_family": "iconfont", + "css_prefix_text": "icon-", + "description": "", + "glyphs": [ + { + "icon_id": "1718350", + "name": "日历28", + "font_class": "rili", + "unicode": "e600", + "unicode_decimal": 58880 + }, + { + "icon_id": "7730889", + "name": "商品排序", + "font_class": "shangpinpaixu", + "unicode": "e62a", + "unicode_decimal": 58922 + }, + { + "icon_id": "37319803", + "name": "按时间排序-升序", + "font_class": "date-asce", + "unicode": "e62b", + "unicode_decimal": 58923 + }, + { + "icon_id": "13410868", + "name": "按时间排序-降序", + "font_class": "date-desc", + "unicode": "e618", + "unicode_decimal": 58904 + }, + { + "icon_id": "582661", + "name": "ioc_table", + "font_class": "tubiao", + "unicode": "e60c", + "unicode_decimal": 58892 + }, + { + "icon_id": "1000805", + "name": "list", + "font_class": "list", + "unicode": "e613", + "unicode_decimal": 58899 + } + ] +} diff --git a/editor/src/assets/iconfont/iconfont.ttf b/editor/src/assets/iconfont/iconfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..0b64abf4811dfa43c929f6a4efd58356a9afc728 GIT binary patch literal 3188 zcmd^BU2G#)6+UNXJma6tIG!2*C-&Ih*lCj5$;5F&lTA^V?sm)aqwcaG5wY<)lh|9w zwruaR3({_&DpggqEiZ`22#P33MFon~NFc#erHY42s31jI*gjw+7KtAT@xW%oxt?*l zo0g}2ga>2j!FYqy4fcKAmyZXU+ za6>?Ti+DG1OiGPelHfJ)^f@N=(vRHqd3)Xq)36NOCr+L@2THx%Z9oic^Y;Ax80)o- zGTFChko4Frk_16?q=EqW3b-iN7de*2m+cYnfQht{0~b%yk7%D=e{v0e)q~z-KOeyF z!PiLb!EA8M;r}9hBS#n;h;EQGDEtU&KAQ1fEk*e z#I!hysY68eFc{DiIGZwQeV~{}O&2q%bapO~nj^i^B9g&HG)u=Zb%D#|qLI;=;YW>RA?a2Z zve|_(;qKi`WT~%!+obMtFGka-$l;;L z$0tmmuBpMuNGdoyX6SlmW<*Vf6^yFB`h>nl={UhU1W6F)5vFOyy%v5yDgYK>z zH)ApL8^gw8Dz(Us6N(B*lz8RtzrC8E%e=>@3Y_GAUQ{(jkld&DKPBr_OH_Bx zX_<#b_$1Y=1#Q|-I+w>>wm83xR1e_B3bm!aCgU`ly7#WE;j?6Xz3;(;|AgLoyRRI=z5V(J znET=_*7kSzSli$2bL^aD@5Vd)pZKK*e3RE;Wtl6?1f4C7T7j@ zWU)p2M}ggZvhbXP5r*N14(33C9S8Fyf7`(V;om#h1tIvegGG|>JJ<{7QFgEovRFCM zw#r>=&A52Ys8yPc^=6|x7@x5=F4xQL0XrDaTkTG**)V4E(*w~dt6{YVW;$0k%x-tx zSZ_Bsjgw^0s@I!FtKGb0Rl50Vx7&KGP+)$TJYQ*U!U;OUEGR>l`ZX}%B3z@sMz3KL z8g$WQefLBBGo-x%m!VE8+aH=ey83+opzM$yvu3~y6H;8ATpYO-H$u5|7WB1&T zY7%nDqkt|ts3p3Q3hi3GCU>gk#zw2wXq9VMFMHPLDLh*4R4f~uIql@=f1D^eV*t2=MKc0n*$I44DKF)|KLPY|HBXJ z{|}4HcE$hz;Q>`$5Vq0@vccHPs%TI%1^EGR7%|HoA{kEswOEkT!Sj`+M&zi6PmntR zKtT!i5CGt{Vs9tZJg?*3z`6t!RY7RUk|=$ipa^Q?AeROq!#x9w@g!5hKJYD&3xJT6 z1e1>+5xj>u*hT07a9e~I8BYi2B94Om7zk;II8f`0Ctn9O9dHjraK4AoeM}|A&z}nZ z7widOLAXl|kFL>~M4THAH$zavRT6PGZ*1`5XP#RrbBL(~96lUK#h~*}%c<+7-G+6+ zdaJ@o0@Ci0u~6WoF5D70c#Fs5m~3`0n?1szM&G_&c+6P+sk<8CJrIwsM62WA5K@H# zk~{x2X4u_#>n)qj4v8r|EZ-Mf6w3>Es3Q~U%$2mZLmg^J7`BRdgb}vw7X9G9TX^!2 zL-)Km%dIY_NMiutZ9~Z85b+xn#c_!fU)M*>uQjH$+-&?j^=FB(OM?B%N8US=?Jnqo zpLZ$h{jNE)2Hv})-Fv%_#q>_TYzY>Y7)npf(TpRL&b3^wmCF=tb&%|(GmCAledbJ$ z+!_q%3b-^`*Bv0)yj^9@{3Lv7N#=shV<#@-l7ty3R7SC$hvq~&b5As4GJsJltpfLz zRN{gjFMpPjs>Ln9Ox0Ow@{-k|EJ5P<@BH{aA9)8Sp&z9RxoF{J@lZRqd;Q*9(OF9rbK3E|cLcEp?f)q^HJM>#KswZAX<~ZL z_cgvlnMM|q-CJ)3h!eMtp0Mxy!exV@o^OtRqE@`we+$*rKdW{Y;jSay>JxQ@5=9E& z?GPC`=fZ7({uMpPALD9eUu^Gk6Lt1yXK>wR=c{IaHQP94d?F9?=|*1gG_(7=`bU>n zXIX`^{!oePn5eg=m3^LkVy5x4_jA@wLa6t3U6n>DmFg0aCFm!MokdjYkw3iExu;|| zBhaO$CEoM!&ua-J&WG%|Q;Y#Z4SsU1H{DKkId{G!;K`zv;)-mCqnCrY6nxaZxTboD zrD10~{VHN)b9fUaabHc6w|_oD8I}D}FAe)ys~Vx#zOgN{F+1L|_c9k%@I66KXrXx} zj#dR1y9&rF6VY9@$?gTVf#>gVo?PX~NVxk-d6)2W(79S2n-R|OZrq)6pV(cO)&%cf zcDjqEEnzwR(IafZs1JanCX;zP zCtolQV_>QsVp9(`Ov_d3(|=MDZ3XAWvt7E2gO>=eiU>oUxjKDbJ#Ht>IkJ6%E@pWg z8zqhkG5eJ#LAnDI6J)L>!eV+R@x^1JGrh05WCj|D%={Qlx@+J3X5w{Ya2Vn+yj$C> zWxcH-Lv`Bf$(a5g-o!tr@L_0@f5AqxeOlE}1wp*@x6g`OA48mm8-vRioR_joy*p23 z?R1v^)*7m~j=a{O+@Hxb4Ewh1e5Vejzx{K1HAxJW#1>81k{An6xH3%^=YQIwpb$;6 z%wx>?HAbg=aPHpJOxBj)s{17}{@JWZjku$Srb`U!9gV&`l{+-xWH#57?eFq$8Z2Ch z_M_b3?t>dx=@00ruEck+iHBxeD9>pCW0yhK>zEnx+@D z04)}x^Tromomz1wB-~#&sX$+ct+cb_W^t^5y|srEA zbK`ddUjl6wzYw*aQ-#ivf$x8qy1;Kr5gV|Mf$hhhrO?13=Fk_Q9i&$o;ku z1R$;t83g+?()4`W4TTs5ZK+(D@@F4tkn9!Yg*m&KmaL2S9^T`w`Cw5$c16{4pkuMNj{D%b;-+m|4rjYrpBIIZ_;8wK67Vz4@cc>kiqPbfrf2eM;7kj7sb+v5LaD ze9GM)WfYhyUBtJV*d>r3*7=ALh!=TOgsReJI_f(Ukng?Xvvnqy0g9Iz2zw4Eg8g)}dmL9u#rO%DZ? z&5f7z<<<$fR8m!z8z$2hj68*rxWdy6BGUvPEj7n?9C^vqjOwxd=PFz))`_%F!!FeK zu#Vkis6eR$S5G`1Y7hF|I{B3AvJH)@+8eBjvKC;%j>2WYKfl6s2LJ#7 literal 0 HcmV?d00001 diff --git a/editor/src/assets/iconfont/iconfont.woff2 b/editor/src/assets/iconfont/iconfont.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..3f18dbf9af59f237d21c7a3f3576022925049d2e GIT binary patch literal 1512 zcmVJ+9HUcCA919Qt1Rw>3X9s2*whB>{FHVr`XMx>VUBuYNvW#E< zC*f@%>=XrIUkvk?j`8sQe*ath_;*CT(})h-k$!U`a6hMmVO8j|wUGofc|lS%)HoI@{0W`{+^ z=pSCy6iFr;QWxRATG99k42qd11XQ7kgSTrkKn0o}P=clp&C}rQ7c65R7XL}tnDp*b z+9ge$IHw)nJ~44h;_S0y6zqHdbH~0N1GhvW!kQH)3_^ThW*0*2m>U8M zz2f$hb+$mXwU-#rr2tgT`P>pu`O1XZA|7#%oSwaM<**38^R^>QpKbb?ut=*6ueOS2jdR!%N8+}i!tK>En~ET$M)XWtF&I?3L zQ`+qN(@V1E4=^=s$f$De#JOa#f%w>o!Q^t$=Nm+u(xcRhkHL*j z6G|wQ7uV>{%w)@n`5GA+T^BG8{n5MS)a2M1nQVF0qgs9|<5=wYU{{PHCfAjj*(Ikc z&psH$CC_6+Y1vH(m6?d5g~!){UL)($&v2e)nIDNnf_{)z{ATOW{+EbQ=g9Q}Dt z`l7+ip>|XAFyy8R2L4JKHvi)VmQWbeRAN{_FCWkT4X1|PYl3`N(YzYRe=U2B-|gvZ z+2X&guT3H8wn8@Su^0Cds1}@oW;hG;jykz~!CKakD04XALqJ&N3zB7TAY0|zUfe%W zZSV)pD*rI=O3d7B!GRD*4mn{C*$Zf4TB!{Rw|j%CtGTNxc|hkiiXaK&V$~V8=r(bR ze^e=v1nEozr~SuaT^q2@lqe`Hi0oz*$+}Mog=yFk2XO@DK;{L|1fwP0*p&Z$H;~e) zagJ$!NOgR`X!{@$iW&AZwPp2rB(1$iN{T2|0wWz&Ho)nSK-PnD1K9e~5)!lYP_ag- zwJB0#Nv<01UvD1dSujt&PmdHiO4Ml4V<=B`t!I3}TV-`?xqj{@@*7YQa4#^vLylgI OQkQ9NhHLGKF#rIh?bmSt literal 0 HcmV?d00001 diff --git a/editor/src/assets/styles/custom-theme.less b/editor/src/assets/styles/custom-theme.less new file mode 100644 index 0000000..3f4b0f4 --- /dev/null +++ b/editor/src/assets/styles/custom-theme.less @@ -0,0 +1,5 @@ +@primary-color: #11a6b4; // #1890ff; // 全局主色 +@link-color: #11a6b4; // 链接色 +@success-color: #34cb80; // #52c41a; // 成功色 +@warning-color: #faad14; // 警告色 +@error-color: #f05b59; // #f5222d; // 错误色 \ No newline at end of file diff --git a/editor/src/assets/styles/index.less b/editor/src/assets/styles/index.less new file mode 100644 index 0000000..229e3a7 --- /dev/null +++ b/editor/src/assets/styles/index.less @@ -0,0 +1,2 @@ +@import '~ant-design-vue/dist/antd.less'; // 引入官方提供的 less 样式入口文件 +@import 'custom-theme.less'; // 用于覆盖上面定义的变量 \ No newline at end of file diff --git a/editor/src/components/editor/config.ts b/editor/src/components/editor/config.ts new file mode 100644 index 0000000..0c02cc2 --- /dev/null +++ b/editor/src/components/editor/config.ts @@ -0,0 +1,301 @@ +import { PluginEntry, CardEntry, PluginOptions, NodeInterface, RangeInterface, EngineInterface } from '@aomao/engine'; +//引入插件 begin +import Redo from '@aomao/plugin-redo'; +import Undo from '@aomao/plugin-undo'; +import Bold from '@aomao/plugin-bold'; +import Code from '@aomao/plugin-code'; +import Backcolor from '@aomao/plugin-backcolor'; +import Fontcolor from '@aomao/plugin-fontcolor'; +import Fontsize from '@aomao/plugin-fontsize'; +import Italic from '@aomao/plugin-italic'; +import Underline from '@aomao/plugin-underline'; +import Hr, { HrComponent } from '@aomao/plugin-hr'; +import Tasklist, { CheckboxComponent } from '@aomao/plugin-tasklist'; +import Orderedlist from '@aomao/plugin-orderedlist'; +import Unorderedlist from '@aomao/plugin-unorderedlist'; +import Indent from '@aomao/plugin-indent'; +import Heading from '@aomao/plugin-heading'; +import Strikethrough from '@aomao/plugin-strikethrough'; +import Sub from '@aomao/plugin-sub'; +import Sup from '@aomao/plugin-sup'; +import Alignment from '@aomao/plugin-alignment'; +import Mark from '@aomao/plugin-mark'; +import Quote from '@aomao/plugin-quote'; +import PaintFormat from '@aomao/plugin-paintformat'; +import RemoveFormat from '@aomao/plugin-removeformat'; +import SelectAll from '@aomao/plugin-selectall'; +import Link from '@aomao/plugin-link-vue'; +import Codeblock, { CodeBlockComponent } from '@aomao/plugin-codeblock-vue'; +import Image, { ImageComponent, ImageUploader } from '@/plugins/image'; // "@aomao/plugin-image"; +import Table, { TableComponent } from '@aomao/plugin-table'; +import File, { FileComponent, FileUploader } from '@aomao/plugin-file'; +import Video, { VideoComponent, VideoUploader } from '@aomao/plugin-video'; +import Math, { MathComponent } from '@/plugins/math'; // "@aomao/plugin-math"; +import Fontfamily from '@aomao/plugin-fontfamily'; +import Status, { StatusComponent } from '@aomao/plugin-status'; +import LineHeight from '@aomao/plugin-line-height'; +import Mention, { MentionComponent } from '@aomao/plugin-mention'; +import Embed, { EmbedComponent } from '@aomao/plugin-embed'; +import MarkRange from '@aomao/plugin-mark-range'; +import Lightblock, { LightblockComponent } from '@/plugins/lightblock'; +import Audio, { AudioComponent, AudioUploader } from '@/plugins/audio'; +import Draw, { DrawComponent } from '@/plugins/draw'; +import Tag, { TagComponent } from '@/plugins/tag'; +import Test, { TestComponent } from '@/plugins/test'; +import { ToolbarPlugin, ToolbarComponent, fontFamilyDefaultData } from '@aomao/toolbar-vue'; + +import { Empty } from 'ant-design-vue'; +import { createApp } from 'vue'; +import Loading from './loading.vue'; +import MentionPopover from './mention-popover.vue'; + +const DOMAIN = process.env.VUE_APP_BASE_URL || ''; +const TOKEN = localStorage.getItem("token"); + +export const plugins: Array = [ + Redo, + Undo, + Bold, + Code, + Backcolor, + Fontcolor, + Fontsize, + Italic, + Underline, + Hr, + Tasklist, + Orderedlist, + Unorderedlist, + Indent, + Heading, + Strikethrough, + Sub, + Sup, + Alignment, + Mark, + Quote, + PaintFormat, + RemoveFormat, + SelectAll, + Link, + Codeblock, + Image, + ImageUploader, + Table, + File, + FileUploader, + Video, + VideoUploader, + Math, + ToolbarPlugin, + Fontfamily, + Status, + LineHeight, + Mention, + Embed, + MarkRange, + Lightblock, + Audio, + AudioUploader, + Draw, + Tag, + Test, +]; + +export const cards: Array = [ + HrComponent, + CheckboxComponent, + CodeBlockComponent, + ImageComponent, + TableComponent, + FileComponent, + VideoComponent, + MathComponent, + ToolbarComponent, + StatusComponent, + MentionComponent, + EmbedComponent, + LightblockComponent, + AudioComponent, + DrawComponent, + TagComponent, + TestComponent, +]; +let engine: EngineInterface | null = null; + +export const onLoad = (e: EngineInterface) => { + engine = e; +}; +export const pluginConfig: { [key: string]: PluginOptions } = { + [MarkRange.pluginName]: { + //标记类型集合 + keys: ['mark'], + //标记数据更新后触发 + onChange: (addIds: { [key: string]: Array }, removeIds: { [key: string]: Array }) => { + // 新增的标记 + const commentAddIds = addIds['comment'] || []; + // 删除的标记 + const commentRemoveIds = removeIds['comment'] || []; + }, + //光标改变时触发 + onSelect: (range: RangeInterface, selectInfo?: { key: string; id: string }) => { + const { key, id } = selectInfo || {}; + // 移除预览标记 + engine?.command.executeMethod('mark-range', 'action', 'comment', 'revoke'); + if (key === 'mark' && id) { + engine?.command.executeMethod('mark-range', 'action', key, 'preview', id); + } + }, + }, + [Italic.pluginName]: { + // 默认为 _ 下划线,这里修改为单个 * 号 + markdown: '*', + }, + [ImageUploader.pluginName]: { + file: { + action: `${DOMAIN}/resource/upload`, // `${DOMAIN}/upload/image`, + crossOrigin: false, + headers: { Authorization: `${TOKEN}` }, + limitSize: 1024 * 1024 * 50, + }, + remote: { + action: `${DOMAIN}/resource/upload`, // `${DOMAIN}/upload/image`, + }, + isRemote: (src: string) => src.indexOf(DOMAIN) < 0, + parse: (response: any) => { + const res: any = {}; + if (response.msg == 'success') { + res.result = true; + res.data = `${DOMAIN}/file-bucket/${response.data.diskname}`; + } else { + res.result = false; + res.data = response.message || response.data.message; + } + return res; + }, + }, + [Image.pluginName]: { + onBeforeRender: (status: string, url: string) => { + if (url.startsWith('data:image/')) return url; + return `${url}`; + }, + }, + [FileUploader.pluginName]: { + action: `${DOMAIN}/resource/upload`, // `${DOMAIN}/upload/file`, + crossOrigin: false, + headers: { Authorization: `${TOKEN}` }, + parse: (response: any) => { + const res: any = {}; + if (response.msg == 'success') { + res.result = true; + res.data = response.data.diskname; + } else { + res.result = false; + res.data = response.message || response.data.message; + } + return res; + }, + }, + [File.pluginName]: { + onBeforeRender: (action: 'download' | 'preview', url: string) => { + return `${DOMAIN}/file-bucket/${url}`; + }, + }, + [VideoUploader.pluginName]: { + action: `${DOMAIN}/resource/upload`, // `${DOMAIN}/upload/video`, + crossOrigin: false, + headers: { Authorization: `${TOKEN}` }, + limitSize: 1024 * 1024 * 50, + parse: (response: any) => { + const res: any = {}; + if (response.msg == 'success') { + res.result = true; + res.data = response.data.diskname; + } else { + res.result = false; + res.data = response.message || response.data.message; + } + return res; + }, + }, + [Video.pluginName]: { + onBeforeRender: (status: string, url: string) => { + return `${DOMAIN}/file-bucket/${url}`; + }, + }, + [AudioUploader.pluginName]: { + action: `${DOMAIN}/resource/upload`, // `${DOMAIN}/upload/video`, + crossOrigin: false, + headers: { Authorization: `${TOKEN}` }, + limitSize: 1024 * 1024 * 50, + parse: (response: any) => { + const res: any = {}; + if (response.msg == 'success') { + res.result = true; + res.data = response.data.diskname; + } else { + res.result = false; + res.data = response.message || response.data.message; + } + return res; + }, + }, + [Audio.pluginName]: { + onBeforeRender: (status: string, url: string) => { + return `${DOMAIN}/file-bucket/${url}`; + }, + }, + [Math.pluginName]: { + action: `${DOMAIN}/latex`, + parse: (res: any) => { + if (res.success) return { result: true, data: res.svg }; + return { result: false, data: '' }; + }, + }, + [Mention.pluginName]: { + action: `${DOMAIN}/user/search`, + onLoading: (root: NodeInterface) => { + const vm = createApp(Loading); + vm.mount(root.get()!); + }, + onEmpty: (root: NodeInterface) => { + const vm = createApp(Empty); + vm.mount(root.get()!); + }, + onClick: (root: NodeInterface, { key, name }: { key: string; name: string }) => { + console.log('mention click:', key, '-', name); + }, + onMouseEnter: (layout: NodeInterface, { name }: { key: string; name: string }) => { + const vm = createApp(MentionPopover, { + name, + }); + vm.mount(layout.get()!); + }, + }, + [Fontsize.pluginName]: { + //配置粘贴后需要过滤的字体大小 + filter: (fontSize: string) => { + return ['12px', '13px', '14px', '15px', '16px', '19px', '22px', '24px', '29px', '32px', '40px', '48px'].indexOf(fontSize) > -1; + }, + }, + [Fontfamily.pluginName]: { + //配置粘贴后需要过滤的字体 + filter: (fontfamily: string) => { + const item = fontFamilyDefaultData.find(item => fontfamily.split(',').some(name => item.value.toLowerCase().indexOf(name.replace(/"/, '').toLowerCase()) > -1)); + return item ? item.value : false; + }, + }, + [LineHeight.pluginName]: { + //配置粘贴后需要过滤的行高 + filter: (lineHeight: string) => { + if (lineHeight === '14px') return '1'; + if (lineHeight === '16px') return '1.15'; + if (lineHeight === '21px') return '1.5'; + if (lineHeight === '28px') return '2'; + if (lineHeight === '35px') return '2.5'; + if (lineHeight === '42px') return '3'; + // 不满足条件就移除掉 + return ['1', '1.15', '1.5', '2', '2.5', '3'].indexOf(lineHeight) > -1; + }, + }, +}; diff --git a/editor/src/components/editor/loading.vue b/editor/src/components/editor/loading.vue new file mode 100644 index 0000000..49f9f9d --- /dev/null +++ b/editor/src/components/editor/loading.vue @@ -0,0 +1,34 @@ + + + + diff --git a/editor/src/components/editor/mention-popover.vue b/editor/src/components/editor/mention-popover.vue new file mode 100644 index 0000000..35d4cb9 --- /dev/null +++ b/editor/src/components/editor/mention-popover.vue @@ -0,0 +1,24 @@ + + + + diff --git a/editor/src/components/editor/outline.vue b/editor/src/components/editor/outline.vue new file mode 100644 index 0000000..3ac4a4f --- /dev/null +++ b/editor/src/components/editor/outline.vue @@ -0,0 +1,205 @@ + + + + diff --git a/editor/src/main.ts b/editor/src/main.ts new file mode 100644 index 0000000..8e92030 --- /dev/null +++ b/editor/src/main.ts @@ -0,0 +1,48 @@ +import './publice-path' +import { createApp, reactive } from 'vue'; +import App from './App.vue'; +import router from './router'; +import store from './store'; +import './assets/styles/index.less'; +import './assets/iconfont/iconfont.css'; + + +let instance: any = null; + + + +function render(props: any = null) { + instance = createApp(App); + const token = JSON.parse(localStorage.getItem("token") || '') + instance.config.globalProperties.$globalVariable = reactive({ + token, + baseUrl: process.env.VUE_APP_BASE_URL, + resourceBaseUrl: `${process.env.VUE_APP_BASE_URL}/file-bucket`, + }); + instance.use(store).use(router); + instance.mount(props ? props.container.querySelector('#app') : '#app') +} +// 独立运行时 +if (!window.__POWERED_BY_QIANKUN__) { + const token = + "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0NTRkYWMxMy1jZjVmLTQ3MTgtOWQ4MC00MjM5MzM5YWVhZDUiLCJhdXRoIjoxLCJiYXNlIjoxLCJvcmciOjEsImlhdCI6MTY5NTA5MDAwNiwiZXhwIjoxNjk1Njk0ODA2fQ.-ub4h04wpIY227ZJbamtuTv_XUjNG7YdX7VN6nMO4W4"; + localStorage.setItem('token', JSON.stringify(token)); + render(); + console.log('独立运行'); +} + +// 抛出qiankun执行依赖----------START +export async function bootstrap() { + console.log('[vue] vue app bootstraped'); +} +// 加载 +export async function mount(props: any) { + render(props); +} +// 注销 +export async function unmount() { + instance?.$destroy?.(); + instance && instance.$el && (instance.$el.innerHTML = '') + instance = null; +} +// 抛出qiankun执行依赖----------END diff --git a/editor/src/plugins/audio/component/index.css b/editor/src/plugins/audio/component/index.css new file mode 100644 index 0000000..7093e00 --- /dev/null +++ b/editor/src/plugins/audio/component/index.css @@ -0,0 +1,86 @@ +[data-card-key="audio"] { + outline: 1px solid #ddd; + border-radius: 54px; +} + .data-audio-content { + position: relative; + height: 54px; + background: #f7f7f7; + } + .data-audio-content audio { + width: 100%; + outline: none; + } + .data-audio-uploading, + .data-audio-uploaded, + .data-audio-error { + border: 1px solid #e6e6e6; + border-radius: 54px; + background: #f6f6f6; + } + .data-audio-done { + height: auto; + border: none; + background: none; + line-height: 0; + } + .data-audio-active { + outline: 1px solid #d9d9d9; + border-radius: 54px; + } + .data-audio-center { + position: absolute; + top: 50%; + margin-top: -48px; + width: 100%; + height: 96px; + } + .data-audio-center .data-audio-icon, + .data-audio-center .data-audio-name, + .data-audio-center .data-audio-message, + .data-audio-center .data-audio-progress, + .data-audio-center .data-audio-transcoding { + text-align: center; + } + .data-audio-center .data-audio-icon { + font-size: 24px; + color: #BFBFBF; + margin-bottom: 12px; + } + .data-audio-center .data-audio-name { + color: #595959; + margin-bottom: 12px; + } + .data-audio-center .data-audio-message { + color: #595959; + } + .data-audio-center .data-audio-anticon { + display: inline-block; + font-style: normal; + vertical-align: -0.125em; + text-align: center; + text-transform: none; + line-height: 0; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + margin-right: 5px; + } + .data-audio-center .data-audio-anticon .data-audio-anticon-spin { + display: inline-block; + -webkit-animation: loadingCircle 1s infinite linear; + animation: loadingCircle 1s infinite linear; + } + .data-audio-center .data-error-icon { + width: 16px; + height: 16px; + display: inline-block; + background: #F5222D; + text-align: center; + font-size: 12px; + color: #ffffff; + padding: 1px 0 0 0; + line-height: 16px; + border-radius: 100%; + vertical-align: middle; + margin: -2px 5px 0 0; + } \ No newline at end of file diff --git a/editor/src/plugins/audio/component/index.ts b/editor/src/plugins/audio/component/index.ts new file mode 100644 index 0000000..f3f9f2c --- /dev/null +++ b/editor/src/plugins/audio/component/index.ts @@ -0,0 +1,375 @@ +import { CardValue, Tooltip } from '@aomao/engine'; +import { $, Card, CardToolbarItemOptions, CardType, escape, getFileSize, isEngine, isMobile, NodeInterface, sanitizeUrl, ToolbarItemOptions } from '@aomao/engine'; +import './index.css'; + +export interface AudioValue extends CardValue { + /** + * 音频唯一标识 + */ + audio_id?: string; + /** + * 音频名称 + */ + name: string; + /** + * 音频地址 + */ + url: string; + /** + * 下载地址 + */ + download?: string; + /** + * 状态 + * uploading 上传中 + * done 上传成功 + */ + status?: 'uploading' | 'transcoding' | 'done' | 'error'; + /** + * 上传进度 + */ + percent?: number; + /** + * 音频大小 + */ + size?: number; + /** + * 错误状态下的错误信息 + */ + message?: string; +} + +class AudioComponent extends Card { + static get cardName() { + return 'audio'; + } + + static get cardType() { + return CardType.BLOCK; + } + + static get autoSelected() { + return false; + } + + private container?: NodeInterface; + + getLocales() { + return this.editor.language.get<{ [key: string]: string }>('audio'); + } + + renderTemplate(value: AudioValue) { + const { name, status, size, message, percent } = value; + const locales = this.getLocales(); + + const icons = { + audio: `
+
`, + spin: ``, + warn: `
`, + error: 'X', + }; + + if (status === 'error') { + return ` +
+
+
+
${escape(name)}
+
+ ${icons.error} + ${message || locales['loadError']} +
+
+
+
`; + } + + const fileSize: string = size ? getFileSize(size) : ''; + + if (status === 'uploading') { + return ` +
+
+
+ ${icons.audio} +
+ ${escape(name)} (${escape(fileSize)}) +
+
+ ${icons.spin} + ${percent || 0}% +
+
+
+
`; + } + const isLoading = typeof status === 'undefined'; + if (status === 'transcoding' || isLoading) { + return ` +
+
+
+ ${icons.audio} +
+ ${escape(name)} (${escape(fileSize)}) +
+
+ ${icons.spin} + ${isLoading ? locales['loading'] : locales['transcoding']}% +
+
+
+
+ `; + } + + return ` +
+
+
+ `; + } + + onBeforeRender = (action: 'query' | 'download' | 'cover', url: string) => { + const audioPlugin = this.editor.plugin.components['audio'] as any; + if (audioPlugin) { + const { onBeforeRender } = audioPlugin['options'] || {}; + if (onBeforeRender) return onBeforeRender(action, url); + } + return url; + }; + + initPlayer() { + const value = this.getValue(); + if (!value) return; + + const url = sanitizeUrl(this.onBeforeRender('query', value.url)); + const audio = document.createElement('audio'); + audio.preload = 'none'; + audio.setAttribute('src', url); + audio.setAttribute('webkit-playsinline', 'webkit-playsinline'); + audio.setAttribute('playsinline', 'playsinline'); + + this.container?.find('.data-audio-content').append(audio); + + audio.oncontextmenu = function () { + return false; + }; + // 一次渲染时序开启 controls 会触发一次内容为空的 window.onerror,疑似 chrome bug + setTimeout(() => { + audio.controls = true; + }, 0); + } + + downloadFile = () => { + const value = this.getValue(); + if (!value?.download) return; + window.open(sanitizeUrl(this.onBeforeRender('download', value.url))); + }; + + toolbar() { + const items: Array = []; + const value = this.getValue(); + if (!value) return items; + const { status, download } = value; + const locale = this.getLocales(); + + if (status === 'done') { + if (download) { + items.push({ + type: 'button', + content: '', + title: locale.download, + onClick: this.downloadFile, + }); + } + + if (isEngine(this.editor) && !this.editor.readonly) { + items.push({ + type: 'copy', + }); + items.push({ + type: 'separator', + }); + } + } + + if (isEngine(this.editor) && !this.editor.readonly) { + items.push({ + type: 'delete', + }); + } + return items; + } + + setProgressPercent(percent: number) { + this.container?.find('.percent').html(`${percent}%`); + } + + onActivate(activated: boolean) { + if (activated) this.container?.addClass('data-audio-active'); + else this.container?.removeClass('data-audio-active'); + } + + checker(audio_id: string, success: (data?: { url: string; name?: string; cover?: string; download?: string; status?: string }) => void, failed: (message: string) => void) { + const { command } = this.editor; + const handle = () => { + command.executeMethod( + 'audio-uploader', + 'query', + audio_id, + (data?: { url: string; name?: string; cover?: string; download?: string; status?: string }) => { + if (data && data.status !== 'done') setTimeout(handle, 3000); + else success(data); + }, + (message: string) => { + failed(message); + } + ); + }; + handle(); + } + + render(): string | void | NodeInterface { + const value = this.getValue(); + if (!value) return; + const center = this.getCenter(); + //先清空卡片内容容器 + center.empty(); + const { command, plugin } = this.editor; + const { audio_id, status } = value; + const locales = this.getLocales(); + //阅读模式 + if (!isEngine(this.editor)) { + if (status === 'done') { + //设置为加载状态 + this.container = $(this.renderTemplate({ ...value, status: undefined })); + const updateValue = (data?: { url: string; name?: string; cover?: string; download?: string }) => { + const newValue: AudioValue = { + ...value, + url: data?.url ? data.url : value.url, + name: data?.name ? data.name : value.name, + download: data?.download ? data.download : value.download, + }; + this.container = $(this.renderTemplate(newValue)); + center.empty(); + center.append(this.container); + this.initPlayer(); + }; + if (plugin.components['audio-uploader']) { + command.executeMethod( + 'audio-uploader', + 'query', + audio_id, + (data?: { url: string; name?: string; cover?: string; download?: string }) => { + updateValue(data); + }, + (error: string) => { + this.container = $( + this.renderTemplate({ + ...value, + status: 'error', + message: error || locales['loadError'], + }) + ); + center.empty(); + center.append(this.container); + } + ); + } else { + updateValue(); + } + return this.container; + } else if (status === 'error') { + return $( + this.renderTemplate({ + ...value, + message: value.message || locales['loadError'], + }) + ); + } + } + //转换中 + else if (status === 'transcoding') { + this.container = $(this.renderTemplate(value)); + if (!audio_id) throw 'audio id is undefined'; + this.checker( + audio_id, + (data?: { url: string; name?: string; cover?: string; download?: string; status?: string }) => { + const newValue: V = { + ...value, + url: data?.url ? data.url : value.url, + name: data?.name ? data.name : value.name, + download: data?.download ? data.download : value.download, + status: 'done', + }; + this.setValue(newValue); + this.container = $(this.renderTemplate(newValue)); + center.empty(); + center.append(this.container); + this.initPlayer(); + }, + (error: string) => { + const newValue: V = { + ...value, + status: 'error', + message: error || locales['loadError'], + }; + this.setValue(newValue); + this.container = $(this.renderTemplate(newValue)); + center.empty(); + center.append(this.container); + } + ); + return this.container; + } + //已完成 + else if (status === 'done') { + //设置为加载状态 + this.container = $(this.renderTemplate({ ...value, status: undefined })); + command.executeMethod( + 'audio-uploader', + 'query', + audio_id, + (data?: { url: string; name?: string; cover?: string; download?: string }) => { + const newValue: AudioValue = { + ...value, + url: data?.url ? data.url : value.url, + name: data?.name ? data.name : value.name, + download: data?.download ? data.download : value.download, + }; + this.container = $(this.renderTemplate(newValue)); + center.empty(); + center.append(this.container); + this.initPlayer(); + }, + (error: string) => { + this.container = $( + this.renderTemplate({ + ...value, + status: 'error', + message: error || locales['loadError'], + }) + ); + center.empty(); + center.append(this.container); + } + ); + return this.container; + } else { + return $(this.renderTemplate(value)); + } + } + + didRender() { + super.didRender(); + this.container?.on(isMobile ? 'touchstart' : 'click', () => { + if (isEngine(this.editor) && !this.activated) { + this.editor.card.activate(this.root); + } + }); + } +} + +export default AudioComponent; diff --git a/editor/src/plugins/audio/index.ts b/editor/src/plugins/audio/index.ts new file mode 100644 index 0000000..9b02944 --- /dev/null +++ b/editor/src/plugins/audio/index.ts @@ -0,0 +1,138 @@ +import { $, CardEntry, CardInterface, CARD_KEY, decodeCardValue, encodeCardValue, isEngine, NodeInterface, Plugin, PluginEntry, PluginOptions, sanitizeUrl, SchemaInterface } from '@aomao/engine'; +import AudioComponent, { AudioValue } from './component'; +import AudioUploader from './uploader'; +import locales from './locales'; + +export interface AudioOptions extends PluginOptions { + onBeforeRender?: (action: 'download' | 'query' | 'cover', url: string) => string; +} +export default class AudioPlugin extends Plugin { + static get pluginName() { + return 'audio'; + } + + init() { + this.editor.language.add(locales); + if (!isEngine(this.editor)) return; + this.editor.on('parse:html', node => this.parseHtml(node)); + this.editor.on('paste:each', child => this.pasteHtml(child)); + this.editor.on('paste:schema', (schema: SchemaInterface) => this.pasteSchema(schema)); + } + + execute(status: 'uploading' | 'transcoding' | 'done' | 'error', url: string, name?: string, audio_id?: string, size?: number, download?: string): void { + const value: AudioValue = { + status, + audio_id, + url, + name: name || url, + size, + download, + }; + if (status === 'error') { + value.url = ''; + value.message = url; + } + this.editor.card.insert('audio', value); + } + + async waiting(callback?: (name: string, card?: CardInterface, ...args: any) => boolean | number | void): Promise { + const { card } = this.editor; + // 检测单个组件 + const check = (component: CardInterface) => { + return component.root.inEditor() && (component.constructor as CardEntry).cardName === AudioComponent.cardName && (component as AudioComponent).getValue()?.status === 'uploading'; + }; + // 找到不合格的组件 + const find = (): CardInterface | undefined => { + return card.components.find(check); + }; + const waitCheck = (component: CardInterface): Promise => { + let time = 60000; + return new Promise((resolve, reject) => { + if (callback) { + const result = callback((this.constructor as PluginEntry).pluginName, component); + if (result === false) { + return reject({ + name: (this.constructor as PluginEntry).pluginName, + card: component, + }); + } else if (typeof result === 'number') { + time = result; + } + } + const beginTime = new Date().getTime(); + const now = new Date().getTime(); + const timeout = () => { + if (now - beginTime >= time) return resolve(); + setTimeout(() => { + if (check(component)) timeout(); + else resolve(); + }, 10); + }; + timeout(); + }); + }; + return new Promise((resolve, reject) => { + const component = find(); + const wait = (component: CardInterface) => { + waitCheck(component) + .then(() => { + const next = find(); + if (next) wait(next); + else resolve(); + }) + .catch(reject); + }; + if (component) wait(component); + else resolve(); + }); + } + + pasteSchema(schema: SchemaInterface) { + schema.add({ + type: 'block', + name: 'div', + attributes: { + 'data-value': '*', + 'data-type': { + required: true, + value: AudioComponent.cardName, + }, + }, + }); + } + + pasteHtml(node: NodeInterface) { + if (!isEngine(this.editor)) return; + if (node.isElement()) { + const type = node.attributes('data-type'); + if (type === AudioComponent.cardName) { + const value = node.attributes('data-value'); + const cardValue = decodeCardValue(value) as AudioValue; + if (!cardValue.url) return; + this.editor.card.replaceNode(node, AudioComponent.cardName, cardValue); + node.remove(); + return false; + } + } + return true; + } + + parseHtml(root: NodeInterface) { + root.find(`[${CARD_KEY}=${AudioComponent.cardName}`).each(cardNode => { + const node = $(cardNode); + const card = this.editor.card.find(node); + const value = card?.getValue(); + if (value?.url && value.status === 'done') { + const { onBeforeRender } = this.options; + const { url } = value; + const html = `
`; + node.empty(); + node.replaceWith($(html)); + } else node.remove(); + }); + } +} + +export { AudioComponent, AudioUploader }; diff --git a/editor/src/plugins/audio/locales/en-US.ts b/editor/src/plugins/audio/locales/en-US.ts new file mode 100644 index 0000000..9c3aae3 --- /dev/null +++ b/editor/src/plugins/audio/locales/en-US.ts @@ -0,0 +1,12 @@ +export default { + audio: { + errorMessageCopy: 'Copy error message', + loadError: 'The audio failed to load!', + uploadError: 'The audio failed to upload!', + uploadLimitError: 'Upload audio size is limited to $size', + download: 'Download', + preview: 'Preview', + loading: 'Loading...', + transcoding: 'Transcoding...', + }, +}; diff --git a/editor/src/plugins/audio/locales/index.ts b/editor/src/plugins/audio/locales/index.ts new file mode 100644 index 0000000..c32f63b --- /dev/null +++ b/editor/src/plugins/audio/locales/index.ts @@ -0,0 +1,7 @@ +import en from './en-US'; +import cn from './zh-CN'; + +export default { + 'en-US': en, + 'zh-CN': cn, +}; diff --git a/editor/src/plugins/audio/locales/zh-CN.ts b/editor/src/plugins/audio/locales/zh-CN.ts new file mode 100644 index 0000000..3464ab3 --- /dev/null +++ b/editor/src/plugins/audio/locales/zh-CN.ts @@ -0,0 +1,12 @@ +export default { + audio: { + errorMessageCopy: '复制错误信息', + loadError: '音频加载失败!', + uploadError: '上传音频失败!', + uploadLimitError: '上传音频大小限制为 $size', + download: '下载', + preview: '预览', + loading: '加载中...', + transcoding: '转码中...', + }, +}; diff --git a/editor/src/plugins/audio/uploader.ts b/editor/src/plugins/audio/uploader.ts new file mode 100644 index 0000000..96a385c --- /dev/null +++ b/editor/src/plugins/audio/uploader.ts @@ -0,0 +1,376 @@ +import { + File, + isAndroid, + isEngine, + NodeInterface, + Plugin, + READY_CARD_KEY, + getExtensionName, + PluginOptions, + CARD_VALUE_KEY, + decodeCardValue, + encodeCardValue, + RequestData, + RequestHeaders, +} from '@aomao/engine'; + +import AudioComponent, { AudioValue } from './component'; + +export interface Options extends PluginOptions { + /** + * 音频上传地址 + */ + action: string; + /** + * 数据返回类型,默认 json + */ + type?: '*' | 'json' | 'xml' | 'html' | 'text' | 'js'; + /** + * 音频文件上传时 FormData 的名称,默认 file + */ + name?: string; + /** + * 额外携带数据上传 + */ + data?: RequestData; + /** + * 请求类型,默认 multipart/form-data; + */ + contentType?: string; + /** + * 是否跨域 + */ + crossOrigin?: boolean; + /** + * 请求头 + */ + headers?: RequestHeaders; + /** + * 文件接收的格式,默认 "*" + */ + accept?: string | Array; + /** + * 文件选择限制数量 + */ + multiple?: boolean | number; + /** + * 上传大小限制,默认 1024 * 1024 * 5 就是5M + */ + limitSize?: number; + /** + * 解析上传后的Respone,返回 result:是否成功,data:成功:{id:音频唯一标识,url:音频地址},失败:错误信息 + */ + parse?: (response: any) => { + result: boolean; + data: + | { + url: string; + id?: string; + status?: 'uploading' | 'transcoding' | 'done' | 'error'; + } + | string; + }; + /** + * 查询地址 + */ + query?: { + /** + * 查询地址 + */ + action: string; + /** + * 数据返回类型,默认 json + */ + type?: '*' | 'json' | 'xml' | 'html' | 'text' | 'js'; + /** + * 额外携带数据上传 + */ + data?: RequestData; + /** + * 请求类型,默认 multipart/form-data; + */ + contentType?: string; + }; +} + +export default class extends Plugin { + private cardComponents: { [key: string]: AudioComponent } = {}; + + static get pluginName() { + return 'audio-uploader'; + } + + extensionNames = ['mp3']; + + init() { + if (isEngine(this.editor)) { + this.editor.on('drop:files', files => this.dropFiles(files)); + this.editor.on('paste:event', ({ files }) => this.pasteFiles(files)); + this.editor.on('paste:each', node => this.pasteEach(node)); + } + let { accept } = this.options; + const names: Array = []; + if (typeof accept === 'string') accept = accept.split(','); + + (accept || []).forEach(name => { + name = name.trim(); + const newName = name.split('.').pop(); + if (newName) names.push(newName); + }); + if (names.length > 0) this.extensionNames = names; + } + + isAudio(file: File) { + const name = getExtensionName(file); + return this.extensionNames.indexOf(name) >= 0; + } + + async execute(files?: Array | MouseEvent | string, ...args: any) { + if (typeof files === 'string') { + switch (files) { + case 'query': + return this.query(args[0], args[1], args[2]); + } + return; + } + const { request, card, language } = this.editor; + const { action, data, type, contentType, multiple, crossOrigin, headers, name } = this.options; + const { parse } = this.options; + const limitSize = this.options.limitSize || 5 * 1024 * 1024; + if (!Array.isArray(files)) { + files = await request.getFiles({ + event: files, + accept: isAndroid ? 'audio/*' : this.extensionNames.length > 0 ? '.' + this.extensionNames.join(',.') : '', + multiple, + }); + } + if (files.length === 0) return; + request.upload( + { + url: action, + data, + type, + contentType, + crossOrigin, + headers, + onBefore: file => { + if (file.size > limitSize) { + this.editor.messageError( + 'upload-limit', + language + .get('audio', 'uploadLimitError') + .toString() + .replace('$size', (limitSize / 1024 / 1024).toFixed(0) + 'M') + ); + return false; + } + return true; + }, + onReady: fileInfo => { + if (!isEngine(this.editor) || this.cardComponents[fileInfo.uid]) return; + const component = card.insert('audio', { + status: 'uploading', + name: fileInfo.name, + size: fileInfo.size, + }) as AudioComponent; + this.cardComponents[fileInfo.uid] = component; + }, + onUploading: (file, { percent }) => { + const component = this.cardComponents[file.uid || '']; + if (!component) return; + component.setProgressPercent(percent); + }, + onSuccess: (response, file) => { + const component = this.cardComponents[file.uid || '']; + if (!component) return; + const id: string = response.id || (response.data && response.data.id); + const url: string = response.url || (response.data && response.data.url); + const cover: string = response.cover || (response.data && response.data.cover); + const download: string = response.download || (response.data && response.data.download); + let status: 'uploading' | 'transcoding' | 'done' | 'error' = response.status || (response.data && response.data.status); + status = status === 'transcoding' ? 'transcoding' : 'done'; + let result: { + result: boolean; + data: + | { + url: string; + audio_id?: string; + cover?: string; + download?: string; + status?: 'uploading' | 'transcoding' | 'done' | 'error'; + } + | string; + } = { + result: true, + data: { + audio_id: id, + url, + cover, + download, + status, + }, + }; + if (parse) { + const customizeResult = parse(response); + if (customizeResult.result) { + let data = result.data as { + url: string; + audio_id?: string; + cover?: string; + download?: string; + status?: 'uploading' | 'transcoding' | 'done' | 'error'; + }; + if (typeof customizeResult.data === 'string') + result.data = { + ...data, + url: customizeResult.data, + }; + else { + data.url = customizeResult.data.url; + if (customizeResult.data.status !== undefined) + data = { + ...data, + status: customizeResult.data.status, + }; + if (customizeResult.data.id !== undefined) + data = { + ...data, + audio_id: customizeResult.data.id, + }; + result.data = { ...data }; + } + } else { + result = { + result: false, + data: customizeResult.data.toString(), + }; + } + } else if (!url) { + result = { result: false, data: response.data }; + } + //失败 + if (!result.result) { + card.update(component.id, { + status: 'error', + message: (result.data as string) || this.editor.language.get('audio', 'uploadError'), + }); + } + //成功 + else { + this.editor.card.update( + component.id, + typeof result.data === 'string' + ? { url: result.data } + : { + ...result.data, + } + ); + } + delete this.cardComponents[file.uid || '']; + }, + onError: (error, file) => { + const component = this.cardComponents[file.uid || '']; + if (!component) return; + card.update(component.id, { + status: 'error', + message: error.message || this.editor.language.get('audio', 'uploadError'), + }); + delete this.cardComponents[file.uid || '']; + }, + }, + files, + name + ); + return; + } + + query( + audio_id: string, + success: (data?: { url: string; name?: string; cover?: string; download?: string; status?: string }) => void, + failed: (message: string) => void = () => { + return; + } + ) { + const { request } = this.editor; + + const { query, parse } = this.options; + if (!query || !audio_id) return success(); + + const { action, type, contentType, data } = query; + request.ajax({ + url: action, + contentType: contentType || '', + type: type === undefined ? 'json' : type, + data: + typeof data === 'function' + ? async () => { + const newData = data(); + return { + ...newData, + id: audio_id, + }; + } + : { + ...data, + id: audio_id, + }, + success: (response: any) => { + const { result, data } = response; + if (!result) { + failed(data); + } else { + const result = parse ? parse(response) : response; + if (result.result === false) { + failed(result.data || this.editor.language.get('audio', 'loadError')); + } else + success({ + ...result.data, + status: result.data.status !== 'transcoding' ? 'done' : 'transcoding', + }); + } + }, + error: error => { + failed(error.message || this.editor.language.get('audio', 'loadError')); + }, + method: 'GET', + }); + } + + dropFiles(files: Array) { + if (!isEngine(this.editor)) return; + files = files.filter(file => this.isAudio(file)); + if (files.length === 0) return; + this.editor.command.execute('audio-uploader', files); + return false; + } + + pasteFiles(files: Array) { + if (!isEngine(this.editor)) return; + files = files.filter(file => this.isAudio(file)); + if (files.length === 0) return; + this.editor.command.execute( + 'audio-uploader', + files.filter(file => this.isAudio(file)), + files + ); + return false; + } + + pasteEach(node: NodeInterface) { + //是卡片,并且还没渲染 + if (node.isCard() && node.attributes(READY_CARD_KEY)) { + if (node.attributes(READY_CARD_KEY) !== 'audio') return; + const value = decodeCardValue(node.attributes(CARD_VALUE_KEY)); + if (!value || !value.url) { + node.remove(); + return; + } + if (value.status === 'uploading') { + //如果是上传状态,设置为正常状态 + value.percent = 0; + node.attributes(CARD_VALUE_KEY, encodeCardValue({ ...value, status: 'done' })); + } + return; + } + } +} diff --git a/editor/src/plugins/draw/component/constant.ts b/editor/src/plugins/draw/component/constant.ts new file mode 100644 index 0000000..e180335 --- /dev/null +++ b/editor/src/plugins/draw/component/constant.ts @@ -0,0 +1,15 @@ +export const XML_DATA = ` + + + + + + + + + + + + + +`; diff --git a/editor/src/plugins/draw/component/diagram-loader.ts b/editor/src/plugins/draw/component/diagram-loader.ts new file mode 100644 index 0000000..9fb8835 --- /dev/null +++ b/editor/src/plugins/draw/component/diagram-loader.ts @@ -0,0 +1,11 @@ +import type * as Diagram from 'embed-drawio'; + +let instance: typeof Diagram | null = null; + +export const diagramLoader = (): Promise => { + if (instance) return Promise.resolve(instance); + return import('embed-drawio').then(res => { + instance = res; + return res; + }); +}; diff --git a/editor/src/plugins/draw/component/draw-edit.vue b/editor/src/plugins/draw/component/draw-edit.vue new file mode 100644 index 0000000..2dd0d9e --- /dev/null +++ b/editor/src/plugins/draw/component/draw-edit.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/editor/src/plugins/draw/component/draw-view.vue b/editor/src/plugins/draw/component/draw-view.vue new file mode 100644 index 0000000..fa4c204 --- /dev/null +++ b/editor/src/plugins/draw/component/draw-view.vue @@ -0,0 +1,58 @@ + + + + + diff --git a/editor/src/plugins/draw/component/index.css b/editor/src/plugins/draw/component/index.css new file mode 100644 index 0000000..93ee324 --- /dev/null +++ b/editor/src/plugins/draw/component/index.css @@ -0,0 +1,166 @@ +.data-draw .data-draw-mask { + opacity: 0; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + height: 100%; + z-index: 3; + display: block; + transition: all .3s cubic-bezier(.3, 1.2, .2, 1) +} + +.data-draw .data-draw-mask:hover { + cursor: pointer +} + +.data-draw-body { + border: 1px solid #e8e8e8; + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; + height: 400px; +} + +.data-draw-body .data-draw-content { + padding: 0; + margin: 0; + width: 100%; + height: 100%; + position: relative; + z-index: 2; +} + +.draw-icon-edit { + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + position: relative; +} + +.draw-icon-edit:hover path{ + fill: #40a9ff; +} + +.data-draw-body .data-draw-maximize { + position: absolute; + z-index: 2; + right: 10px; + top: 10px; + background-color: rgba(0, 0, 0, 0.65); + width: 20px; + height: 20px; + text-align: center; + font-size: 16px; + color: #fff; + line-height: 20px; + border-radius: 4px; + box-shadow: 0px 2px 8px 0px rgba(0, 0, 0, 0.08); + cursor: pointer; + display: none; +} + +.data-draw-body:hover .data-draw-maximize { + display: block; +} + +/* 自定义Drawio样式,覆盖原有样式 */ +body { + font-size: 14px; +} + +.example { + align-items: center; + display: flex; +} + +.example .buttonGroup { + margin: 30px; +} + +.example .buttonGroup>button { + display: block; + margin: 10px; +} + +:global .diagram-container .diagram-exit-btn { + cursor: pointer; + margin-right: 50px; +} + +#draw-view { + overflow: hidden; +} + +#draw-view, +.draw-view { + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +.diagram-container, +.diagram-container *, +.diagram-container *::before, +.diagram-container *::after { + box-sizing: content-box !important; +} + +.geSidebar, +.geSidebar input { + box-sizing: border-box !important; +} + +.draw-edit { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +.geEditor .geSidebarContainer, +.geEditor .geDiagramContainer, +.geEditor .geHsplit { + bottom: 0 !important; +} + +.geEditor .geFooterContainer { + display: none !important; +} + +.draw-full-modal .ant-modal { + max-width: 100%; + top: 0; + padding-bottom: 0; + margin: 0; +} +.draw-full-modal .ant-modal-content { + display: flex; + flex-direction: column; + height: calc(100vh); + overflow: auto; +} + +.draw-full-modal .ant-modal-body { + flex: 1; +} + +.draw-full-modal .ant-modal-confirm .ant-modal-body { + padding: 0; + display: flex; + align-items: center; + justify-content: center; +} + +.draw-full-modal .ant-modal-confirm-btns { + position: fixed; + right: 20px; + bottom: 20px; + margin: 0; + display: none; +} \ No newline at end of file diff --git a/editor/src/plugins/draw/component/index.ts b/editor/src/plugins/draw/component/index.ts new file mode 100644 index 0000000..17569da --- /dev/null +++ b/editor/src/plugins/draw/component/index.ts @@ -0,0 +1,143 @@ +import { EmbedOptions, EmbedValue, EmbedRenderBeforeEvent } from '../types'; +import { $, Card, CardType, NodeInterface, CardToolbarItemOptions, ToolbarItemOptions, isEngine } from '@aomao/engine'; +import { App, createApp, createVNode } from 'vue'; +import { Modal } from 'ant-design-vue'; +import DrawView from './draw-view.vue'; +import DrawEdit from './draw-edit.vue'; +import './index.css'; + +export const editIcon = ` + + + +`; + +class EmbedComponent extends Card { + renderBefore?: EmbedRenderBeforeEvent; + + static get cardName() { + return 'draw'; + } + + static get cardType() { + return CardType.BLOCK; + } + + static get lazyRender() { + return true; + } + + static get singleSelectable() { + return false; + } + + static get autoSelected() { + return false; + } + + #drawContent?: NodeInterface; + private vm?: App; + + resize = () => { + const value = this.getValue(); + if (!value?.isResize) return; + return this.#drawContent?.parent(); + }; + + toolbar(): Array { + const getItems = () => { + if (isEngine(this.editor) && !this.editor.readonly) { + const items: Array = [ + { type: 'dnd' }, + { + type: 'node', + node: $(editIcon), + didMount: node => { + if (node?.get()) { + node.on('click', () => { + const value = this.getValue(); + createApp(DrawEdit, { + value: value.xml, + change: (xml: string) => { + this.setValue({ + ...value, + xml, + }); + this.didRender(); + }, + }).mount(node.get() as Element); + }); + } + }, + }, + { type: 'copy' }, + { type: 'separator' }, + { type: 'delete' }, + ]; + return items; + } + return []; + }; + return getItems(); + } + + renderContainer() { + const value = this.getValue(); + const height = value?.height || 'auto'; + + const container = $(` +
+
+
+
+ + + +
+
+ `); + const drawContent = container.find('.data-draw-content'); + const maximize = container.find('.data-draw-maximize'); + maximize.on('click', () => { + const value = this.getValue(); + Modal.confirm({ + icon: null, + content: createVNode(DrawView, { value: value.xml }), + wrapClassName: 'draw-full-modal', + width: '100%', + cancelText: '关 闭', + closable: true, + }); + }); + if (value?.height) { + drawContent.attributes('data-height', value.height); + } + this.#drawContent = drawContent; + return container; + } + + render(renderBefore?: EmbedRenderBeforeEvent): string | void | NodeInterface { + this.renderBefore = renderBefore; + const center = this.getCenter(); + center.empty(); + center.append(this.renderContainer()); + } + + didRender() { + super.didRender(); + const value = this.getValue(); + this.vm = createApp(DrawView, { value: value.xml }); + this.vm.mount(this.#drawContent?.get()); + } + + destroy() { + super.destroy(); + this.vm?.unmount(); + this.vm = undefined; + } +} + +export default EmbedComponent; diff --git a/editor/src/plugins/draw/component/utils.ts b/editor/src/plugins/draw/component/utils.ts new file mode 100644 index 0000000..a4a66e3 --- /dev/null +++ b/editor/src/plugins/draw/component/utils.ts @@ -0,0 +1,59 @@ +export const clearElement = (element: HTMLElement | null): void => { + element && element.childNodes.forEach(node => element.removeChild(node)); +}; + +export const getDrawIOSvgString = (xml: XMLDocument) => { + return xmlToString(xml.documentElement.firstChild?.firstChild || null); +}; + +export const xmlToString = (xml: Node | null): string | null => { + if (!xml) return null; + try { + const serialize = new XMLSerializer(); + return serialize.serializeToString(xml); + } catch (error) { + console.log('XmlToString Error: ', error); + return null; + } +}; + +export const stringToXml = (str: string): XMLDocument | null => { + try { + const parser = new DOMParser(); + return parser.parseFromString(str, 'text/xml') as XMLDocument; + } catch (error) { + console.log('StringToXml Error: ', error); + return null; + } +}; + +export const svgToString = (svg: Node | null): string | null => { + if (!svg) return null; + try { + const serialize = new XMLSerializer(); + return serialize.serializeToString(svg); + } catch (error) { + console.log('SvgToString Error: ', error); + return null; + } +}; + +export const stringToSvg = (str: string): SVGElement | null => { + try { + const parser = new DOMParser(); + return parser.parseFromString(str, 'image/svg+xml').firstChild as SVGElement; + } catch (error) { + console.log('StringToSvg Error: ', error); + return null; + } +}; + +export const base64ToSvgString = (base64: string): string | null => { + try { + const svg = atob(base64.replace('data:image/svg+xml;base64,', '')); + return svg; + } catch (error) { + console.log('base64ToSvgString Error: ', error); + return null; + } +}; diff --git a/editor/src/plugins/draw/index.ts b/editor/src/plugins/draw/index.ts new file mode 100644 index 0000000..8f5aa99 --- /dev/null +++ b/editor/src/plugins/draw/index.ts @@ -0,0 +1,73 @@ +import { $, Plugin, NodeInterface, CARD_KEY, isEngine, SchemaInterface, PluginOptions, decodeCardValue, encodeCardValue } from '@aomao/engine'; +import DrawComponent from './component'; + +export interface Options extends PluginOptions { + hotkey?: string | Array; +} +export default class extends Plugin { + static get pluginName() { + return 'draw'; + } + // 插件初始化 + init() { + // 监听解析成html的事件 + this.editor.on('paser:html', node => this.parseHtml(node)); + // 监听粘贴时候设置schema规则的入口 + this.editor.on('paste:schema', schema => this.pasteSchema(schema)); + // 监听粘贴时候的节点循环 + this.editor.on('paste:each', child => this.pasteHtml(child)); + } + // 执行方法 + execute() { + if (!isEngine(this.editor)) return; + const { card } = this.editor; + card.insert(DrawComponent.cardName); + } + // 快捷键 + hotkey() { + return this.options.hotkey || 'mod+shift+0'; + } + // 粘贴的时候添加需要的 schema + pasteSchema(schema: SchemaInterface) { + schema.add({ + type: 'block', + name: 'div', + attributes: { + 'data-type': { + required: true, + value: DrawComponent.cardName, + }, + 'data-value': '*', + }, + }); + } + // 解析粘贴过来的html + pasteHtml(node: NodeInterface) { + if (!isEngine(this.editor)) return; + if (node.isElement()) { + const type = node.attributes('data-type'); + if (type === DrawComponent.cardName) { + const value = node.attributes('data-value'); + const cardValue = decodeCardValue(value); + this.editor.card.replaceNode(node, DrawComponent.cardName, cardValue); + node.remove(); + return false; + } + } + return true; + } + // 解析成html + parseHtml(root: NodeInterface) { + root.find(`[${CARD_KEY}=${DrawComponent.cardName}`).each(cardNode => { + const node = $(cardNode); + const card = this.editor.card.find(node) as DrawComponent; + const value = card?.getValue(); + if (value) { + node.empty(); + const div = $(`
`); + node.replaceWith(div); + } else node.remove(); + }); + } +} +export { DrawComponent }; diff --git a/editor/src/plugins/draw/types.ts b/editor/src/plugins/draw/types.ts new file mode 100644 index 0000000..edef954 --- /dev/null +++ b/editor/src/plugins/draw/types.ts @@ -0,0 +1,15 @@ +import { CardToolbarItemOptions, CardValue, EditorInterface, PluginOptions, ToolbarItemOptions } from '@aomao/engine'; + +export interface EmbedValue extends CardValue { + height?: number; + collapsed?: boolean; + xml?: string; + isResize?: boolean; +} + +export type EmbedRenderBeforeEvent = (url: string) => EmbedValue; + +export interface EmbedOptions extends PluginOptions { + renderBefore?: EmbedRenderBeforeEvent; + cardToolbars?: (items: (ToolbarItemOptions | CardToolbarItemOptions)[], editor: EditorInterface) => (ToolbarItemOptions | CardToolbarItemOptions)[]; +} diff --git a/editor/src/plugins/image/component/image/index.css b/editor/src/plugins/image/component/image/index.css new file mode 100644 index 0000000..9aedb44 --- /dev/null +++ b/editor/src/plugins/image/component/image/index.css @@ -0,0 +1,189 @@ +.am-engine [data-card-key="image"].card-selected [data-card-element="center"].data-card-border-selected { + outline: none; +} + +.am-engine [data-card-key="image"].card-selected [data-card-element="center"].data-card-border-selected .data-image-disable-resize { + outline: 2px solid #1890FF; +} + +.data-image { + position: relative; + display: inline-block; + font-size: 14px; + text-align: left; + border-radius: 3px 3px; + line-height: 24px; + text-indent: 0; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.data-image-blcok { + display: flex; + margin: 0 auto; + width: max-content; +} + +.data-image-blcok.data-image-disable-resize { + width: 100%; + display: block; +} + +.data-image-detail { + display: inline-block; +} + +.data-image-blcok .data-image-detail { + display: block; +} + +.data-image-meta { + position: relative; + color: #595959; + line-height: 0; +} + +.data-image-progress { + padding-left: 8px; + color: rgba(255, 255, 255, 0.9); +} + +.data-image-progress .data-anticon { + color: rgba(255, 255, 255, 0.9); + line-height: 24px; + margin-right: 10px; +} + +.data-image-maximize { + position: absolute; + z-index: 2; + right: 10px; + top: 10px; + background-color: rgba(0, 0, 0, 0.65); + width: 20px; + height: 20px; + text-align: center; + font-size: 16px; + color: #fff; + line-height: 20px; + border-radius: 4px; + box-shadow: 0px 2px 8px 0px rgba(0, 0, 0, 0.08); + cursor: pointer; +} + +.data-image-maximize .data-icon-maximize { + margin-left: 1px; +} + +.data-image-loading , .data-image-loaded { + display: inline-block; + border-radius: 0 0; + padding: 0 0; + background: transparent; + border-color: transparent; +} + +.data-image-blcok .data-image-loading, .data-image-blcok .data-image-loaded { + display: block; +} + +.data-image-loading .data-image-meta, .data-image-loaded .data-image-meta { + display: inline-block; +} + +.data-image-blcok .data-image-loading .data-image-meta, .data-image-blcok .data-image-loaded .data-image-meta { + display: block; +} + +.data-image-loading .data-image-meta img,.data-image-loaded .data-image-meta img { + border-radius: 2px 2px; + display: inline-block; + width: 100%; + opacity: 0.6; + text-align: left; + cursor: pointer; + transition: opacity 0.3s ease-in-out, box-shadow 0.3s ease-in-out; +} + +.data-image-blcok .data-image-loading .data-image-meta img, .data-image-blcok .data-image-loaded .data-image-meta img { + display: block; +} + +.data-image-loading .data-image-meta img , .data-image-blcok .data-image-loading .data-image-meta img { + display: none; +} + +.data-image-loading .data-image-meta .data-image-progress,.data-image-loaded .data-image-meta .data-image-progress { + font-family: 'Lucida Console', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + position: absolute; + bottom: 8px; + right: 8px; + font-size: 12px; + line-height: 24px; + color: rgba(255, 255, 255, 0.9); + padding: 0px 6px; + border-radius: 2px 2px; + background: rgba(0, 0, 0, 0.8); + white-space: nowrap; +} + +.data-image-loaded .data-image-meta img { + opacity: 1; +} + +.data-image-error { + padding: 2px 4px; + background: #f5f5f5; + display: flex; + border-radius: 4px; + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 12px; + align-items: center; + user-select: none; +} + +.data-image-error .data-icon { + font-size: 12px; +} + +.data-image-error .data-icon-error { + color: red; + margin-right: 8px; +} + +.data-image-error .data-icon-copy { + margin-left: 8px; + cursor: pointer; +} + +.am-engine [data-card-key="image"].card-selected [data-card-element=center].data-card-background-selected { + border-radius: 4px; +} + +.data-image-bg { + display: none; + border-radius: 2px; + opacity: 0.6; + background-size:100% 100%; + background-color: #FAFAFA; + background-repeat: no-repeat; + background-position: center; + background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMjhweCIgaGVpZ2h0PSIyMnB4IiB2aWV3Qm94PSIwIDAgMjggMjIiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDU1LjIgKDc4MTgxKSAtIGh0dHBzOi8vc2tldGNoYXBwLmNvbSAtLT4KICAgIDx0aXRsZT5pbWFnZS1maWxs5aSH5Lu9PC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGcgaWQ9Iuafpeeci+WbvueJh+S8mOWMljQuMCIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgaWQ9IuWKoOi9veWbvueJhyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTU3Mi4wMDAwMDAsIC01MDYuMDAwMDAwKSI+CiAgICAgICAgICAgIDxnIGlkPSJpbWFnZS1maWxs5aSH5Lu9IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg1NzAuMDAwMDAwLCA1MDEuMDAwMDAwKSI+CiAgICAgICAgICAgICAgICA8cmVjdCBpZD0iUmVjdGFuZ2xlIiBmaWxsPSIjMDAwMDAwIiBvcGFjaXR5PSIwIiB4PSIwIiB5PSIwIiB3aWR0aD0iMzIiIGhlaWdodD0iMzIiPjwvcmVjdD4KICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0yOSw1IEwzLDUgQzIuNDQ2ODc1LDUgMiw1LjQ0Njg3NSAyLDYgTDIsMjYgQzIsMjYuNTUzMTI1IDIuNDQ2ODc1LDI3IDMsMjcgTDI5LDI3IEMyOS41NTMxMjUsMjcgMzAsMjYuNTUzMTI1IDMwLDI2IEwzMCw2IEMzMCw1LjQ0Njg3NSAyOS41NTMxMjUsNSAyOSw1IFogTTEwLjU2MjUsOS41IEMxMS42NjU2MjUsOS41IDEyLjU2MjUsMTAuMzk2ODc1IDEyLjU2MjUsMTEuNSBDMTIuNTYyNSwxMi42MDMxMjUgMTEuNjY1NjI1LDEzLjUgMTAuNTYyNSwxMy41IEM5LjQ1OTM3NSwxMy41IDguNTYyNSwxMi42MDMxMjUgOC41NjI1LDExLjUgQzguNTYyNSwxMC4zOTY4NzUgOS40NTkzNzUsOS41IDEwLjU2MjUsOS41IFogTTI2LjYyMTg3NSwyMy4xNTkzNzUgQzI2LjU3ODEyNSwyMy4xOTY4NzUgMjYuNTE4NzUsMjMuMjE4NzUgMjYuNDU5Mzc1LDIzLjIxODc1IEw1LjUzNzUsMjMuMjE4NzUgQzUuNCwyMy4yMTg3NSA1LjI4NzUsMjMuMTA2MjUgNS4yODc1LDIyLjk2ODc1IEM1LjI4NzUsMjIuOTA5Mzc1IDUuMzA5Mzc1LDIyLjg1MzEyNSA1LjM0Njg3NSwyMi44MDYyNSBMMTAuNjY4NzUsMTYuNDkzNzUgQzEwLjc1NjI1LDE2LjM4NzUgMTAuOTE1NjI1LDE2LjM3NSAxMS4wMjE4NzUsMTYuNDYyNSBDMTEuMDMxMjUsMTYuNDcxODc1IDExLjA0Mzc1LDE2LjQ4MTI1IDExLjA1MzEyNSwxNi40OTM3NSBMMTQuMTU5Mzc1LDIwLjE4MTI1IEwxOS4xLDE0LjMyMTg3NSBDMTkuMTg3NSwxNC4yMTU2MjUgMTkuMzQ2ODc1LDE0LjIwMzEyNSAxOS40NTMxMjUsMTQuMjkwNjI1IEMxOS40NjI1LDE0LjMgMTkuNDc1LDE0LjMwOTM3NSAxOS40ODQzNzUsMTQuMzIxODc1IEwyNi42NTkzNzUsMjIuODA5Mzc1IEMyNi43NDA2MjUsMjIuOTEyNSAyNi43MjgxMjUsMjMuMDcxODc1IDI2LjYyMTg3NSwyMy4xNTkzNzUgWiIgaWQ9IlNoYXBlIiBmaWxsPSIjRThFOEU4Ij48L3BhdGg+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==); +} + +.data-image-loading .data-image-bg { + display: inline-block; +} + +.data-image-blcok .data-image-loading .data-image-bg { + display: block; +} + +.data-image-loading .data-image-maximize { + display: none; +} \ No newline at end of file diff --git a/editor/src/plugins/image/component/image/index.ts b/editor/src/plugins/image/component/image/index.ts new file mode 100644 index 0000000..217eff4 --- /dev/null +++ b/editor/src/plugins/image/component/image/index.ts @@ -0,0 +1,720 @@ +import type { ImageOptions, PswpInterface } from '../../types'; +import type { EditorInterface, NodeInterface } from '@aomao/engine'; +import { $, File, isEngine, escape, random, getExtensionName, sanitizeUrl, Tooltip, isMobile, Resizer, CardType } from '@aomao/engine'; +import 'cropperjs/dist/cropper.css'; +import Cropper from 'cropperjs'; +import PhotoSwipe from 'photoswipe'; +import { ImageValue } from '..'; +import Pswp from '../pswp'; +import './index.css'; + +export type Status = 'uploading' | 'done' | 'error'; + +export type Size = { + width: number; + height: number; + naturalWidth: number; + naturalHeight: number; +}; + +export type Options = { + /** + * 卡片根节点 + */ + root: NodeInterface; + /** + * 容器 + */ + container: NodeInterface; + /** + * 状态 + * uploading 上传中 + * done 上传完成 + * error 错误 + */ + status: Status; + /** + * 图标链接 + */ + src: string; + /** + * 标题 + */ + alt?: string; + /** + * 链接 + */ + link?: { + href: string; + target?: string; + }; + display?: CardType; + /** + * 错误消息 + */ + message?: string; + /** + * 样式名称,多个以空格隔空 + */ + className?: string; + /** + * 图片大小 + */ + size?: Size; + /** + * 上传进度 + */ + percent?: number; + /** + * 图片渲染前调用 + * @param status 状态 + * @param src 图片地址 + * @returns 图片地址 + */ + onBeforeRender?: (status: 'uploading' | 'done', src: string, editor: EditorInterface) => string; + onChangeSrc?: (url?: string, loaded?: boolean) => void; + onChange?: (size?: Size, loaded?: boolean) => void; + onError?: () => void; + onLoad?: () => void; + enableResizer?: boolean; + maxHeight?: number | undefined; +}; + +export const winPixelRatio = window.devicePixelRatio; +let pswp: PswpInterface | undefined = undefined; +class Image { + private editor: EditorInterface; + options: Options; + root: NodeInterface; + private progress: NodeInterface; + private image: NodeInterface; + private detail: NodeInterface; + private meta: NodeInterface; + private maximize: NodeInterface; + private bg: NodeInterface; + resizer?: Resizer; + private pswp: PswpInterface; + src: string; + status: Status; + size: Size; + maxWidth: number; + maxHeight: number | undefined; + rate = 1; + isLoad = false; + message: string | undefined; + cropper: Cropper | undefined; + + constructor(editor: EditorInterface, options: Options) { + this.editor = editor; + this.options = options; + this.src = this.options.src; + this.size = this.options.size || { + width: 0, + height: 0, + naturalHeight: 0, + naturalWidth: 0, + }; + this.maxHeight = this.options.maxHeight; + this.status = this.options.status; + this.root = $(this.renderTemplate()); + this.progress = this.root.find('.data-image-progress'); + this.image = this.root.find('img'); + this.detail = this.root.find('.data-image-detail'); + this.meta = this.root.find('.data-image-meta'); + this.maximize = this.root.find('.data-image-maximize'); + this.bg = this.root.find('.data-image-bg'); + this.maxWidth = this.getMaxWidth(); + this.pswp = pswp || new Pswp(editor); + this.message = this.options.message; + + pswp = this.pswp; + } + + renderTemplate(message?: string) { + const { link, percent, className, onBeforeRender } = this.options; + + if (this.status === 'error') { + return ` + + ${message || this.options.message} + + `; + } + const src = onBeforeRender ? onBeforeRender(this.status, this.options.src, this.editor) : this.options.src; + const progress = ` + + + + ${percent || 0}% + `; + + const alt = escape(this.options.alt || ''); + const attr = alt ? ` alt="${alt}" title="${alt}" ` : ''; + //加上 data-drag-image 样式可以拖动图片 + let img = ``; + //只读渲染加载链接 + if (link && !isEngine(this.editor)) { + const target = link.target || '_blank'; + img = `${img}`; + } + //全屏图标 + const maximize = ``; + + return ` + + + + + ${img} + ${progress} + + ${maximize} + + + + `; + } + + bindErrorEvent(node: NodeInterface) { + const editor = this.editor; + const copyNode = node.find('.data-icon-copy'); + copyNode.on('mouseenter', () => { + Tooltip.show(copyNode, editor.language.get('image', 'errorMessageCopy').toString()); + }); + copyNode.on('mouseleave', () => { + Tooltip.hide(); + }); + copyNode.on('click', (event: MouseEvent) => { + event.stopPropagation(); + event.preventDefault(); + Tooltip.hide(); + editor.clipboard.copy(this.message || this.options.message || 'Error message'); + editor.messageSuccess('copy', editor.language.get('copy', 'success').toString()); + }); + } + + setProgressPercent(percent: number) { + this.progress.find('.percent').html(`${percent}%`); + } + + imageLoadCallback() { + const editor = this.editor; + const root = editor.card.closest(this.root); + if (!root || this.status === 'uploading') { + return; + } + + if (this.status === 'done') { + const contentNode = this.root.find('.data-image-content'); + contentNode.addClass('data-image-loaded'); + contentNode.removeClass('data-image-loading'); + } + + const img = this.image.get(); + if (!img) return; + const { naturalWidth, naturalHeight } = img; + this.rate = naturalHeight / naturalWidth; + + this.size.naturalWidth = naturalWidth; + this.size.naturalHeight = naturalHeight; + + if (!this.size.width) this.size.width = naturalWidth; + if (!this.size.height) this.size.height = naturalHeight; + + this.resetSize(); + + this.image.css('visibility', 'visible'); + this.detail.css('height', ''); + this.detail.css('width', ''); + const { onChange } = this.options; + if (isEngine(editor) && onChange) { + onChange(this.size, true); + } + window.removeEventListener('resize', this.onWindowResize); + window.addEventListener('resize', this.onWindowResize); + editor.off('editor:resize', this.onWindowResize); + editor.on('editor:resize', this.onWindowResize); + // 重新调整拖动层尺寸 + if (this.resizer) { + this.resizer.setSize(img.clientWidth, img.clientHeight); + } + this.isLoad = true; + if (this.options.onLoad) { + this.options.onLoad(); + } + } + + onWindowResize = () => { + if (!isEngine(this.editor)) return; + this.maxWidth = this.getMaxWidth(); + this.resetSize(); + const image = this.image.get(); + if (!image) return; + const { clientWidth, clientHeight } = image; + if (this.resizer) { + this.resizer.maxWidth = this.maxWidth; + this.resizer.setSize(clientWidth, clientHeight); + } + }; + + imageLoadError() { + if (this.status === 'uploading') return; + this.status = 'error'; + const { container } = this.options; + container.empty(); + container.append(this.renderTemplate(this.editor.language.get('image', 'loadError').toString())); + this.detail.css('width', ''); + this.detail.css('height', ''); + this.bindErrorEvent(container); + const { onError } = this.options; + if (onError) onError(); + this.isLoad = true; + } + + getMaxWidth(node: NodeInterface = this.options.root) { + const block = this.editor.block.closest(node).get(); + if (!block) return 0; + return block.clientWidth - 6; + } + + /** + * 重置大小 + */ + resetSize() { + this.meta.css({ + 'background-color': '', + width: '', + //height: "", + }); + + this.image.css({ + width: '', + //height: "", + }); + + const img = this.image.get(); + if (!img) return; + + let { width, height } = this.size; + + if (!height) { + height = Math.round(this.rate * width); + } else if (!width) { + width = Math.round(height / this.rate); + } else if (width && height) { + // 修正非正常的比例 + height = Math.round(this.rate * width); + this.size.height = height; + } else { + const { clientWidth, clientHeight } = img; + width = clientWidth; + height = clientHeight; + const { naturalWidth, naturalHeight } = this.size; + // fix:svg 图片宽度 300px 问题 + if (this.isSvg() && naturalWidth && naturalHeight) { + width = naturalWidth; + height = naturalHeight; + } + } + + if (width > this.maxWidth) { + width = this.maxWidth; + height = Math.round(width * this.rate); + } + if (this.options.enableResizer === false) { + this.image.css('width', ''); + } else { + this.image.css('width', `${width}px`); + //this.image.css("height", `${height}px`); + } + } + + changeSize(width: number, height: number) { + if (width < 24) { + width = 24; + height = width * this.rate; + } + + if (width > this.maxWidth) { + width = this.maxWidth; + height = width * this.rate; + } + + if (height < 24) { + height = 24; + width = height / this.rate; + } + + width = Math.round(width); + height = Math.round(height); + this.size.width = width; + this.size.height = height; + this.image.css({ + width: `${width}px`, + //height: `${height}px`, + }); + const { onChange } = this.options; + if (onChange) onChange(this.size); + + this.destroyEditor(); + this.renderEditor(); + } + + changeUrl(url: string) { + if (this.src !== url) { + this.src = url; + this.isLoad = false; + this.image.attributes('src', this.getSrc()); + } + } + + getSrc = () => { + const { onBeforeRender } = this.options; + return onBeforeRender && this.status !== 'error' ? onBeforeRender(this.status, this.src, this.editor) : this.src; + }; + + isSvg() { + return this.src.split('?')[0].endsWith('.svg') || this.src.startsWith('data:image/svg+xml'); + } + + openZoom = (event: MouseEvent | TouchEvent) => { + event.preventDefault(); + event.stopPropagation(); + const editor = this.editor; + const imageArray: PhotoSwipe.Item[] = []; + const cardRoot = editor.card.closest(this.root); + let rootIndex = 0; + + editor.container + .find(`[data-card-key="image"]`) + .toArray() + .filter(image => { + return image.find('img').length > 0; + }) + .forEach((imageNode, index) => { + const card = editor.card.find(imageNode); + const value = card?.getValue(); + if (!card || !value) return; + const image = card.getCenter().find('img'); + const imageWidth = parseInt(image.css('width')); + const imageHeight = parseInt(image.css('height')); + const size = value.size; + const naturalWidth = size ? size.naturalWidth || this.size.naturalWidth : imageWidth * winPixelRatio; + const naturalHeight = size ? size.naturalHeight || this.size.naturalHeight : imageHeight * winPixelRatio; + let src = value['src']; + const { onBeforeRender } = this.options; + if (onBeforeRender) src = onBeforeRender('done', src, this.editor); + const msrc = image.attributes('src'); + imageArray.push({ + src, + msrc, + w: naturalWidth, + h: naturalHeight, + }); + if (cardRoot?.equal(imageNode)) { + rootIndex = index; + } + }); + this.pswp.open(imageArray, rootIndex); + }; + + closeZoom() { + this.pswp?.close(); + } + + cropImage() { + if (this.status === 'done') { + this.destroyEditor(); + } + const img = this.image.get(); + this.cropper = new Cropper(img as HTMLImageElement, { autoCropArea: 1 }); + } + + async cropImageSave() { + if (this.cropper) { + const canvas = this.cropper.getCroppedCanvas(); + const base64url = canvas?.toDataURL(); + /* 直接使用 base64赋值 + this.changeUrl(base64url); + onChangeSrc && onChangeSrc(base64url); + */ + // 重新上传用后台返回的url 展示 + const fileBlob = this.dataURIToFile(base64url); + const ext = getExtensionName(fileBlob); + const name = ext ? 'image.'.concat(ext) : 'image'; + const file: File = new globalThis.File([fileBlob], name, { type: 'image/jpeg' }); + file.uid = new Date().getTime() + '-' + random(); + const src = await this.uploadImage([file]); + const { onChangeSrc } = this.options; + this.changeUrl(src); + onChangeSrc && onChangeSrc(src); + this.size.naturalWidth = canvas?.width; + this.size.naturalHeight = canvas?.height; + const { width, height } = this.cropper?.getCropBoxData(); + this.changeSize(width, height); + this.cropper.destroy(); + this.cropper = undefined; + } + } + + async rotateImage() { + console.log("rotateImage", this.editor) + const canvas = document.createElement('canvas'); + // 默认旋转90° 将图片的初始宽高交换赋值给画布的宽高 + canvas.width = this.size.naturalHeight; + canvas.height = this.size.naturalWidth; + const ctx = canvas.getContext('2d'); + ctx?.save(); + // 从坐标 0,0 渲染canvas.width, canvas.height的画布 + ctx?.fillRect(0, 0, canvas.width, canvas.height); + // 旋转90° + ctx?.rotate(90 * Math.PI / 180); + // 将图片渲染到画布 + const img = this.image.get(); + ctx?.drawImage(img as HTMLImageElement, 0, -canvas.width); + // 生成base64url + const base64url = canvas.toDataURL('image/png'); + /* 直接使用 base64赋值 + this.changeUrl(base64url); + onChangeSrc && onChangeSrc(base64url); + */ + // 重新上传用后台返回的url 展示 + const fileBlob = this.dataURIToFile(base64url); + const ext = getExtensionName(fileBlob); + const name = ext ? 'image.'.concat(ext) : 'image'; + const file: File = new globalThis.File([fileBlob], name, { type: 'image/jpeg' }); + file.uid = new Date().getTime() + '-' + random(); + const src = await this.uploadImage([file]); + // 修改图片地址 + const { onChangeSrc } = this.options; + this.changeUrl(src); + onChangeSrc && onChangeSrc(src); + // 计算旋转后的宽高 + const size = { + width: this.size.height, + height: this.size.width, + naturalWidth: this.size.naturalHeight, + naturalHeight: this.size.naturalWidth + }; + this.size.naturalWidth = size.naturalWidth; + this.size.naturalHeight = size.naturalHeight; + // 修改图片宽高 + this.changeSize(size.width, size.height); + } + + dataURIToFile(dataURI: string) { + let byteString; + + if (dataURI.split(',')[0].indexOf('base64') >= 0) { + byteString = atob(dataURI.split(',')[1]); + } else { + byteString = unescape(dataURI.split(',')[1]); + } + // separate out the mime component + const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; // write the bytes of the string to a typed array + + const ia = new Uint8Array(byteString.length); + + for (let i = 0; i < byteString.length; i++) { + ia[i] = byteString.charCodeAt(i); + } + + return new Blob([ia], { + type: mimeString, + }); + } + + uploadImage(files: Array): Promise { + const { request, card } = this.editor; + const imageUploaderPlugin = this.editor.plugin.findPlugin('image-uploader'); + const { action, crossOrigin, headers } = imageUploaderPlugin?.options.file; + const parse = imageUploaderPlugin?.options.parse; + return new Promise((resolve, reject) => { + return request.upload( + { + url: action, + crossOrigin, + headers, + onSuccess: (response, file) => { + const result = parse(response); + return resolve(result.data); + }, + onError: (error, file) => { + return reject(error); + }, + }, + files, + 'file' + ); + }) + } + + renderEditor() { + const img = this.image.get(); + if (!img) return; + const { clientWidth, clientHeight } = img; + + if (!clientWidth || !clientHeight) { + return; + } + const editor = this.editor; + this.maxWidth = this.getMaxWidth(); + this.rate = clientHeight / clientWidth; + if (isMobile || !isEngine(editor) || editor.readonly) return; + if (this.options.enableResizer === false) { + return; + } + // 拖动调整图片大小 + const resizer = new Resizer({ + imgUrl: this.getSrc(), + width: clientWidth, + height: clientHeight, + rate: this.rate, + maxWidth: this.maxWidth, + onChange: ({ width, height }) => this.changeSize(width, height), + }); + const resizerNode = resizer.render(); + this.root.find('.data-image-detail').append(resizerNode); + this.resizer = resizer; + this.resizer.on('dblclick', this.openZoom); + } + + destroyEditor() { + this.resizer?.off('dblclick', this.openZoom); + this.resizer?.destroy(); + } + + destroy() { + window.removeEventListener('resize', this.onWindowResize); + this.editor.off('editor:resize', this.onWindowResize); + this.destroyEditor(); + this.image.off('click', this.openZoom); + this.image.off('dblclick', this.openZoom); + this.maximize.off('click', this.openZoom); + } + + focus = () => { + if (!isEngine(this.editor)) { + return; + } + this.root.addClass('data-image-active'); + if (this.status === 'done') { + this.destroyEditor(); + this.renderEditor(); + } + }; + + blur = () => { + if (!isEngine(this.editor)) { + return; + } + this.root.removeClass('data-image-active'); + if (this.status === 'done') { + this.destroyEditor(); + this.cropImageSave(); + } + }; + + render(loadingBg?: string) { + // 阅读模式不展示错误 + const { container, display, enableResizer } = this.options; + if (display === CardType.BLOCK) { + this.root.addClass('data-image-blcok'); + } + const editor = this.editor; + if (enableResizer === false) { + this.root.addClass('data-image-disable-resize'); + } + if (this.status === 'error' && isEngine(editor)) { + this.root = $(this.renderTemplate(this.message || editor.language.get('image', 'uploadError'))); + this.bindErrorEvent(this.root); + container.empty().append(this.root); + this.progress.remove(); + return; + } + if (this.status === 'uploading') { + this.progress.show(); + container.empty().append(this.root); + } else { + this.progress.remove(); + } + if (this.status === 'done' && this.isLoad) { + const contentNode = this.root.find('.data-image-content'); + contentNode.addClass('data-image-loaded'); + contentNode.removeClass('data-image-loading'); + } + if (this.status === 'done' && !this.isLoad) { + if (!this.root.inEditor()) container.empty().append(this.root); + } + this.maxWidth = this.getMaxWidth(); + let { width, height } = this.size; + if ((width && height) || !this.src) { + if (width > this.maxWidth) { + width = this.maxWidth; + height = Math.round((width * height) / this.size.width); + } else if (!this.src && !width && !height) { + width = this.maxWidth; + height = this.maxWidth / 2; + } + if (this.src) { + if (this.options.enableResizer === false) { + this.image.css({ + width: '100%', + }); + } else { + this.image.css({ + width: width + 'px', + //height: height + "px", + }); + } + + const { onChange } = this.options; + if (width > 0 && height > 0) { + this.size = { ...this.size, width, height }; + if (onChange) onChange(this.size); + } + } + if (this.options.enableResizer === false) { + this.bg.css({ + width: '100%', + }); + } else { + this.bg.css({ + width: width + 'px', + height: height + 'px', + }); + } + + if (loadingBg) { + this.bg.css('background-image', `url(${loadingBg})`); + } + } + + this.image.on('load', () => this.imageLoadCallback()); + this.image.on('error', () => this.imageLoadError()); + if (!isMobile) { + this.root.on('mouseenter', () => { + this.maximize.show(); + }); + this.root.on('mouseleave', () => { + this.maximize.hide(); + }); + } + + if (!isEngine(editor) || editor.readonly) { + const link = this.image.closest('a'); + // 无链接 + if (link.length === 0) { + this.image.on('click', this.openZoom); + } + } + this.maximize.on('click', this.openZoom); + if (isEngine(editor) || !this.root.inEditor()) { + this.image.on('dblclick', this.openZoom); + } + } +} + +export default Image; diff --git a/editor/src/plugins/image/component/index.ts b/editor/src/plugins/image/component/index.ts new file mode 100644 index 0000000..8148ad1 --- /dev/null +++ b/editor/src/plugins/image/component/index.ts @@ -0,0 +1,354 @@ +import type { ImageOptions } from '../types'; +import { Card, CardToolbarItemOptions, CardType, CardValue, isEngine, isMobile, NodeInterface, ToolbarItemOptions } from '@aomao/engine'; +import Image, { Size } from './image'; + +export interface ImageValue extends CardValue { + /** + * 图片地址 + */ + src: string; + /** + * 位置 + */ + align?: string; + /** + * 状态 + * uploading 上传中 + * done 上传成功 + */ + status?: 'uploading' | 'done' | 'error'; + /** + * 标题 + */ + alt?: string; + /** + * 链接 + */ + link?: { + href: string; + target?: string; + }; + /** + * 上传进度 + */ + percent?: number; + /** + * 错误状态下的错误信息 + */ + message?: string; + /** + * 图片大小 + */ + size?: { + /** + * 图片展示宽度 + */ + width: number; + /** + * 图片展示高度 + */ + height: number; + /** + * 图片真实宽度 + */ + naturalWidth: number; + /** + * 图片真实高度 + */ + naturalHeight: number; + }; +} + +class ImageComponent extends Card { + protected image?: Image; + protected widthInput?: NodeInterface; + protected heightInput?: NodeInterface; + protected isLocalError?: boolean; + + static get cardName() { + return 'image'; + } + + static get cardType() { + return CardType.INLINE; + } + + static get collab() { + return false; + } + // static get autoSelected() { + // return false; + // } + + /** + * 设置上传进度 + * @param percent 进度百分比 + */ + setProgressPercent(percent: number) { + this.image?.setProgressPercent(percent); + this.setValue({ + percent, + } as T); + } + + setSize(size: Size, loaded?: boolean) { + if (!size.width || !size.height) return; + const value = this.getValue(); + if (!loaded || !value.size || !value.size.height || !value.size.width || !value.size.naturalWidth || !value.size.naturalHeight) this.setValue({ size } as T); + if (this.widthInput) { + this.widthInput.get()!.value = size.width.toString(); + } + if (this.heightInput) { + this.heightInput.get()!.value = size.height.toString(); + } + } + + onInputChange(width: string | number, height: string | number) { + const value = this.getValue(); + if (typeof width === 'string') { + if (!/^[1-9]+(\d+)?$/.test(width) && this.widthInput) { + width = value?.size?.width || value?.size?.naturalWidth || 0; + this.widthInput.get()!.value = width.toString(); + } + width = parseInt(width.toString(), 10); + } + if (typeof height === 'string') { + if (!/^[1-9]+(\d+)?$/.test(height) && this.heightInput) { + height = value?.size?.height || value?.size?.naturalHeight || 0; + this.heightInput.get()!.value = height.toString(); + } + height = parseInt(height.toString(), 10); + } + this.image?.changeSize(parseInt(width.toString(), 10), height); + } + + toolbar(): Array { + const editor = this.editor; + const getItems = (): Array => { + if (!isEngine(editor) || editor.readonly) return []; + const { language } = editor; + let value = this.getValue(); + if (this.isLocalError === true || value?.status !== 'done') + return [ + { + key: 'delete', + type: 'delete', + }, + ]; + + const items: Array = [ + { + key: 'copy', + type: 'copy', + }, + { + key: 'delete', + type: 'delete', + }, + ]; + if (isMobile) return items; + const rotateItems: (CardToolbarItemOptions | ToolbarItemOptions)[] = [ + { + key: 'button', + type: 'button', + content: ``, + title: '旋转', + onClick: () => { + this.image?.rotateImage(); + }, + }, + ]; + const cropperItems: (CardToolbarItemOptions | ToolbarItemOptions)[] = [ + { + key: 'button', + type: 'button', + content: ``, + title: '裁剪', + onClick: () => { + this.image?.cropImage(); + }, + }, + ]; + const resizerItems: (CardToolbarItemOptions | ToolbarItemOptions)[] = [ + { + key: 'width', + type: 'input', + placeholder: language.get('image', 'toolbbarWidthTitle').toString(), + prefix: '宽:', + value: value?.size?.width || 0, + didMount: node => { + this.widthInput = node.find('input[type=input]'); + }, + onChange: value => { + const height = Math.round(parseInt(value, 10) * (this.image?.rate || 1)); + this.onInputChange(value, height); + }, + }, + { + key: 'height', + type: 'input', + placeholder: language.get('image', 'toolbbarHeightTitle').toString(), + prefix: '高:', + value: value?.size?.height || 0, + didMount: node => { + this.heightInput = node.find('input[type=input]'); + }, + onChange: value => { + const width = Math.round(parseInt(value, 10) / (this.image?.rate || 1)); + this.onInputChange(width, value); + }, + }, + { + key: 'resize', + type: 'button', + content: ``, + title: language.get('image', 'toolbarReductionTitle'), + onClick: () => { + value = this.getValue(); + this.onInputChange(value?.size?.naturalWidth || 0, value?.size?.naturalHeight || 0); + }, + }, + ]; + const typeItems: (CardToolbarItemOptions | ToolbarItemOptions)[] = [ + { + key: 'block', + type: 'button', + content: ``, + title: language.get('image', 'displayBlockTitle'), + onClick: () => { + this.type = CardType.BLOCK; + }, + }, + { + key: 'inline', + type: 'button', + content: ``, + title: language.get('image', 'displayInlineTitle'), + onClick: () => { + this.type = CardType.INLINE; + }, + }, + ]; + const imagePlugin = editor.plugin.findPlugin('image'); + return items.concat([ + ...(imagePlugin?.options?.enableRotate === false ? [] : rotateItems), + ...(imagePlugin?.options?.enableCropper === false ? [] : cropperItems), + ...(imagePlugin?.options?.enableResizer === false ? [] : resizerItems), + ...(imagePlugin?.options?.enableTypeSwitch === false ? [] : typeItems), + ]); + }; + const options = editor.plugin.findPlugin('image')?.options; + if (options?.cardToolbars) { + return options.cardToolbars(getItems(), this.editor); + } + return getItems(); + } + + onActivate(activated: boolean) { + super.onActivate(activated); + if (activated && !this.selectedByOther) this.image?.focus(); + else this.image?.blur(); + } + + onSelectByOther( + selected: boolean, + value?: { + color: string; + rgb: string; + } + ): NodeInterface | void { + this.image?.root?.css('outline', selected ? '2px solid ' + value!.color : ''); + const className = 'card-selected-other'; + if (selected) this.root.addClass(className); + else this.root.removeClass(className); + return this.image?.root; + } + + writeHistoryOnValueChange() { + if (this.loading) return false; + return; + } + + render(loadingBg?: string): string | void | NodeInterface { + const value = this.getValue(); + if (!value) return; + const editor = this.editor; + if (!this.image || this.image.root.length === 0) { + const imagePlugin = editor.plugin.findPlugin('image'); + this.image = new Image(editor, { + root: this.root, + container: this.getCenter(), + status: value.status || 'done', + src: value.src, + size: value.size, + alt: value.alt, + link: value.link, + display: this.type, + percent: value.percent, + message: value.message, + enableResizer: imagePlugin?.options?.enableResizer, + onBeforeRender: (status, src) => { + const imagePlugin = editor.plugin.findPlugin('image'); + if (imagePlugin) { + const { onBeforeRender } = imagePlugin.options || {}; + if (onBeforeRender) return onBeforeRender(status, src, this.editor); + } + return src; + }, + onChangeSrc: src => { + if (isEngine(editor) && !editor.readonly && src) { + const value = this.getValue(); + console.log("value", value); + this.setValue({ ...value, src } as T); + } + }, + onChange: (size, loaded) => { + if (isEngine(editor) && !editor.readonly && size) this.setSize(size, loaded); + }, + onError: () => { + this.isLocalError = true; + this.didUpdate(); + }, + onLoad: () => { + if (this.image?.size && (!value.size?.naturalHeight || !value.size?.naturalWidth)) { + const { naturalHeight, naturalWidth } = this.image.size; + this.setSize( + { + ...value.size, + naturalHeight, + naturalWidth, + } as Size, + true + ); + } + if (this.activated) this.image?.focus(); + }, + maxHeight: imagePlugin?.options?.maxHeight, + }); + } else { + this.image.changeUrl(value.src); + this.image.status = value.status || 'done'; + this.image.message = value.message; + this.image.size.width = value.size?.width || 0; + this.image.size.height = value.size?.height || 0; + if (value.percent) this.image.setProgressPercent(value.percent); + this.image.resizer?.destroy(); + } + this.image.render(loadingBg); + } + + didUpdate() { + super.didUpdate(); + this.toolbarModel?.getContainer()?.remove(); + this.toolbarModel?.create(); + this.toolbarModel?.setDefaultAlign('top'); + } + + didRender() { + const value = this.getValue(); + if (value.status === 'done') super.didRender(); + this.toolbarModel?.setDefaultAlign('top'); + } +} + +export default ImageComponent; diff --git a/editor/src/plugins/image/component/pswp/index.css b/editor/src/plugins/image/component/pswp/index.css new file mode 100644 index 0000000..53182c3 --- /dev/null +++ b/editor/src/plugins/image/component/pswp/index.css @@ -0,0 +1,194 @@ +.pswp .data-pswp-tool-bar { + position: absolute; + top: initial; + margin: 0 auto; + bottom: 20px; + vertical-align: middle; + height: 54px; + width: 100%; + text-align: center; + +} + +.pswp .data-pswp-tool-bar .separation { + width: 1px; + margin: 0px 10px; + display: inline-block; + height: 20px; + border: 0.5px solid #383838; + +} + +.pswp .data-pswp-tool-bar .btn { + color: #f8f9fa; + display: inline-block; + width: 32px; + height: 32px; + padding: 6px; + margin: 0 6px; + border: 1px solid #383838; + border-radius: 2px; + +} + +.pswp .data-pswp-tool-bar .btn::before { + width: 20px; + height: 20px; + margin: 0 auto; + content: ' '; + display: inline-block; + background-repeat: no-repeat; + opacity: 0.65; + background-position: -1px -1px; + +} + +.pswp .data-pswp-tool-bar .data-pswp-arrow-left { + padding: 8px; + +} + +.pswp .data-pswp-tool-bar .data-pswp-arrow-right { + padding: 8px; + +} + +.pswp .data-pswp-tool-bar .data-pswp-arrow-left::before { + width: 16px; + height: 16px; + background-size: 16px 16px; + background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDU1LjIgKDc4MTgxKSAtIGh0dHBzOi8vc2tldGNoYXBwLmNvbSAtLT4KICAgIDx0aXRsZT5sZWZ0LWNpcmNsZTwvdGl0bGU+CiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4KICAgIDxnIGlkPSJsZWZ0LWNpcmNsZSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgaWQ9ImxlZnQiIGZpbGwtcnVsZT0ibm9uemVybyI+CiAgICAgICAgICAgIDxyZWN0IGlkPSLnn6nlvaIiIGZpbGw9IiMwMDAwMDAiIG9wYWNpdHk9IjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCI+PC9yZWN0PgogICAgICAgICAgICA8cGF0aCBkPSJNMTQuMTQwNjI1LDQuMjYzNjcxODggTDE0LjE0MDYyNSwyLjc1MzkwNjI1IEMxNC4xNDA2MjUsMi42MjMwNDY4OCAxMy45OTAyMzQ0LDIuNTUwNzgxMjUgMTMuODg4NjcxOSwyLjYzMDg1OTM3IEw1LjA4Mzk4NDM4LDkuNTA3ODEyNSBDNC43NjM2NzE4OCw5Ljc1NzgxMjUgNC43NjM2NzE4OCwxMC4yNDAyMzQ0IDUuMDgzOTg0MzgsMTAuNDkwMjM0NCBMMTMuODg4NjcxOSwxNy4zNjcxODc1IEMxMy45OTIxODc1LDE3LjQ0NzI2NTYgMTQuMTQwNjI1LDE3LjM3NSAxNC4xNDA2MjUsMTcuMjQ0MTQwNiBMMTQuMTQwNjI1LDE1LjczNDM3NSBDMTQuMTQwNjI1LDE1LjYzODY3MTkgMTQuMDk1NzAzMSwxNS41NDY4NzUgMTQuMDIxNDg0NCwxNS40ODgyODEzIEw2Ljk5MDIzNDM4LDEwIEwxNC4wMjE0ODQ0LDQuNTA5NzY1NjMgQzE0LjA5NTcwMzEsNC40NTExNzE4OCAxNC4xNDA2MjUsNC4zNTkzNzUgMTQuMTQwNjI1LDQuMjYzNjcxODggWiIgaWQ9Iui3r+W+hCIgZmlsbD0iI0ZGRkZGRiI+PC9wYXRoPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+'); + +} + +.pswp .data-pswp-tool-bar .data-pswp-arrow-right::before { + width: 16px; + height: 16px; + background-size: 16px 16px; + background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDU1LjIgKDc4MTgxKSAtIGh0dHBzOi8vc2tldGNoYXBwLmNvbSAtLT4KICAgIDx0aXRsZT5yaWdodC1jaXJjbGU8L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZyBpZD0icmlnaHQtY2lyY2xlIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8ZyBpZD0icmlnaHQiIGZpbGwtcnVsZT0ibm9uemVybyI+CiAgICAgICAgICAgIDxyZWN0IGlkPSLnn6nlvaIiIGZpbGw9IiMwMDAwMDAiIG9wYWNpdHk9IjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCI+PC9yZWN0PgogICAgICAgICAgICA8cGF0aCBkPSJNMTQuOTU1MDc4MSw5LjUwNzgxMjUgTDYuMTUwMzkwNjIsMi42MzA4NTkzNyBDNi4wNDY4NzUsMi41NTA3ODEyNSA1Ljg5ODQzNzUsMi42MjMwNDY4NyA1Ljg5ODQzNzUsMi43NTM5MDYyNSBMNS44OTg0Mzc1LDQuMjYzNjcxODggQzUuODk4NDM3NSw0LjM1OTM3NSA1Ljk0MzM1OTM4LDQuNDUxMTcxODggNi4wMTc1NzgxMiw0LjUwOTc2NTYyIEwxMy4wNDg4MjgxLDEwIEw2LjAxNzU3ODEyLDE1LjQ5MDIzNDQgQzUuOTQxNDA2MjUsMTUuNTQ4ODI4MSA1Ljg5ODQzNzUsMTUuNjQwNjI1IDUuODk4NDM3NSwxNS43MzYzMjgxIEw1Ljg5ODQzNzUsMTcuMjQ2MDkzOCBDNS44OTg0Mzc1LDE3LjM3Njk1MzEgNi4wNDg4MjgxMiwxNy40NDkyMTg4IDYuMTUwMzkwNjIsMTcuMzY5MTQwNiBMMTQuOTU1MDc4MSwxMC40OTIxODc1IEMxNS4yNzUzOTA2LDEwLjI0MjE4NzUgMTUuMjc1MzkwNiw5Ljc1NzgxMjUgMTQuOTU1MDc4MSw5LjUwNzgxMjUgWiIgaWQ9Iui3r+W+hCIgZmlsbD0iI0ZGRkZGRiI+PC9wYXRoPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+'); + +} + +.pswp .data-pswp-tool-bar .data-pswp-zoom-in::before { + background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDU1LjEgKDc4MTM2KSAtIGh0dHBzOi8vc2tldGNoYXBwLmNvbSAtLT4KICAgIDx0aXRsZT56b29tIGluPC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGcgaWQ9Inpvb20taW4iIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPgogICAgICAgIDxnIGZpbGwtcnVsZT0ibm9uemVybyI+CiAgICAgICAgICAgIDxyZWN0IGlkPSLnn6nlvaIiIGZpbGw9IiMwMDAwMDAiIG9wYWNpdHk9IjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCI+PC9yZWN0PgogICAgICAgICAgICA8cGF0aCBkPSJNMTIuNDQxNDA2Myw4LjY1MjM0Mzc1IEwxMC4xMzY3MTg4LDguNjUyMzQzNzUgTDEwLjEzNjcxODgsNi4wMzUxNTYyNSBDMTAuMTM2NzE4OCw1Ljk0OTIxODc1IDEwLjA2NjQwNjMsNS44Nzg5MDYyNSA5Ljk4MDQ2ODc1LDUuODc4OTA2MjUgTDguODA4NTkzNzUsNS44Nzg5MDYyNSBDOC43MjI2NTYyNSw1Ljg3ODkwNjI1IDguNjUyMzQzNzUsNS45NDkyMTg3NSA4LjY1MjM0Mzc1LDYuMDM1MTU2MjUgTDguNjUyMzQzNzUsOC42NTIzNDM3NSBMNi4zNDc2NTYyNSw4LjY1MjM0Mzc1IEM2LjI2MTcxODc1LDguNjUyMzQzNzUgNi4xOTE0MDYyNSw4LjcyMjY1NjI1IDYuMTkxNDA2MjUsOC44MDg1OTM3NSBMNi4xOTE0MDYyNSw5Ljk4MDQ2ODc1IEM2LjE5MTQwNjI1LDEwLjA2NjQwNjMgNi4yNjE3MTg3NSwxMC4xMzY3MTg4IDYuMzQ3NjU2MjUsMTAuMTM2NzE4OCBMOC42NTIzNDM3NSwxMC4xMzY3MTg4IEw4LjY1MjM0Mzc1LDEyLjc1MzkwNjMgQzguNjUyMzQzNzUsMTIuODM5ODQzNyA4LjcyMjY1NjI1LDEyLjkxMDE1NjMgOC44MDg1OTM3NSwxMi45MTAxNTYzIEw5Ljk4MDQ2ODc1LDEyLjkxMDE1NjMgQzEwLjA2NjQwNjMsMTIuOTEwMTU2MyAxMC4xMzY3MTg4LDEyLjgzOTg0MzcgMTAuMTM2NzE4OCwxMi43NTM5MDYzIEwxMC4xMzY3MTg4LDEwLjEzNjcxODggTDEyLjQ0MTQwNjMsMTAuMTM2NzE4OCBDMTIuNTI3MzQzNywxMC4xMzY3MTg4IDEyLjU5NzY1NjMsMTAuMDY2NDA2MyAxMi41OTc2NTYzLDkuOTgwNDY4NzUgTDEyLjU5NzY1NjMsOC44MDg1OTM3NSBDMTIuNTk3NjU2Myw4LjcyMjY1NjI1IDEyLjUyNzM0MzcsOC42NTIzNDM3NSAxMi40NDE0MDYzLDguNjUyMzQzNzUgWiBNMTcuOTg4MjgxMywxNi45MzM1OTM4IEwxNS4xMzY3MTg4LDE0LjA4MjAzMTMgQzE3LjUyMTQ4NDQsMTEuMTczODI4MSAxNy4zNTU0Njg4LDYuODY1MjM0MzggMTQuNjI4OTA2Myw0LjE0MDYyNSBDMTEuNzM4MjgxMywxLjI0ODA0Njg4IDcuMDQyOTY4NzUsMS4yNDgwNDY4OCA0LjE0MDYyNSw0LjE0MDYyNSBDMS4yNDgwNDY4OCw3LjA0Mjk2ODc1IDEuMjQ4MDQ2ODgsMTEuNzM4MjgxMyA0LjE0MDYyNSwxNC42Mjg5MDYzIEM2Ljg2NTIzNDM4LDE3LjM1NTQ2ODggMTEuMTczODI4MSwxNy41MjE0ODQ0IDE0LjA4MjAzMTMsMTUuMTM2NzE4OCBMMTYuOTMzNTkzOCwxNy45ODgyODEzIEMxNi45OTYwOTM4LDE4LjA0Mjk2ODggMTcuMDk1NzAzMSwxOC4wNDI5Njg4IDE3LjE0ODQzNzUsMTcuOTg4MjgxMyBMMTcuOTg4MjgxMywxNy4xNDg0Mzc1IEMxOC4wNDI5Njg4LDE3LjA5NTcwMzEgMTguMDQyOTY4OCwxNi45OTYwOTM4IDE3Ljk4ODI4MTMsMTYuOTMzNTkzOCBaIE0xMy41OTM3NSwxMy41OTM3NSBDMTEuMjczNDM3NSwxNS45MTIxMDk0IDcuNTE1NjI1LDE1LjkxMjEwOTQgNS4xOTUzMTI1LDEzLjU5Mzc1IEMyLjg3Njk1MzEzLDExLjI3MzQzNzUgMi44NzY5NTMxMyw3LjUxNTYyNSA1LjE5NTMxMjUsNS4xOTUzMTI1IEM3LjUxNTYyNSwyLjg3Njk1MzEzIDExLjI3MzQzNzUsMi44NzY5NTMxMyAxMy41OTM3NSw1LjE5NTMxMjUgQzE1LjkxMjEwOTQsNy41MTU2MjUgMTUuOTEyMTA5NCwxMS4yNzM0Mzc1IDEzLjU5Mzc1LDEzLjU5Mzc1IFoiIGlkPSLlvaLnirYiIGZpbGw9IiNGRkZGRkYiPjwvcGF0aD4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg=='); + +} + +.pswp .data-pswp-tool-bar .data-pswp-zoom-out::before { + background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDU1LjEgKDc4MTM2KSAtIGh0dHBzOi8vc2tldGNoYXBwLmNvbSAtLT4KICAgIDx0aXRsZT56b29tIG91dDwvdGl0bGU+CiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4KICAgIDxnIGlkPSJ6b29tLW91dCIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgZmlsbC1ydWxlPSJub256ZXJvIj4KICAgICAgICAgICAgPHJlY3QgaWQ9IuefqeW9oiIgZmlsbD0iIzAwMDAwMCIgb3BhY2l0eT0iMCIgeD0iMCIgeT0iMCIgd2lkdGg9IjIwIiBoZWlnaHQ9IjIwIj48L3JlY3Q+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0xMi40NDE0MDYzLDguNjUyMzQzNzUgTDYuMzQ3NjU2MjUsOC42NTIzNDM3NSBDNi4yNjE3MTg3NSw4LjY1MjM0Mzc1IDYuMTkxNDA2MjUsOC43MjI2NTYyNSA2LjE5MTQwNjI1LDguODA4NTkzNzUgTDYuMTkxNDA2MjUsOS45ODA0Njg3NSBDNi4xOTE0MDYyNSwxMC4wNjY0MDYzIDYuMjYxNzE4NzUsMTAuMTM2NzE4OCA2LjM0NzY1NjI1LDEwLjEzNjcxODggTDEyLjQ0MTQwNjMsMTAuMTM2NzE4OCBDMTIuNTI3MzQzNywxMC4xMzY3MTg4IDEyLjU5NzY1NjMsMTAuMDY2NDA2MyAxMi41OTc2NTYzLDkuOTgwNDY4NzUgTDEyLjU5NzY1NjMsOC44MDg1OTM3NSBDMTIuNTk3NjU2Myw4LjcyMjY1NjI1IDEyLjUyNzM0MzcsOC42NTIzNDM3NSAxMi40NDE0MDYzLDguNjUyMzQzNzUgWiBNMTcuOTg4MjgxMywxNi45MzM1OTM4IEwxNS4xMzY3MTg4LDE0LjA4MjAzMTMgQzE3LjUyMTQ4NDQsMTEuMTczODI4MSAxNy4zNTU0Njg4LDYuODY1MjM0MzggMTQuNjI4OTA2Myw0LjE0MDYyNSBDMTEuNzM4MjgxMywxLjI0ODA0Njg4IDcuMDQyOTY4NzUsMS4yNDgwNDY4OCA0LjE0MDYyNSw0LjE0MDYyNSBDMS4yNDgwNDY4OCw3LjA0Mjk2ODc1IDEuMjQ4MDQ2ODgsMTEuNzM4MjgxMyA0LjE0MDYyNSwxNC42Mjg5MDYzIEM2Ljg2NTIzNDM4LDE3LjM1NTQ2ODggMTEuMTczODI4MSwxNy41MjE0ODQ0IDE0LjA4MjAzMTMsMTUuMTM2NzE4OCBMMTYuOTMzNTkzOCwxNy45ODgyODEzIEMxNi45OTYwOTM4LDE4LjA0Mjk2ODggMTcuMDk1NzAzMSwxOC4wNDI5Njg4IDE3LjE0ODQzNzUsMTcuOTg4MjgxMyBMMTcuOTg4MjgxMywxNy4xNDg0Mzc1IEMxOC4wNDI5Njg4LDE3LjA5NTcwMzEgMTguMDQyOTY4OCwxNi45OTYwOTM4IDE3Ljk4ODI4MTMsMTYuOTMzNTkzOCBaIE0xMy41OTM3NSwxMy41OTM3NSBDMTEuMjczNDM3NSwxNS45MTIxMDk0IDcuNTE1NjI1LDE1LjkxMjEwOTQgNS4xOTUzMTI1LDEzLjU5Mzc1IEMyLjg3Njk1MzEzLDExLjI3MzQzNzUgMi44NzY5NTMxMyw3LjUxNTYyNSA1LjE5NTMxMjUsNS4xOTUzMTI1IEM3LjUxNTYyNSwyLjg3Njk1MzEzIDExLjI3MzQzNzUsMi44NzY5NTMxMyAxMy41OTM3NSw1LjE5NTMxMjUgQzE1LjkxMjEwOTQsNy41MTU2MjUgMTUuOTEyMTA5NCwxMS4yNzM0Mzc1IDEzLjU5Mzc1LDEzLjU5Mzc1IFoiIGlkPSLlvaLnirYiIGZpbGw9IiNGRkZGRkYiPjwvcGF0aD4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg=='); + +} + +.pswp .data-pswp-tool-bar .data-pswp-origin-size::before { + background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDU1LjEgKDc4MTM2KSAtIGh0dHBzOi8vc2tldGNoYXBwLmNvbSAtLT4KICAgIDx0aXRsZT5hY3R1YWwgc2l6ZTwvdGl0bGU+CiAgICA8ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz4KICAgIDxnIGlkPSJhY3R1YWwtc2l6ZSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgaWQ9Iue8lue7hC0yIj4KICAgICAgICAgICAgPGcgaWQ9Iue8lue7hCI+CiAgICAgICAgICAgICAgICA8cmVjdCBpZD0i55+p5b2iIiB4PSIwIiB5PSIwIiB3aWR0aD0iMjAiIGhlaWdodD0iMjAiPjwvcmVjdD4KICAgICAgICAgICAgPC9nPgogICAgICAgICAgICA8cGF0aCBkPSJNNS45Mzc1LDE1LjM5MDYyNSBMNC4xMTY3NzYzMiwxNS4zOTA2MjUgTDQuMTE2Nzc2MzIsNy40NDU3MDk3NSBMMS44NzUsNy40NDU3MDk3NSBMMS44NzUsNi40MjA3NTQ5NCBDMi42NjcxNzc2LDYuNDE3MjcwNjUgMy4zMDI5MzEwOCw2LjI2Mzg2MzM2IDMuNzc5NjI2MzcsNS45NTM5MDU1MyBDNC4yMzAwNDk4Miw1LjY2MTAzMDIxIDQuNTI4ODg0MSw1LjIwODg0ODQxIDQuNjc2MDQ5NzgsNC42MDkzNzUgTDUuOTM3NSw0LjYwOTM3NSBMNS45Mzc1LDE1LjM5MDYyNSBaIE05LjY4NzUsNy41MzkwNjI1IEwxMS40MDYyNSw3LjUzOTA2MjUgTDExLjQwNjI1LDkuNTcwMzEyNSBMOS42ODc1LDkuNTcwMzEyNSBMOS42ODc1LDcuNTM5MDYyNSBaIE05LjY4NzUsMTMuMzU5Mzc1IEwxMS40MDYyNSwxMy4zNTkzNzUgTDExLjQwNjI1LDE1LjM5MDYyNSBMOS42ODc1LDE1LjM5MDYyNSBMOS42ODc1LDEzLjM1OTM3NSBaIE0xNy41LDE1LjM5MDYyNSBMMTcuNSw0LjYwOTM3NSBMMTYuMjM4NTQ5OCw0LjYwOTM3NSBDMTYuMDkxMzg0MSw1LjIwODg0ODQxIDE1Ljc5MjU0OTgsNS42NjEwMzAyMSAxNS4zNDIxMjY0LDUuOTUzOTA1NTMgQzE0Ljg2NTQzMTEsNi4yNjM4NjMzNiAxNC4yMjk2Nzc2LDYuNDE3MjcwNjUgMTMuNDM3NSw2LjQyMDc1NDk0IEwxMy40Mzc1LDcuNDQ1NzA5NzUgTDE1LjY3OTI3NjMsNy40NDU3MDk3NSBMMTUuNjc5Mjc2MywxNS4zOTA2MjUgTDE3LjUsMTUuMzkwNjI1IFoiIGlkPSLlvaLnirbnu5PlkIgiIGZpbGw9IiNGRkZGRkYiIGZpbGwtcnVsZT0ibm9uemVybyI+PC9wYXRoPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+'); + +} + +.pswp .data-pswp-tool-bar .data-pswp-best-size::before { + background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMjBweCIgaGVpZ2h0PSIyMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDU1LjIgKDc4MTgxKSAtIGh0dHBzOi8vc2tldGNoYXBwLmNvbSAtLT4KICAgIDx0aXRsZT5maXQgc2NyZWVuPC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGcgaWQ9ImZpdC1zY3JlZW4iIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPgogICAgICAgIDxnIGlkPSJhcnJhd3NhbHQiIGZpbGwtcnVsZT0ibm9uemVybyI+CiAgICAgICAgICAgIDxyZWN0IGlkPSLnn6nlvaIiIGZpbGw9IiMwMDAwMDAiIG9wYWNpdHk9IjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCI+PC9yZWN0PgogICAgICAgICAgICA8cGF0aCBkPSJNMTYuNjk5MjE4OCwzLjEyNjk1MzEyIEwxMy4wMDM5MDYzLDMuNTg1OTM3NSBDMTIuODc1LDMuNjAxNTYyNSAxMi44MjIyNjU2LDMuNzU3ODEyNSAxMi45MTIxMDk0LDMuODQ5NjA5MzcgTDEzLjk4MDQ2ODgsNC45MTc5Njg3NSBMMTAuOTgyNDIxOSw3LjkxNjAxNTYyIEMxMC45MjE4NzUsNy45NzY1NjI1IDEwLjkyMTg3NSw4LjA3NjE3MTg3IDEwLjk4MjQyMTksOC4xMzY3MTg3NSBMMTEuODYzMjgxMyw5LjAxNzU3ODEyIEMxMS45MjM4MjgxLDkuMDc4MTI1IDEyLjAyMzQzNzUsOS4wNzgxMjUgMTIuMDgzOTg0NCw5LjAxNzU3ODEyIEwxNS4wODM5ODQ0LDYuMDE3NTc4MTIgTDE2LjE1MjM0MzgsNy4wODU5Mzc1IEMxNi4yNDQxNDA2LDcuMTc3NzM0MzcgMTYuNDAwMzkwNiw3LjEyMzA0Njg3IDE2LjQxNjAxNTYsNi45OTQxNDA2MiBMMTYuODczMDQ2OSwzLjMwMDc4MTI1IEMxNi44ODY3MTg4LDMuMTk5MjE4NzUgMTYuODAwNzgxMywzLjExMzI4MTI1IDE2LjY5OTIxODgsMy4xMjY5NTMxMiBaIE04LjEzNjcxODc1LDEwLjk4MjQyMTkgQzguMDc2MTcxODgsMTAuOTIxODc1IDcuOTc2NTYyNSwxMC45MjE4NzUgNy45MTYwMTU2MywxMC45ODI0MjE5IEw0LjkxNzk2ODc1LDEzLjk4MjQyMTkgTDMuODQ5NjA5MzgsMTIuOTE0MDYyNSBDMy43NTc4MTI1LDEyLjgyMjI2NTYgMy42MDE1NjI1LDEyLjg3Njk1MzEgMy41ODU5Mzc1LDEzLjAwNTg1OTQgTDMuMTI2OTUzMTMsMTYuNjk5MjE4OCBDMy4xMTUyMzQzOCwxNi44MDA3ODEyIDMuMTk5MjE4NzUsMTYuODg0NzY1NiAzLjMwMDc4MTI1LDE2Ljg3MzA0NjkgTDYuOTk2MDkzNzUsMTYuNDE0MDYyNSBDNy4xMjUsMTYuMzk4NDM3NSA3LjE3NzczNDM4LDE2LjI0MjE4NzUgNy4wODc4OTA2MywxNi4xNTAzOTA2IEw2LjAxOTUzMTI1LDE1LjA4MjAzMTIgTDkuMDE5NTMxMjUsMTIuMDgyMDMxMiBDOS4wODAwNzgxMywxMi4wMjE0ODQ0IDkuMDgwMDc4MTMsMTEuOTIxODc1IDkuMDE5NTMxMjUsMTEuODYxMzI4MSBMOC4xMzY3MTg3NSwxMC45ODI0MjE5IEw4LjEzNjcxODc1LDEwLjk4MjQyMTkgWiIgaWQ9IuW9oueKtiIgZmlsbD0iI0ZGRkZGRiI+PC9wYXRoPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+'); + +} + +.pswp .data-pswp-tool-bar .btn:not(.disable):hover { + cursor: pointer; + +} + +.pswp .data-pswp-tool-bar .btn:not(.disable):hover::before { + opacity: 1; + +} + +.pswp .data-pswp-tool-bar .btn.disable { + background: none; + +} + +.pswp .data-pswp-tool-bar .btn.activated { + background: #454545; + +} + +.pswp .data-pswp-tool-bar .btn.activated::before { + opacity: 1; + +} + +.pswp .data-pswp-tool-bar .btn.disable::before { + opacity: 0.25; + +} + +.pswp .data-pswp-tool-bar .disable:hover { + cursor: not-allowed; + +} + +.pswp .data-pswp-tool-bar .pswp-toolbar-content { + display: inline-block; + width: auto; + background: #252525; + border-radius: 4px; + padding: 12px; + +} + +.pswp .data-pswp-tool-bar .pswp-toolbar-content .data-pswp-counter { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + display: inline-block; + font-size: 16px; + vertical-align: top; + line-height: 34px; + color: #DEDEDE; + margin: 0 2px; + +} + +.pswp.data-pswp-mobile .data-pswp-tool-bar { + display: none; + +} + +.pswp__ui { + -webkit-font-smoothing: auto; + visibility: visible; + opacity: 1; + z-index: 1550; +} + +.pswp__button { + width: 44px; + height: 44px; + position: relative; + background: none; + cursor: pointer; + overflow: visible; + display: block; + border: 0; + padding: 0; + margin: 0; + float: right; + opacity: 0.75; + -webkit-transition: opacity 0.2s; + transition: opacity 0.2s; + box-shadow: none; +} + +.pswp-fade-out { + opacity: 0; + animation: fadeOut 0.333s; +} + +.pswp .data-pswp-button-close { + margin: 32px; + margin-right: 0px; + width: 40px; + height: 40px; + z-index: 999999; + position: relative; + background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iNDBweCIgaGVpZ2h0PSI0MHB4IiB2aWV3Qm94PSIwIDAgNDAgNDAiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDU1LjIgKDc4MTgxKSAtIGh0dHBzOi8vc2tldGNoYXBwLmNvbSAtLT4KICAgIDx0aXRsZT5jbG9zZS1kZWZhdWx0PC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGcgaWQ9Iuafpeeci+WbvueJh+S8mOWMljQuMCIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+CiAgICAgICAgPGcgaWQ9IueUu+advyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTQ4LjAwMDAwMCwgLTczNS4wMDAwMDApIj4KICAgICAgICAgICAgPGcgaWQ9ImNsb3NlLWRlZmF1bHQiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQ4LjAwMDAwMCwgNzM1LjAwMDAwMCkiPgogICAgICAgICAgICAgICAgPHJlY3QgaWQ9IuefqeW9oiIgZmlsbD0iIzFBMUExQSIgb3BhY2l0eT0iMC4zNSIgeD0iMCIgeT0iMCIgd2lkdGg9IjQwIiBoZWlnaHQ9IjQwIiByeD0iNCI+PC9yZWN0PgogICAgICAgICAgICAgICAgPGcgaWQ9ImNsb3NlIiBvcGFjaXR5PSIwLjY1IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg4LjAwMDAwMCwgOC4wMDAwMDApIj4KICAgICAgICAgICAgICAgICAgICA8cmVjdCBpZD0i55+p5b2iIiBmaWxsPSIjMDAwMDAwIiBmaWxsLXJ1bGU9Im5vbnplcm8iIG9wYWNpdHk9IjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PC9yZWN0PgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xMy4yMTQwNjI1LDEyIEwxOS4zNjY0MDYyLDQuNjY2NDA2MjUgQzE5LjQ2OTUzMTIsNC41NDQ1MzEyNSAxOS4zODI4MTI1LDQuMzU5Mzc1IDE5LjIyMzQzNzUsNC4zNTkzNzUgTDE3LjM1MzEyNSw0LjM1OTM3NSBDMTcuMjQyOTY4Nyw0LjM1OTM3NSAxNy4xMzc1LDQuNDA4NTkzNzUgMTcuMDY0ODQzNyw0LjQ5Mjk2ODc1IEwxMS45OTA2MjUsMTAuNTQyMTg3NSBMNi45MTY0MDYyNSw0LjQ5Mjk2ODc1IEM2Ljg0NjA5Mzc1LDQuNDA4NTkzNzUgNi43NDA2MjUsNC4zNTkzNzUgNi42MjgxMjUsNC4zNTkzNzUgTDQuNzU3ODEyNSw0LjM1OTM3NSBDNC41OTg0Mzc1LDQuMzU5Mzc1IDQuNTExNzE4NzUsNC41NDQ1MzEyNSA0LjYxNDg0Mzc1LDQuNjY2NDA2MjUgTDEwLjc2NzE4NzUsMTIgTDQuNjE0ODQzNzUsMTkuMzMzNTkzNyBDNC41MTE3MTg3NSwxOS40NTU0Njg4IDQuNTk4NDM3NSwxOS42NDA2MjUgNC43NTc4MTI1LDE5LjY0MDYyNSBMNi42MjgxMjUsMTkuNjQwNjI1IEM2LjczODI4MTI1LDE5LjY0MDYyNSA2Ljg0Mzc1LDE5LjU5MTQwNjIgNi45MTY0MDYyNSwxOS41MDcwMzEyIEwxMS45OTA2MjUsMTMuNDU3ODEyNSBMMTcuMDY0ODQzNywxOS41MDcwMzEyIEMxNy4xMzUxNTYyLDE5LjU5MTQwNjIgMTcuMjQwNjI1LDE5LjY0MDYyNSAxNy4zNTMxMjUsMTkuNjQwNjI1IEwxOS4yMjM0Mzc1LDE5LjY0MDYyNSBDMTkuMzgyODEyNSwxOS42NDA2MjUgMTkuNDY5NTMxMiwxOS40NTU0Njg4IDE5LjM2NjQwNjIsMTkuMzMzNTkzNyBMMTMuMjE0MDYyNSwxMiBaIiBpZD0i6Lev5b6EIiBmaWxsPSIjRkZGRkZGIj48L3BhdGg+CiAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg=='); +} + diff --git a/editor/src/plugins/image/component/pswp/index.ts b/editor/src/plugins/image/component/pswp/index.ts new file mode 100644 index 0000000..885e201 --- /dev/null +++ b/editor/src/plugins/image/component/pswp/index.ts @@ -0,0 +1,338 @@ +import { EventEmitter2 } from 'eventemitter2'; +import PhotoSwipe from 'photoswipe'; +import PhotoSwipeUI from 'photoswipe/dist/photoswipe-ui-default'; +import { $, EditorInterface, isHotkey, isMobile, NodeInterface } from '@aomao/engine'; +import { PswpInterface } from '../../types'; +import Zoom from './zoom'; +import 'photoswipe/dist/photoswipe.css'; +import './index.css'; + +class Pswp extends EventEmitter2 implements PswpInterface { + private editor: EditorInterface; + private options: PhotoSwipeUI.Options; + private timeouts: any = []; + private pswpUI?: PhotoSwipe; + private zoom?: number; + private isDestroy = true; + private zoomUI: Zoom; + root: NodeInterface; + barUI: NodeInterface; + closeUI: NodeInterface; + + constructor(editor: EditorInterface, options?: PhotoSwipeUI.Options) { + super(); + this.editor = editor; + this.options = { + shareEl: false, + fullscreenEl: false, + zoomEl: false, + history: false, + closeOnScroll: false, + preloaderEl: false, + captionEl: false, + counterEl: false, + clickToCloseNonZoomable: false, + showAnimationDuration: 0, + hideAnimationDuration: 0, + closeOnVerticalDrag: isMobile, + tapToClose: true, + bgOpacity: 0.8, + barsSize: { + top: 44, + bottom: 80, + }, + ...options, + }; + + this.isDestroy = true; + this.root = this.renderTemplate(); + this.barUI = this.root.find('.data-pswp-custom-top-bar'); + this.closeUI = this.root.find('.data-pswp-button-close'); + $(document.body).append(this.root); + this.zoomUI = new Zoom(editor, this); + this.zoomUI.render(); + this.reset(); + this.bindClickEvent(); + window.addEventListener('resize', this.reset); + } + + renderTemplate() { + const root = $(` + `); + return root; + } + + reset = () => { + this.root.removeClass(isMobile ? 'data-pswp-mobile' : 'data-pswp-pc'); + this.root.addClass(isMobile ? 'data-pswp-mobile' : 'data-pswp-pc'); + this.unbindKeyboardEvnet(); + this.unbindControllerFadeInAndOut(); + if (!isMobile) { + this.bindKeyboardEvnet(); + this.bindControllerFadeInAndOut(); + } + }; + + onBarMouseEnter = () => { + this.removeFadeOut(this.barUI, 'barFadeInAndOut'); + this.removeFadeOut(this.closeUI, 'closeFadeInAndOut'); + }; + + onBarMouseLeave = () => { + this.fadeOut(this.barUI, 'barFadeInAndOut'); + this.fadeOut(this.closeUI, 'closeFadeInAndOut'); + }; + + onCloseMouseEnter = () => { + this.removeFadeOut(this.barUI, 'barFadeInAndOut'); + this.removeFadeOut(this.closeUI, 'closeFadeInAndOut'); + }; + + onCloseMouseLeave = () => { + this.fadeOut(this.barUI, 'barFadeInAndOut'); + this.fadeOut(this.closeUI, 'closeFadeInAndOut'); + }; + + bindControllerFadeInAndOut() { + this.barUI.on('mouseenter', this.onBarMouseEnter); + this.barUI.on('mouseleave', this.onBarMouseLeave); + this.closeUI.on('mouseenter', this.onCloseMouseEnter); + this.closeUI.on('mouseleave', this.onCloseMouseLeave); + } + + unbindControllerFadeInAndOut() { + this.barUI.off('mouseenter', this.onBarMouseEnter); + this.barUI.off('mouseleave', this.onBarMouseLeave); + this.closeUI.off('mouseenter', this.onCloseMouseEnter); + this.closeUI.off('mouseleave', this.onCloseMouseLeave); + } + + removeFadeOut(node: NodeInterface, id: string) { + if (this.timeouts[id]) { + clearTimeout(this.timeouts[id]); + } + node.removeClass('pswp-fade-out'); + } + + fadeOut(node: NodeInterface, id: string) { + if (this.timeouts[id]) { + clearTimeout(this.timeouts[id]); + } + this.timeouts[id] = setTimeout(() => { + node.addClass('pswp-fade-out'); + }, 3000); + } + + bindClickEvent() { + const onClick = (event: MouseEvent | TouchEvent) => { + const node = window.TouchEvent && event instanceof TouchEvent ? $(event.touches[0].target) : $(event.target || []); + if (node.hasClass('pswp__img')) { + setTimeout(() => { + this.zoom = undefined; + this.afterZoom(); + }, 366); + } + if (node.hasClass('pswp__bg') || node.hasClass('data-pswp-tool-bar')) { + this.close(); + } + }; + this.root.on('click', onClick, { passive: true }); + this.closeUI.on('click', this.close); + } + + prev() { + this.pswpUI?.prev(); + } + + next() { + this.pswpUI?.next(); + } + + renderCounter() { + this.barUI.find('.data-pswp-counter').html(`${(this.pswpUI?.getCurrentIndex() || 0) + 1} / ${this.pswpUI?.items.length || ''}`); + } + + getCurrentZoomLevel() { + return (this.zoom && +this.zoom.toFixed(2)) || (this.pswpUI && +this.pswpUI.getZoomLevel().toFixed(2)) || 0; + } + + zoomTo(zoom: number) { + if (!this.pswpUI) return; + this.pswpUI.zoomTo( + zoom, + { + x: this.pswpUI.viewportSize.x / 2, + y: this.pswpUI.viewportSize.y / 2, + }, + 100 + ); + this.zoom = zoom; + this.afterZoom(); + } + + zoomIn() { + const zoom = this.getCurrentZoomLevel(); + let newZoom = (zoom || 0) + 0.2; + if (5 !== zoom) { + if (newZoom > 5) newZoom = 5; + this.zoomTo(newZoom); + } + } + + zoomOut() { + const zoom = this.getCurrentZoomLevel(); + if (0.05 !== zoom && zoom !== undefined) { + let newZoom = zoom - 0.2; + if (0.05 > newZoom) { + newZoom = 0.05; + } + this.zoomTo(newZoom); + } + } + + onKeyboardEvent = (event: KeyboardEvent) => { + if ((event.metaKey || event.ctrlKey) && 187 === event.keyCode) { + event.preventDefault(); + this.zoomIn(); + } + if (isHotkey('mod+-', event)) { + event.preventDefault(); + this.zoomOut(); + } + }; + + bindKeyboardEvnet() { + this.root.on('keydown', this.onKeyboardEvent); + } + + unbindKeyboardEvnet() { + this.root.off('keydown', this.onKeyboardEvent); + } + + zoomToOriginSize() { + this.zoomTo(1); + } + + zoomToBestSize() { + const zoom = this.getInitialZoomLevel(); + if (!zoom) return; + this.zoomTo(zoom); + } + + updateCursor() { + const { root } = this; + const currentZoomLevel = this.getCurrentZoomLevel(); + const initialZoomLevel = this.getInitialZoomLevel(); + if (currentZoomLevel === 1) { + root.addClass('pswp--zoomed-in'); + } else if (initialZoomLevel === initialZoomLevel) { + root.removeClass('pswp--zoomed-in'); + } + } + + getInitialZoomLevel() { + if (!this.pswpUI) return 0; + return +(this.pswpUI.currItem.initialZoomLevel?.toFixed(2) || 0); + } + + afterZoom() { + this.updateCursor(); + this.emit('afterzoom'); + } + + getCount() { + return this.pswpUI?.items.length || 0; + } + + afterChange() { + if (!isMobile) { + const initialZoomLevel = this.getInitialZoomLevel(); + this.renderCounter(); + this.zoom = initialZoomLevel; + setTimeout(() => { + this.afterZoom(); + }, 100); + this.emit('afterchange'); + this.zoom = this.getInitialZoomLevel(); + } + this.setWhiteBackground(); + } + + bindPswpEvent() { + this.pswpUI?.listen('afterChange', () => { + this.afterChange(); + }); + this.pswpUI?.listen('destroy', () => { + this.isDestroy = true; + }); + this.pswpUI?.listen('resize', () => { + this.emit('resize'); + }); + this.pswpUI?.listen('imageLoadComplete', () => { + this.setWhiteBackground(); + }); + } + + setWhiteBackground() { + this.root.find('.pswp__img').each(img => { + const node = img as HTMLImageElement; + if (node.complete) { + node.style.background = 'white'; + node.style['boxShadow'] = '0 0 10px rgba(0, 0, 0, 0.5)'; + } else { + node.onload = () => { + node.style.background = 'white'; + node.style['boxShadow'] = '0 0 10px rgba(0, 0, 0, 0.5)'; + }; + } + }); + } + + open(items: Array, index: number) { + if (true === this.isDestroy) { + const { root } = this; + const pswp = new PhotoSwipe(this.root.get()!, PhotoSwipeUI, items, { + index, + ...this.options, + }); + pswp.items = items; + pswp.init(); + this.pswpUI = pswp; + this.isDestroy = false; + if (!isMobile) { + this.barUI.removeClass('pswp-fade-out'); + this.fadeOut(this.barUI, 'barFadeInAndOut'); + this.closeUI.removeClass('pswp-fade-out'); + this.fadeOut(this.closeUI, 'closeFadeInAndOut'); + } + root.removeClass('pswp-fade-in'); + root.addClass('pswp-fade-in'); + this.afterChange(); + this.bindPswpEvent(); + } + } + + close = () => { + this.pswpUI?.close(); + }; + + destroy() { + window.removeEventListener('resize', this.reset); + this.close(); + } +} + +export default Pswp; diff --git a/editor/src/plugins/image/component/pswp/zoom.ts b/editor/src/plugins/image/component/pswp/zoom.ts new file mode 100644 index 0000000..ac3d9ea --- /dev/null +++ b/editor/src/plugins/image/component/pswp/zoom.ts @@ -0,0 +1,137 @@ +import { PswpInterface } from '../../types'; +import { $, EditorInterface, Tooltip } from '@aomao/engine'; + +class Zoom { + private pswp: PswpInterface; + private editor: EditorInterface; + prevStatus = 'default'; + nextStatus = 'default'; + zoomInStatus = 'default'; + zoomOutStatus = 'default'; + originSizeStatus = 'default'; + bestSizeStatus = 'default'; + + constructor(editor: EditorInterface, pswp: PswpInterface) { + this.editor = editor; + this.pswp = pswp; + } + + init() { + this.pswp.on('afterzoom', () => { + this.afterZoom(); + }); + + this.pswp.on('afterchange', () => { + this.afterChange(); + }); + + this.pswp.on('resize', () => { + setTimeout(() => { + this.afterChange(); + this.afterZoom(); + }, 333); + }); + this.render(); + } + + renderTemplate() { + const root = $(` +
+
+
+ `); + + const toolbarContent = root.find('.pswp-toolbar-content'); + + const lang: any = this.editor.language.get('image'); + + toolbarContent.append( + this.renderBtn('arrow-left', lang['prev'], this.prevStatus, () => { + if ('disable' !== this.prevStatus) this.pswp.prev(); + }) + ); + + toolbarContent.append(``); + + toolbarContent.append( + this.renderBtn('arrow-right', lang['next'], this.nextStatus, () => { + if ('disable' !== this.nextStatus) this.pswp.next(); + }) + ); + + toolbarContent.append(``); + + toolbarContent.append( + this.renderBtn('zoom-in', lang['zoomIn'], this.zoomInStatus, () => { + if ('disable' !== this.zoomInStatus) this.pswp.zoomIn(); + }) + ); + + toolbarContent.append( + this.renderBtn('zoom-out', lang['zoomOut'], this.zoomOutStatus, () => { + if ('disable' !== this.zoomOutStatus) this.pswp.zoomOut(); + }) + ); + + toolbarContent.append( + this.renderBtn('origin-size', lang['originSize'], this.originSizeStatus, () => { + if ('disable' !== this.originSizeStatus) this.pswp.zoomToOriginSize(); + }) + ); + + toolbarContent.append( + this.renderBtn('best-size', lang['bestSize'], this.bestSizeStatus, () => { + if ('disable' !== this.bestSizeStatus) this.pswp.zoomToBestSize(); + }) + ); + + return root; + } + + afterZoom() { + const currentLevel = this.pswp.getCurrentZoomLevel(); + const initLevel = this.pswp.getInitialZoomLevel(); + let status = 'default'; + if (currentLevel === initLevel) { + status = 'activated'; + } + if (1 === initLevel) { + status = 'disable'; + } + this.zoomOutStatus = 0.05 === currentLevel ? 'disable' : 'default'; + this.zoomInStatus = 5 === currentLevel ? 'disable' : 'default'; + this.originSizeStatus = 1 === currentLevel ? 'activated' : 'default'; + this.bestSizeStatus = status; + this.render(); + } + + afterChange() { + const count = this.pswp.getCount(); + this.nextStatus = 1 === count ? 'disable' : 'default'; + this.prevStatus = 1 === count ? 'disable' : 'default'; + this.render(); + } + + renderBtn(zoomClass: string, title: string, status: string, onClick: () => void) { + const btn = $(``); + btn.on('mouseenter', () => { + Tooltip.show(btn, title); + }); + btn.on('mouseleave', () => { + Tooltip.hide(); + }); + btn.on('mousedown', e => { + e.stopPropagation(); + Tooltip.hide(); + }); + btn.on('click', onClick); + return btn; + } + + render() { + this.pswp.barUI.empty(); + this.pswp.barUI.append(this.renderTemplate()); + } +} + +export default Zoom; diff --git a/editor/src/plugins/image/index.ts b/editor/src/plugins/image/index.ts new file mode 100644 index 0000000..753f211 --- /dev/null +++ b/editor/src/plugins/image/index.ts @@ -0,0 +1,129 @@ +import { $, CardInterface, CardType, CARD_KEY, CARD_TYPE_KEY, CARD_VALUE_KEY, decodeCardValue, NodeInterface, Plugin, PluginEntry, READY_CARD_KEY } from '@aomao/engine'; +import ImageComponent, { ImageValue } from './component'; +import ImageUploader from './uploader'; +import { ImageUploaderOptions } from './uploader'; +import locales from './locales'; +import { ImageOptions } from './types'; + +const PARSE_HTML = 'parse:html'; + +export default class extends Plugin { + static get pluginName() { + return 'image'; + } + + init() { + const editor = this.editor; + editor.language.add(locales); + editor.on(PARSE_HTML, this.parseHtml); + } + + execute(status: 'uploading' | 'done' | 'error', src: string, alt?: string): void { + const value: ImageValue = { + status, + src, + alt, + }; + if (status === 'error') { + value.src = ''; + value.message = src; + } + this.editor.card.insert('image', value); + } + + async waiting(callback?: (name: string, card?: CardInterface, ...args: any) => boolean | number | void): Promise { + const { card } = this.editor; + // 检测单个组件 + const check = (component: CardInterface) => { + return component.root.inEditor() && component.name === ImageComponent.cardName && (component as ImageComponent).getValue()?.status === 'uploading'; + }; + // 找到不合格的组件 + const find = (): CardInterface | undefined => { + return card.components.find(check); + }; + const waitCheck = (component: CardInterface): Promise => { + let time = 60000; + return new Promise((resolve, reject) => { + if (callback) { + const result = callback((this.constructor as PluginEntry).pluginName, component); + if (result === false) { + return reject({ + name: (this.constructor as PluginEntry).pluginName, + card: component, + }); + } else if (typeof result === 'number') { + time = result; + } + } + const beginTime = new Date().getTime(); + const now = new Date().getTime(); + const timeout = () => { + if (now - beginTime >= time) return resolve(); + setTimeout(() => { + if (check(component)) timeout(); + else resolve(); + }, 10); + }; + timeout(); + }); + }; + return new Promise((resolve, reject) => { + const component = find(); + const wait = (component: CardInterface) => { + waitCheck(component) + .then(() => { + const next = find(); + if (next) wait(next); + else resolve(); + }) + .catch(reject); + }; + if (component) wait(component); + else resolve(); + }); + } + + parseHtml = (root: NodeInterface, callback?: (node: NodeInterface, value: ImageValue) => NodeInterface) => { + const results: NodeInterface[] = []; + const editor = this.editor; + root.find(`[${CARD_KEY}="${ImageComponent.cardName}"],[${READY_CARD_KEY}="${ImageComponent.cardName}"]`).each(cardNode => { + const node = $(cardNode); + const card = editor.card.find(node) as ImageComponent; + const value = card?.getValue() || decodeCardValue(node.attributes(CARD_VALUE_KEY)); + if (value?.src && value.status === 'done') { + const currentImg = node.find('.data-image-meta img'); + let img = currentImg.length > 0 ? currentImg.clone(true) : $(''); + node.empty(); + let src = value.src; + const { onBeforeRender } = this.options; + if (onBeforeRender) { + src = onBeforeRender(value.status, value.src, this.editor); + } + const type = node.attributes(CARD_TYPE_KEY); + img.attributes('src', src); + img.css('visibility', 'visible'); + const size = value.size; + if (size?.width) img.css('width', `${size.width}px`); + if (size?.height) img.css('height', `${size.height}px`); + img.removeAttributes('class'); + img.attributes('data-type', type); + if (callback) { + img = callback(img, value); + } + if (type === CardType.BLOCK) { + img = editor.node.wrap(img, $(`

`)); + } + node.replaceWith(img); + results.push(img); + } else node.remove(); + }); + return results; + }; + + destroy() { + this.editor.off(PARSE_HTML, this.parseHtml); + } +} + +export { ImageComponent, ImageUploader }; +export type { ImageValue, ImageOptions, ImageUploaderOptions }; diff --git a/editor/src/plugins/image/locales/en-US.ts b/editor/src/plugins/image/locales/en-US.ts new file mode 100644 index 0000000..f432118 --- /dev/null +++ b/editor/src/plugins/image/locales/en-US.ts @@ -0,0 +1,19 @@ +export default { + image: { + next: 'Next', + prev: 'Previous', + zoomIn: 'Zoom In', + zoomOut: 'Zoom Out', + originSize: 'Origin Size', + bestSize: 'Best Size', + errorMessageCopy: 'Copy error message', + loadError: 'The picture failed to load!', + uploadError: 'The picture failed to upload!', + uploadLimitError: 'Upload image size is limited to $size', + toolbarReductionTitle: 'Reduction size', + toolbarWidthTitle: 'Width', + toolbarHeightTitle: 'Height', + displayBlockTitle: 'Block', + displayInlineTitle: 'In line', + }, +}; diff --git a/editor/src/plugins/image/locales/index.ts b/editor/src/plugins/image/locales/index.ts new file mode 100644 index 0000000..c32f63b --- /dev/null +++ b/editor/src/plugins/image/locales/index.ts @@ -0,0 +1,7 @@ +import en from './en-US'; +import cn from './zh-CN'; + +export default { + 'en-US': en, + 'zh-CN': cn, +}; diff --git a/editor/src/plugins/image/locales/zh-CN.ts b/editor/src/plugins/image/locales/zh-CN.ts new file mode 100644 index 0000000..58f7361 --- /dev/null +++ b/editor/src/plugins/image/locales/zh-CN.ts @@ -0,0 +1,19 @@ +export default { + image: { + next: '下一张', + prev: '上一张', + zoomIn: '放大', + zoomOut: '缩小', + originSize: '实际尺寸', + bestSize: '适应屏幕', + errorMessageCopy: '复制错误信息', + loadError: '图片加载失败!', + uploadError: '上传图片失败!', + uploadLimitError: '上传图片大小限制为 $size', + toolbarReductionTitle: '还原', + toolbarWidthTitle: '宽度', + toolbarHeightTitle: '宽度', + displayBlockTitle: '独占一行', + displayInlineTitle: '嵌入行内', + }, +}; diff --git a/editor/src/plugins/image/types.ts b/editor/src/plugins/image/types.ts new file mode 100644 index 0000000..8724484 --- /dev/null +++ b/editor/src/plugins/image/types.ts @@ -0,0 +1,56 @@ +import { CardToolbarItemOptions, CardType, EditorInterface, NodeInterface, PluginOptions, ToolbarItemOptions } from '@aomao/engine'; +import { EventEmitter2 } from 'eventemitter2'; +import PhotoSwipe from 'photoswipe'; + +export interface PswpInterface extends EventEmitter2 { + root: NodeInterface; + barUI: NodeInterface; + closeUI: NodeInterface; + bindControllerFadeInAndOut(): void; + unbindControllerFadeInAndOut(): void; + removeFadeOut(node: NodeInterface, id: string): void; + fadeOut(node: NodeInterface, id: string): void; + prev(): void; + next(): void; + renderCounter(): void; + getCurrentZoomLevel(): number; + zoomTo(zoom: number): void; + zoomIn(): void; + zoomOut(): void; + zoomToOriginSize(): void; + zoomToBestSize(): void; + updateCursor(): void; + getInitialZoomLevel(): number; + afterZoom(): void; + getCount(): number; + afterChange(): void; + setWhiteBackground(): void; + open(items: PhotoSwipe.Item[], index: number): void; + reset: () => void; + close(): void; + destroy(): void; +} + +export interface ImageOptions extends PluginOptions { + /** + * 图片渲染前调用,可以在这里修改图片链接 + */ + onBeforeRender?: (status: 'uploading' | 'done', src: string, editor: EditorInterface) => string; + /** + * 是否启用大小拖动,默认为 true + */ + enableResizer?: boolean; + /** + * 是否启用block、inline切换 + */ + enableTypeSwitch?: boolean; + /** + * 默认使用的卡片类型 + */ + defaultType?: CardType; + /** + * 最高高度,设置后默认按最高高度缩放 + */ + maxHeight?: number; + cardToolbars?: (items: (ToolbarItemOptions | CardToolbarItemOptions)[], editor: EditorInterface) => (ToolbarItemOptions | CardToolbarItemOptions)[]; +} diff --git a/editor/src/plugins/image/uploader.ts b/editor/src/plugins/image/uploader.ts new file mode 100644 index 0000000..ca194f7 --- /dev/null +++ b/editor/src/plugins/image/uploader.ts @@ -0,0 +1,630 @@ +import { + $, + File, + isAndroid, + isEngine, + NodeInterface, + Plugin, + random, + READY_CARD_KEY, + getExtensionName, + SchemaInterface, + PluginOptions, + CARD_VALUE_KEY, + decodeCardValue, + encodeCardValue, + removeUnit, + CardType, +} from '@aomao/engine'; +import type MarkdownIt from 'markdown-it'; +import type { RequestData, RequestHeaders } from '@aomao/engine'; +import { ImageOptions } from '.'; +import ImageComponent, { ImageValue } from './component'; +export interface ImageUploaderOptions extends PluginOptions { + /** + * 文件上传配置 + */ + file: { + /** + * 文件上传地址 + */ + action: string; + /** + * 数据返回类型,默认 json + */ + type?: '*' | 'json' | 'xml' | 'html' | 'text' | 'js'; + /** + * 图片文件上传时 FormData 的名称,默认 file + */ + name?: string; + /** + * 额外携带数据上传 + */ + data?: RequestData; + /** + * 请求类型,默认 multipart/form-data; + */ + contentType?: string; + /** + * 图片接收的格式,默认 "svg","png","bmp","jpg","jpeg","gif","tif","tiff","emf","webp" + */ + accept?: string | string[] | Record; + /** + * 是否跨域 + */ + crossOrigin?: boolean; + /** + * https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/withCredentials + */ + withCredentials?: boolean; + /** + * 请求头 + */ + headers?: RequestHeaders; + /** + * 文件选择限制数量 + */ + multiple?: boolean | number; + /** + * 上传大小限制,默认 1024 * 1024 * 5 就是5M + */ + limitSize?: number; + }; + remote: { + /** + * 是否跨域 + */ + crossOrigin?: boolean; + /** + * https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/withCredentials + */ + withCredentials?: boolean; + /** + * 请求头 + */ + headers?: RequestHeaders; + /** + * 上传地址 + */ + action: string; + /** + * 数据返回类型,默认 json + */ + type?: '*' | 'json' | 'xml' | 'html' | 'text' | 'js'; + /** + * 额外携带数据上传 + */ + data?: RequestData; + /** + * 图片地址上传时请求参数的名称,默认 url + */ + name?: string; + /** + * 请求类型,默认 multipart/form-data; + */ + contentType?: string; + }; + /** + * Markdown + */ + markdown?: boolean; + /** + * 解析上传后的Respone,返回 result:是否成功,data:成功:图片地址,失败:错误信息 + */ + parse?: (response: any) => { + result: boolean; + data: string; + }; + /** + * 是否是第三方图片地址,如果是,那么地址将上传服务器下载图片 + */ + isRemote?: (src: string) => boolean; +} + +const DROP_FILES = 'drop:files'; +const PASTE_EVENT = 'paste:event'; +const PASTE_SCHEMA = 'paste:schema'; +const PASTE_EACH = 'paste:each'; +const PASTE_AFTER = 'paste:after'; +const MARKDOWN_IT = 'markdown-it'; + +export default class extends Plugin { + private cardComponents: { [key: string]: ImageComponent } = {}; + private loadCounts: { [key: string]: number } = {}; + + static get pluginName() { + return 'image-uploader'; + } + + extensionNames: Record | string[] = { + svg: 'image/svg+xml', + png: 'image/png', + bmp: 'image/bmp', + jpg: 'image/jpeg', + jpeg: 'image/jpeg', + gif: 'image/gif', + tif: 'image/tiff', + tiff: 'image/tiff', + emf: 'image/emf', + webp: 'image/webp', + }; + + init() { + const editor = this.editor; + if (isEngine(this.editor)) { + editor.on(DROP_FILES, this.dropFiles); + editor.on(PASTE_EVENT, this.pasteFiles); + editor.on(PASTE_SCHEMA, this.pasteSchema); + editor.on(PASTE_EACH, this.pasteEach); + editor.on(PASTE_AFTER, this.pasteAfter); + editor.on(MARKDOWN_IT, this.markdownIt); + } + let { accept } = this.options.file || {}; + if (typeof accept === 'string') accept = accept.split(','); + if (Array.isArray(accept)) { + const names: string[] = []; + (accept || []).forEach(name => { + name = name.trim(); + const newName = name.split('.').pop(); + if (newName) names.push(newName); + }); + if (names.length > 0) this.extensionNames = names; + } else if (typeof accept === 'object') { + this.extensionNames = accept; + } + } + + isImage(file: File) { + const name = getExtensionName(file); + const names = Array.isArray(this.extensionNames) ? this.extensionNames : Object.keys(this.extensionNames); + return names.indexOf('*') >= 0 || names.indexOf(name) >= 0; + } + + dataURIToFile(dataURI: string) { + // convert base64/URLEncoded data component to raw binary data held in a string + let byteString; + + if (dataURI.split(',')[0].indexOf('base64') >= 0) { + byteString = atob(dataURI.split(',')[1]); + } else { + byteString = unescape(dataURI.split(',')[1]); + } + // separate out the mime component + const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; // write the bytes of the string to a typed array + + const ia = new Uint8Array(byteString.length); + + for (let i = 0; i < byteString.length; i++) { + ia[i] = byteString.charCodeAt(i); + } + + return new Blob([ia], { + type: mimeString, + }); + } + + getUrl(value: ImageValue) { + const imagePlugin = this.editor.plugin.components['image']; + if (imagePlugin) { + const { onBeforeRender } = (imagePlugin['options'] || {}) as any; + if (onBeforeRender) return onBeforeRender(value.status, value.src, this.editor); + } + return value.src; + } + + loadImage(id: string, value: ImageValue) { + if (!this.loadCounts[id]) this.loadCounts[id] = 1; + const image = new Image(); + const editor = this.editor; + image.src = this.getUrl(value); + image.onload = () => { + delete this.loadCounts[id]; + editor.card.update(id, value); + }; + image.onerror = () => { + if (this.loadCounts[id] <= 3) { + setTimeout(() => { + this.loadCounts[id]++; + this.loadImage(id, value); + }, 500); + } else { + delete this.loadCounts[id]; + value.status = 'error'; + (value.message = editor.language.get('image', 'loadError')), editor.card.update(id, value); + } + }; + } + + async execute(files?: Array | string | MouseEvent) { + const editor = this.editor; + if (!isEngine(editor)) return; + const { request, card, language } = editor; + const { action, data, type, contentType, multiple, crossOrigin, withCredentials, headers, name } = this.options.file; + const { parse } = this.options; + const limitSize = this.options.file.limitSize || 5 * 1024 * 1024; + + if (!Array.isArray(files) && typeof files !== 'string') { + const accepts = Array.isArray(this.extensionNames) ? '.' + this.extensionNames.join(',.') : Object.values(this.extensionNames).join(','); + files = await request.getFiles({ + event: files, + accept: isAndroid ? 'image/*' : accepts.length > 0 ? accepts : '', + multiple, + }); + } else if (typeof files === 'string') { + this.insertRemote(files); + return; + } + if (files.length === 0) return; + request.upload( + { + url: action, + crossOrigin, + withCredentials, + headers, + data, + type, + contentType, + onBefore: file => { + if (file.size > limitSize) { + editor.messageError( + 'upload-limit', + language + .get('image', 'uploadLimitError') + .toString() + .replace('$size', (limitSize / 1024 / 1024).toFixed(0) + 'M') + ); + return false; + } + return true; + }, + onReady: fileInfo => { + if (!isEngine(editor) || !!this.cardComponents[fileInfo.uid]) return; + const src = fileInfo.src || ''; + const base64String = typeof src !== 'string' ? window.btoa(String.fromCharCode(...new Uint8Array(src))) : src; + const insertCard = (value: Partial) => { + const imagePlugin = editor.plugin.findPlugin('image'); + const component = card.insert>( + 'image', + { + ...value, + status: 'uploading', + type: value.type || imagePlugin?.options?.defaultType, + //fileInfo.src, 再协作中,如果大图片使用base64加载图片预览会造成很大资源浪费 + }, + base64String + ); + this.cardComponents[fileInfo.uid] = component; + }; + return new Promise(resolve => { + const image = new Image(); + image.src = base64String; + const imagePlugin = editor.plugin.findPlugin('image'); + + image.onload = () => { + const { naturalWidth, naturalHeight, height, width } = image; + + let imageWidth: number = width; + let imageHeight: number = height; + const maxHeight: number | undefined = imagePlugin?.options?.maxHeight; + + if (maxHeight && naturalHeight > naturalWidth && height > maxHeight) { + imageHeight = maxHeight; + imageWidth = naturalWidth * (maxHeight / naturalHeight); + } + + insertCard({ + src: '', + size: { + width: imageWidth, + height: imageHeight, + naturalHeight: image.naturalHeight, + naturalWidth: image.naturalWidth, + }, + }); + resolve(); + }; + image.onerror = () => { + insertCard({ src: '', status: 'error' }); + resolve(); + }; + }); + }, + onUploading: (file, { percent }) => { + const component = this.cardComponents[file.uid || '']; + if (!component) return; + component.setProgressPercent(percent); + }, + onSuccess: (response, file) => { + const component = this.cardComponents[file.uid || '']; + if (!component) return; + let src = response.url || (response.data && response.data.url) || response.src || (response.data && response.data.src); + const result = parse ? parse(response) : src ? { result: true, data: src } : { result: false }; + if (!result.result) { + card.update(component.id, { + status: 'error', + message: result.data || language.get('image', 'uploadError'), + }); + } else { + src = result.data; + } + const value: any = { + status: 'done', + }; + if (src) { + value.src = src; + this.loadImage(component.id, value); + } + delete this.cardComponents[file.uid || '']; + }, + onError: (error, file) => { + const component = this.cardComponents[file.uid || '']; + if (!component) return; + card.update(component.id, { + status: 'error', + message: error.message || language.get('image', 'uploadError'), + }); + delete this.cardComponents[file.uid || '']; + }, + }, + files, + name + ); + return; + } + + dropFiles = (files: File[]) => { + const editor = this.editor; + if (!isEngine(editor)) return; + files = files.filter(file => this.isImage(file)); + if (files.length === 0) return; + editor.command.execute('image-uploader', files); + return false; + }; + + pasteSchema = (schema: SchemaInterface) => { + schema.add({ + type: 'inline', + name: 'img', + isVoid: true, + attributes: { + src: { + required: true, + value: '@url', + }, + width: '@number', + height: '@number', + style: { + 'max-width': '@length', + 'max-height': '@length', + width: '@length', + height: '@length', + }, + alt: '*', + title: '*', + 'data-type': '*', + 'data-size': '@number', + 'data-width': '@number', + 'data-height': '@number', + }, + }); + }; + + pasteFiles = ({ files }: Record<'files', File[]>) => { + const editor = this.editor; + if (!isEngine(editor)) return; + files = files.filter(file => this.isImage(file)); + if (files.length === 0) return; + editor.command.execute('image-uploader', files); + return false; + }; + + pasteEach = (node: NodeInterface) => { + const editor = this.editor; + const { isRemote } = this.options; + //是卡片,并且还没渲染 + if (node.isCard() && node.attributes(READY_CARD_KEY)) { + if (node.attributes(READY_CARD_KEY) !== 'image') return; + const value = decodeCardValue(node.attributes(CARD_VALUE_KEY)); + if (!value || !value.src) { + node.remove(); + return; + } + //第三方图片,设置上传状态 + if (isRemote && isRemote(value.src)) { + value.status = 'uploading'; + value.percent = 0; + editor.card.replaceNode(node, 'image', value); + } else if (value.status === 'uploading') { + //如果是上传状态,设置为正常状态 + value.percent = 0; + node.attributes(CARD_VALUE_KEY, encodeCardValue({ ...value, status: 'done' })); + } + return; + } + //图片带链接 + /** + if(node.name === "a" && node.find("img").length > 0){ + const img = node.find("img") + const href = node.attributes("href") + const target = node.attributes("target") + const src = img.attributes("src") || img.attributes("data-src") + const alt = img.attributes("alt") + if(!src) { + node.remove() + return + } + this.editor.card.replaceNode(node,"image",{ + src, + status:isRemote && isRemote(src) || /^data:image\//i.test(src) ? "uploading" : "done", + alt, + link:{ + href, + target + }, + percent:0 + }) + } */ + //图片 + if (node.name === 'img') { + const attributes = node.attributes(); + const src = attributes['src'] || attributes['data-src']; + const alt = attributes['alt']; + if (!src) { + node.remove(); + return; + } + const imagePlugin = editor.plugin.findPlugin('image'); + const attrWidth = attributes['width']; + const attrHeight = attributes['height']; + const width = attrWidth ? attrWidth : node.css('width'); + const height = attrHeight ? attrHeight : node.css('height'); + const dataTypeValue = attributes['data-type'] || imagePlugin?.options.defaultType; + let type = CardType.INLINE; + if (dataTypeValue === 'block') { + const parent = node.parent(); + // 移除转换为html的时候加载的额外p标签 + if (parent && parent.name === 'p') { + editor.node.unwrap(parent); + } + type = CardType.BLOCK; + } + + editor.card.replaceNode(node, 'image', { + type, + src, + status: (isRemote && isRemote(src)) || /^data:image\//i.test(src) ? 'uploading' : 'done', + alt, + percent: 0, + size: { + width: removeUnit(width), + height: removeUnit(height), + }, + }); + node.remove(); + } + }; + + async uploadAddress(src: string, component: ImageComponent) { + const editor = this.editor; + if (!isEngine(editor)) return; + const { action, type, contentType, crossOrigin, withCredentials, headers, name, data } = this.options.remote; + const { parse } = this.options; + const addressName = name || 'url'; + editor.request.ajax({ + url: action, + method: 'POST', + contentType: contentType || 'application/json', + type: type === undefined ? 'json' : type, + crossOrigin, + withCredentials, + headers, + data: + typeof data === 'function' + ? async () => { + const newData = await data(); + return { ...newData, [addressName]: src }; + } + : { + ...data, + [addressName]: src, + }, + success: response => { + let src = response.url || (response.data && response.data.url) || response.src || (response.data && response.data.src); + + const result = parse ? parse(response) : src ? { result: true, data: src } : { result: false }; + if (!result.result) { + editor.card.update(component.id, { + status: 'error', + message: result.data || editor.language.get('image', 'uploadError'), + }); + } else { + src = result.data; + } + + const value: any = { + status: 'done', + }; + if (src) { + value.src = src; + this.loadImage(component.id, value); + } + }, + error: error => { + editor.card.update(component.id, { + status: 'error', + message: error.message || editor.language.get('image', 'uploadError'), + }); + }, + }); + } + + insertRemote(src: string, alt?: string) { + const editor = this.editor; + const imagePlugin = editor.plugin.findPlugin('image'); + const value: ImageValue = { + src, + alt, + status: 'uploading', + type: imagePlugin?.options.defaultType || CardType.INLINE, + }; + const { isRemote } = this.options; + //上传第三方图片 + if (isRemote && isRemote(src)) { + const component = editor.card.insert>('image', value); + this.uploadAddress(src, component); + return; + } + //当前图片 + value.status = 'done'; + editor.card.insert('image', value); + } + + pasteAfter = () => { + const editor = this.editor; + editor.container.find('[data-card-key=image]').each((node, key) => { + const component = editor.card.find(node) as ImageComponent; + if (!component || !isEngine(editor)) return; + const value = component.getValue(); + //不是上传状态,或者当前卡片正在执行上传跳过 + if (value?.status !== 'uploading' || Object.keys(this.cardComponents).find(key => this.cardComponents[key].id === component.id)) { + return; + } + + const { src } = value; + // 转存 base64 图片 + if (/^data:image\//i.test(src)) { + const fileBlob = this.dataURIToFile(src); + const ext = getExtensionName(fileBlob); + const name = ext ? 'image.'.concat(ext) : 'image'; + const file: File = new globalThis.File([fileBlob], name); + file.uid = new Date().getTime() + '-' + random(); + editor.command.execute('image-uploader', [file]); + this.cardComponents[file.uid] = component; + return; + } + const { isRemote } = this.options; + if (isRemote && isRemote(src)) { + this.uploadAddress(src, component); + } + }); + }; + + markdownIt = (mardown: MarkdownIt) => { + if (this.options.markdown !== false) { + mardown.enable('image'); + mardown.enable('reference'); + } + }; + + destroy() { + const editor = this.editor; + if (isEngine(editor)) { + editor.off(DROP_FILES, this.dropFiles); + editor.off(PASTE_EVENT, this.pasteFiles); + editor.off(PASTE_SCHEMA, this.pasteSchema); + editor.off(PASTE_EACH, this.pasteEach); + editor.off(PASTE_AFTER, this.pasteAfter); + editor.off(MARKDOWN_IT, this.markdownIt); + } + } +} diff --git a/editor/src/plugins/lightblock/component/index.tsx b/editor/src/plugins/lightblock/component/index.tsx new file mode 100644 index 0000000..5f30860 --- /dev/null +++ b/editor/src/plugins/lightblock/component/index.tsx @@ -0,0 +1,180 @@ +import { $, Card, CardToolbarItemOptions, CardType, isEngine, NodeInterface, Parser, ToolbarItemOptions } from '@aomao/engine'; +import { createApp } from 'vue'; +import type { LightblockValue, IChangeParam } from './types'; +import LightblockTheme from './lightblock-theme.vue'; +import './style.css'; + +export const themeIcon = ` + + + +`; + +class Lightblock extends Card { + static get cardName() { + return 'lightblock'; + } + + static get cardType() { + return CardType.BLOCK; + } + + static get autoSelected() { + return false; + } + + static get singleSelectable() { + return false; + } + + contenteditable = ['div.lightblock-editor-container']; + + #container?: NodeInterface; + + #changeTimeout?: NodeJS.Timeout; + + toolbar(): Array { + if (!isEngine(this.editor) || this.editor.readonly) return []; + + const value = this.getValue(); + + return [ + { type: 'dnd' }, + { type: 'copy' }, + { + type: 'node', + title: '主题', + node: $(themeIcon), + didMount: node => { + console.log('node?.get', value, node?.get()); + if (node?.get()) { + createApp(LightblockTheme, { + value, + change: (data: IChangeParam) => { + this.setValue({ + ...value, + backgroundColor: data.background, + borderColor: data.border, + }); + this.updateColor(); + }, + }).mount(node.get() as Element); + } + }, + }, + { type: 'separator' }, + { type: 'delete' }, + ]; + } + + getValue() { + const value = super.getValue(); + const editorContainer = this.#container?.find(this.contenteditable.join(',')); + if (!editorContainer) return value; + const editor = this.editor; + const { schema, conversion } = editor; + const container = $('
'); + + container.append(editorContainer.clone(true).children()); + const parser = new Parser(container, editor); + const html = parser.toValue(schema, conversion, false, false); + if (!isEngine(editor)) return { ...value, html }; + return { + ...value, + html, + } as LightblockValue; + } + + updateColor = (value = this.getValue()) => { + this.#container?.css({ + borderColor: value.borderColor, + backgroundColor: value.backgroundColor, + }); + }; + + onChange = (trigger: 'remote' | 'local' = 'local') => { + const editor = this.editor; + if (isEngine(editor) && trigger === 'local' && editor.model.mutation.isStopped) return; + + if (this.#changeTimeout) clearTimeout(this.#changeTimeout); + this.#changeTimeout = setTimeout(() => { + const value = this.getValue(); + this.updateColor(value); + if (trigger === 'local' && isEngine(editor)) { + if (value) this.setValue(value); + } + }, 50); + }; + + render(isFoucs?: boolean) { + const value = this.getValue(); + const { borderColor, backgroundColor } = value; + const childValue = value.html ? new Parser(value.html, this.editor).toValue() : '
'; + this.#container = $( + `
+
+ + + + + + + + + +
+
${childValue}
+
` + ); + + if (isFoucs) { + setTimeout(() => { + this.#container?.find('.lightblock-editor-container')?.get()?.focus?.(); + }, 0); + } + + return this.#container; + } + + didRender() { + super.didRender(); + this.updateColor(); + } +} +export default Lightblock; +export type { LightblockValue }; diff --git a/editor/src/plugins/lightblock/component/lightblock-theme.vue b/editor/src/plugins/lightblock/component/lightblock-theme.vue new file mode 100644 index 0000000..1234bf7 --- /dev/null +++ b/editor/src/plugins/lightblock/component/lightblock-theme.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/editor/src/plugins/lightblock/component/markdown.ts b/editor/src/plugins/lightblock/component/markdown.ts new file mode 100644 index 0000000..cc0eb83 --- /dev/null +++ b/editor/src/plugins/lightblock/component/markdown.ts @@ -0,0 +1,21 @@ +import container from 'markdown-it-container'; +import type MarkdownIt from 'markdown-it'; +import { encodeCardValue } from '@aomao/engine'; + +export default function mk_lightblock(md: MarkdownIt) { + const defaultValue = { + borderColor: '#fed4a4', + backgroundColor: '#fff5eb', + text: 'light-block', + }; + + md.use(container, 'tip', { + render(tokens: any, idx: number) { + if (tokens[idx].nesting === 1) { + return `
`; + } else { + return '
'; + } + }, + }); +} diff --git a/editor/src/plugins/lightblock/component/style.css b/editor/src/plugins/lightblock/component/style.css new file mode 100644 index 0000000..cec6ce7 --- /dev/null +++ b/editor/src/plugins/lightblock/component/style.css @@ -0,0 +1,93 @@ +.lightblock-container { + display: flex; + padding: 12px 16px; + border: 1px solid transparent; + border-radius: 4px; +} +.lightblock-icon { + width: 24px; + height: 24px; + font-size: 14px; + margin-right: 8px; +} + +.lightblock-editor-container { + flex: 1; + max-width: calc(100% - 36px); +} + +.lightblock-icon-theme { + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + position: relative; +} +.lightblock-icon-theme:hover path{ + fill: #40a9ff; +} +.lightblock-theme-contain { + display: none; + position: absolute; + bottom: 0px; + left: 50%; + transform: translate(-50%, 100%); + padding-top: 8px; +} +.lightblock-theme-content { + flex-direction: column; + position: relative; + background: #fff; + box-shadow: 0 2px 10px rgb(0 0 0 / 12%); + padding: 12px 16px; + border-radius: 4px; +} +.lightblock-icon-theme:hover .lightblock-theme-contain { + display: block; +} +.lightblock-theme-random { + position: absolute; + top: 12px; + right: 12px; + color:#3370ff; + font-size: 12px; + height: 20px; + line-height: 20px; + cursor: pointer; +} +.lightblock-theme-random span { + font-size: 12px; +} +.lightblock-theme-title { + padding-bottom: 4px; + color: #888; + border-bottom: 1px solid #eee; +} +.lightblock-theme-box { + display: flex; +} +.lightblock-theme-box-item { + width: 24px; + height: 24px; + margin-right: 4px; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + margin: 8px 2px; + cursor: pointer; +} +.lightblock-theme-box-item.active { + border: 2px solid #3370ff; +} +.lightblock-theme-box-item:hover { + border: 2px solid rgb(51, 112, 255, 0.5); +} +.lightblock-theme-box-item span { + display: inline-block; + width: 20px; + height: 20px; + border-radius: 2px; + filter: brightness(1) +} diff --git a/editor/src/plugins/lightblock/component/types.ts b/editor/src/plugins/lightblock/component/types.ts new file mode 100644 index 0000000..a987847 --- /dev/null +++ b/editor/src/plugins/lightblock/component/types.ts @@ -0,0 +1,23 @@ +import { CardValue } from '@aomao/engine'; + +export interface LightblockValue extends CardValue { + borderColor: string; + backgroundColor: string; + text: string; + html?: string; +} + +export interface ILightblockProp { + value: LightblockValue; +} + +export interface IChangeParam { + border: string; + background: string; +} + +export interface IThemeProp { + language: { [key: string]: string }; + value: LightblockValue; + onChange?: (val: IChangeParam) => void; +} diff --git a/editor/src/plugins/lightblock/index.ts b/editor/src/plugins/lightblock/index.ts new file mode 100644 index 0000000..f132e9b --- /dev/null +++ b/editor/src/plugins/lightblock/index.ts @@ -0,0 +1,109 @@ +import { $, Plugin, NodeInterface, CARD_KEY, isEngine, SchemaInterface, PluginOptions, decodeCardValue, encodeCardValue, READY_CARD_KEY, Parser } from '@aomao/engine'; +import type MarkdownIt from 'markdown-it'; +import LightblockComponent from './component'; +import type { LightblockValue } from './component'; +import lightblockMk from './component/markdown'; + +export type LightblockOptions = PluginOptions; + +export default class extends Plugin { + static get pluginName() { + return 'lightblock'; + } + init() { + const editor = this.editor; + + editor.on('parse:html', this.parseHtml); + editor.on('paste:schema', this.pasteSchema); + editor.on('paste:each', this.pasteHtml); + if (isEngine(editor)) { + editor.on('markdown-it', this.markdownIt); + } + } + + execute() { + const editor = this.editor; + + if (!isEngine(editor) || editor.readonly) return; + const { card } = editor; + + card.insert( + LightblockComponent.cardName, + { + borderColor: '#fed4a4', + backgroundColor: '#fff5eb', + text: 'light-block', + }, + true + ); + } + + markdownIt = (markdown: MarkdownIt) => { + if (this.options.markdown !== false) { + lightblockMk(markdown); + } + }; + + pasteSchema = (schema: SchemaInterface) => { + schema.add({ + type: 'block', + name: 'div', + attributes: { + 'data-type': { + required: true, + value: LightblockComponent.cardName, + }, + 'data-value': '*', + }, + }); + }; + + pasteHtml = (node: NodeInterface) => { + const editor = this.editor; + const cardName = LightblockComponent.cardName; + + if (!isEngine(editor) || editor.readonly) return; + if (node.isElement()) { + const type = node.attributes('data-type'); + if (type === cardName) { + const value = node.attributes('data-value'); + const cardValue = decodeCardValue(value); + editor.card.replaceNode(node, cardName, cardValue); + node.remove(); + return false; + } + } + return true; + }; + + parseHtml = (root: NodeInterface) => { + const cardName = LightblockComponent.cardName; + + root.find(`[${CARD_KEY}="${cardName}"],[${READY_CARD_KEY}="${cardName}"]`).each(cardNode => { + const node = $(cardNode); + const card = this.editor.card.find(node); + const value = card?.getValue(); + if (value) { + node.empty(); + const div = this.renderHtml(value, cardName); + node.replaceWith(div); + } else node.remove(); + }); + }; + + renderHtml = (value: LightblockValue, cardName: string) => { + const htmlstring = new Parser(value.html || value.text, this.editor).toHTML(); + + return $(`
${htmlstring}
`); + }; + + destroy() { + const editor = this.editor; + + editor.off('parse:html', this.parseHtml); + editor.off('paste:schema', this.pasteSchema); + editor.off('paste:each', this.pasteHtml); + } +} +export { LightblockComponent }; +export type { LightblockValue }; diff --git a/editor/src/plugins/math/component/constant.ts b/editor/src/plugins/math/component/constant.ts new file mode 100644 index 0000000..e16835b --- /dev/null +++ b/editor/src/plugins/math/component/constant.ts @@ -0,0 +1,307 @@ +export const mathList = [ + { + name: '积分', + children: [ + 'a^2', + 'a_2', + 'a^{2+2}', + 'a_{i,j}', + '{}_1^2\\!X_3^4', + '\\overset{\\frown} {AB}', + '\\overline{hij}', + `\\underline{klm}`, + '\\overbrace{1+2+\\cdots+100}', + '\\begin{matrix} 5050 \\\\ \\overbrace{ 1+2+\\cdots+100 }\\end{matrix}', + '\\underbrace{a+b+\\cdots+z}', + '\\sum_{k=1}^N k^2', + '\\begin{matrix} \\sum_{k=1}^N k^2 \\end{matrix}', + '\\prod_{i=1}^N x_i', + '\\begin{matrix} \\prod_{i=1}^N x_i \\end{matrix}', + '\\coprod_{i=1}^N x_i', + '\\begin{matrix} \\coprod_{i=1}^N x_i \\end{matrix}', + '\\lim_{n \\to \\infty}x_n', + '\\begin{matrix} \\ lim_{n \\to \\infty}x_n \\end{matrix}', + '\\int_{-N}^{N} e^x\\, \\mathrm{d}x', + '\\begin{matrix} \\int_{_N}^{N} e^x\\, \\mathrm{d}x \\end{matrix}', + '\\iint_{D}^{W} \\, \\mathrm{d}x\\,\\mathrm{d}y', + '\\iiint_{E}^{V} \\,\\mathrm{d}x\\,\\mathrm{d}y,\\mathrm{d}z', + '\\oint_{C} x^3\\, \\mathrm{d}x + 4y^2\\, \\mathrm{d}y', + '\\bigcap_1^{n} p', + '\\bigcup_1^{k} p', + ], + }, + { + name: '分隔符', + children: [ + '\\left(\\frac{a}{b} \\right)', + '\\left[\\frac{a}{b} \\right]', + '\\left\\{\\frac{a}{b} \\right\\}', + '\\left \\langle \\frac{a}{b} \\right \\rangle', + '\\left|\\frac{a}{b} \\right|', + '\\left \\lceil \\frac{c}{d} \\right \\rceil', + '\\left / \\frac{a}{b} \\right \\backslash', + '\\left \\Uparrow \\frac{a}{b} \\right \\Downarrow', + '\\left \\updownarrow \\frac{a}{b} \\right \\Updownarrow', + '\\left [ 0,1 \\right ) \\left \\langle \\psi \\right |', + '\\left \\{ \\frac{a}{b} \\right.', + '\\left . \\frac{a}{b} \\right \\}', + '\\langle', + '\\rangle', + '\\lceil', + '\\rceil', + '\\lfloor', + '\\rfloor', + '\\lbrace', + '\\rbrace', + '\\lvert', + '\\rvert', + ], + }, + { + name: '函数', + children: [ + '\\sin\\theta', + '\\cos\\theta', + '\\tan\\theta', + '\\arcsin\\frac{L}{r}', + '\\arccos\\frac{T}{r}', + '\\arctan\\frac{L}{T}', + '\\sinh g', + '\\cosh h', + '\\tanh i', + '\\coth j', + '\\operatorname{sh}j', + '\\operatorname{ch}h', + '\\operatorname{th}i', + '\\operatorname{argsh}k', + '\\operatorname{argch}l', + '\\operatorname{argth}m', + '\\limsup S', + '\\liminf I', + '\\max H', + '\\min L', + '\\inf s', + '\\sup t', + '\\exp\\!t', + '\\ln X', + '\\lg X', + '\\log X', + '\\log_\\alpha X', + '\\ker x', + '\\deg x', + '\\gcd(T,U,V,W,X)', + '\\Pr x', + '\\det x', + '\\hom x', + '\\arg x', + '\\dim x', + '\\lim_{t\\to n}T', + ], + }, + { + name: '微分导数', + children: ['\\nabla\\psi', '\\partial x', '\\mathrm{d}x', '\\dot x', '\\ddot y', 'X^\\prime', '\\backprime', 'f^{(3)}'], + }, + { + name: '运算符', + children: [ + '\\pm', + '\\times', + '\\div', + '\\mid', + '\\nmid', + '\\cdot', + '\\circ', + '\\ast', + '\\bigodot', + '\\bigoplus', + '\\leq', + '\\geq', + '\\leqq', + '\\geqq', + '=', + '\\neq', + '\\approx', + '\\equiv', + '\\not\\equiv', + '\\sum', + '\\prod', + '\\coprod', + '\\backslash', + '\\sim', + '\\backsim', + '\\simeq', + '\\cong', + '\\dot=', + '\\ggg', + '\\gg', + '>', + '<', + '\\ll', + '\\lll', + '\\propto', + ], + }, + { + name: '逻辑符号', + children: [ + '、emptyset', + '\\varnothing', + '\\in', + '\\not\\in', + '\\subset', + '\\supset', + '\\subseteq', + '\\sqsupseteq', + '\\cap', + '\\cup', + '\\bigcup', + '\\sqcap', + '\\sqcup', + '\\uplus', + '\\biguplus', + '\\bigsqcup', + '\\top', + '\\bot', + '\\complement', + '\\vee', + '\\wedge', + '\\bigvee', + '\\bigwedge', + '\\forall', + '\\exists', + '\\not\\subset', + '\\not=', + '\\not<', + '\\not>', + '\\because', + '\\therefore', + '\\neg', + '\\bar{q} \\to p', + '\\setminus', + '\\smallsetminus', + ], + }, + { + name: '几何符号', + children: ['\\Diamond', '\\Box', '\\triangle', '\\perp', '\\angle\\Alpha\\Beta\\Gamma', '60^\\circ'], + }, + { + name: '戴帽符号', + children: [ + '\\vec{c}', + '\\overleftarrow{ab}', + '\\overrightarrow{cd}', + '\\overleftrightarrow{ab}', + '\\widehat{efg}', + '\\overset{\\frown} {AB}', + '\\hat{xyz}', + '\\tilde{xy}', + '\\bar{y}', + '\\widetilde{xyz}', + '\\acute{y}', + '\\breve{y}', + '\\check{y}', + '\\grave{y}', + ], + }, + { + name: '箭头符号', + children: [ + '\\to', + '\\mapsto', + '\\underrightarrow{1^circ/min}', + '\\implies', + '\\impliedby', + '\\iff', + '\\downarrow', + '\\Uparrow', + '\\Downarrow', + '\\leftarrow', + '\\rightarrow', + '\\leftrightarrow', + '\\Leftarrow', + '\\Rightarrow', + '\\Leftrightarrow', + '\\longleftarrow', + '\\longrightarrow', + '\\longleftrightarrow', + '\\Longleftarrow', + '\\Longrightarrow', + '\\Longleftrightarrow', + ], + }, + { + name: '特殊符号', + children: ['\\eth', '\\%', '\\dagger', '\\ddagger', '\\star', '*', '\\ldots', '\\smile', '\\frown', '\\wr'], + }, + { + name: '分数多行', + children: [ + '\\frac{2}{4}=0.5', + '{2 \\over 3}', + '{{a+b} \\over {a-b}}', + '\\tfrac{2}{4} = 0.5', + '\\cfrac{2}{c + \\cfrac{2}{d + \\cfrac{2}{4}}} = a', + '\\begin{matrix}x & y \\\\z & v\\end{matrix}', + '\\begin{Vmatrix}x & y \\\\z & v\\end{Vmatrix}', + '\\begin{bmatrix}0& \\cdots & 0\\\\\\vdots & \\ddots & \\vdots \\\\0& \\cdots & 0\\end{bmatrix}', + '\\begin{Bmatrix}x & y \\\\z & v\\end{Bmatrix}', + '\\begin{pmatrix}x & y \\\\z & v\\end{pmatrix}', + '\\begin{cases}3x + 5y + z \\\\7x - 2y + 4z \\\\-6x + 3y + 2z\\end{cases}', + '\\begin{array}{|c|c||c|} a & b & S \\\\\\hline0&0&1\\\\0&1&1\\\\1&0&1\\\\1&1&0\\\\\\end{array}', + ], + }, + { + name: '希腊字母', + children: [ + '\\alpha', + '\\beta', + '\\gamma', + '\\delta', + '\\epsilon', + '\\epsilon', + '\\zeta', + '\\eta', + '\\theta', + '\\iota', + '\\kappa', + '\\lambda', + '\\mu', + '\\nu', + '\\xi', + 'o', + '\\pi', + '\\rho', + '\\sigma', + '\\tau', + '\\upsilon', + '\\phi', + ], + }, +]; +export const katexOption = { + delimiters: [ + { + left: '$$', + right: '$$', + display: true, + }, + { + left: '$', + right: '$', + display: false, + }, + { + left: '\\(', + right: '\\)', + display: false, + }, + { + left: '\\[', + right: '\\]', + display: true, + }, + ], + throwOnError: false, +}; diff --git a/editor/src/plugins/math/component/index.ts b/editor/src/plugins/math/component/index.ts new file mode 100644 index 0000000..5532c78 --- /dev/null +++ b/editor/src/plugins/math/component/index.ts @@ -0,0 +1,80 @@ +import { $, Card, CardToolbarItemOptions, CardType, isEngine, NodeInterface, ToolbarItemOptions } from '@aomao/engine'; +import { App, createApp } from 'vue'; +import { MathValue } from './type'; +import MathFormula from './math-formula.vue'; + +export const editIcon = ` + + + +`; + +class Math extends Card { + static get cardName() { + return 'math'; + } + + static get cardType() { + return CardType.INLINE; + } + + #container?: NodeInterface; + private vm?: App; + + toolbar(): Array { + if (!isEngine(this.editor) || this.editor.readonly) return []; + return [ + // { + // type: "dnd", + // }, + { + type: 'node', + node: $(editIcon), + didMount: node => { + node.on('click', () => { + this.defaultVisible = true; + this.didRender(); + }); + }, + }, + { + type: 'copy', + }, + { + type: 'delete', + }, + ]; + } + + defaultVisible = false; + + render(visible?: boolean) { + this.#container = $('
Loading
'); + this.defaultVisible = visible ?? false; + return this.#container; + } + + didRender() { + super.didRender(); + const value = this.getValue(); + setTimeout(() => { + this.vm = createApp(MathFormula, { + value, + defaultVisible: this.defaultVisible, + change: (item: any) => { + this.setValue({ + ...value, + ...item, + }); + }, + }); + this.vm.mount(this.#container?.get()); + }, 20); + } + + destroy() { + super.destroy(); + this.vm?.unmount(); + } +} +export default Math; diff --git a/editor/src/plugins/math/component/math-formula.vue b/editor/src/plugins/math/component/math-formula.vue new file mode 100644 index 0000000..f4bdc11 --- /dev/null +++ b/editor/src/plugins/math/component/math-formula.vue @@ -0,0 +1,225 @@ +