qiyunfeng-create
2 天以前 b15a997e95d715c41df3a76aecbf58ec1141ab53
个人中心-接口测试
1个文件已删除
27个文件已添加
20个文件已修改
15591 ■■■■■ 已修改文件
package-lock.json 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/base.css 120 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/default-book-img.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/personalCenter/book-cover.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/personalCenter/collect-click.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/personalCenter/notification.svg 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/js/config.js 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/js/middleGround/api/edu.js 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/js/middleGround/api/identity.js 171 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/main.css 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/counter.js 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/index.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/breadcrumb.ts 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/modules/user.ts 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/classManage/classHome.vue 778 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/classManage/components/headerPage.vue 288 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/classManage/components/questionDom.vue 420 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/classManage/config.ts 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/classManage/index.vue 283 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/classManage/infoList.vue 254 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/classManage/interactionDetail.vue 373 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/classManage/jobAnalysis.vue 331 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/classManage/jobDetail.vue 840 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/classManage/jobManage.vue 1895 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/classManage/prepareLessons.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/classManage/studentJob.vue 357 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/classManage/studentManage.vue 475 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/classManage/talkDetail.vue 481 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/classManage/talkingPoint.vue 375 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/classManage/teachInteraction.vue 512 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/classManage/teachingPlan.vue 855 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/classManage/testManage.vue 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/courseManage/components/class.vue 502 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/courseManage/index.vue 228 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personalCenter/activeCode.vue 396 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personalCenter/class.vue 461 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personalCenter/config.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personalCenter/course.vue 654 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personalCenter/myApply.vue 317 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personalCenter/myBook.vue 328 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personalCenter/myCart.vue 598 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personalCenter/myCollection.vue 348 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personalCenter/myMessage.vue 205 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personalCenter/myOrder.vue 494 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personalCenter/teacherCertification.vue 858 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personalCenter/userInfo.vue 687 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package-lock.json
@@ -15,6 +15,7 @@
        "pinia": "^3.0.3",
        "spark-md5": "^3.0.2",
        "vue": "^3.5.18",
        "vue-clipboard3": "^2.0.0",
        "vue-router": "^4.5.1"
      },
      "devDependencies": {
@@ -1740,6 +1741,16 @@
        }
      ]
    },
    "node_modules/clipboard": {
      "version": "2.0.11",
      "resolved": "https://registry.npmmirror.com/clipboard/-/clipboard-2.0.11.tgz",
      "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==",
      "dependencies": {
        "good-listener": "^1.2.2",
        "select": "^1.1.2",
        "tiny-emitter": "^2.0.0"
      }
    },
    "node_modules/combined-stream": {
      "version": "1.0.8",
      "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -1862,6 +1873,11 @@
      "engines": {
        "node": ">=0.4.0"
      }
    },
    "node_modules/delegate": {
      "version": "3.2.0",
      "resolved": "https://registry.npmmirror.com/delegate/-/delegate-3.2.0.tgz",
      "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw=="
    },
    "node_modules/dunder-proto": {
      "version": "1.0.1",
@@ -2225,6 +2241,14 @@
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/good-listener": {
      "version": "1.2.2",
      "resolved": "https://registry.npmmirror.com/good-listener/-/good-listener-1.2.2.tgz",
      "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==",
      "dependencies": {
        "delegate": "^3.1.2"
      }
    },
    "node_modules/gopd": {
@@ -2974,6 +2998,11 @@
      "license": "ISC",
      "optional": true
    },
    "node_modules/select": {
      "version": "1.1.2",
      "resolved": "https://registry.npmmirror.com/select/-/select-1.1.2.tgz",
      "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA=="
    },
    "node_modules/semver": {
      "version": "6.3.1",
      "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz",
@@ -3084,6 +3113,11 @@
      "engines": {
        "node": ">=16"
      }
    },
    "node_modules/tiny-emitter": {
      "version": "2.1.0",
      "resolved": "https://registry.npmmirror.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
      "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
    },
    "node_modules/tinyglobby": {
      "version": "0.2.14",
@@ -3390,6 +3424,14 @@
        "typescript": {
          "optional": true
        }
      }
    },
    "node_modules/vue-clipboard3": {
      "version": "2.0.0",
      "resolved": "https://registry.npmmirror.com/vue-clipboard3/-/vue-clipboard3-2.0.0.tgz",
      "integrity": "sha512-Q9S7dzWGax7LN5iiSPcu/K1GGm2gcBBlYwmMsUc5/16N6w90cbKow3FnPmPs95sungns4yvd9/+JhbAznECS2A==",
      "dependencies": {
        "clipboard": "^2.0.6"
      }
    },
    "node_modules/vue-router": {
@@ -4527,6 +4569,16 @@
      "integrity": "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==",
      "dev": true
    },
    "clipboard": {
      "version": "2.0.11",
      "resolved": "https://registry.npmmirror.com/clipboard/-/clipboard-2.0.11.tgz",
      "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==",
      "requires": {
        "good-listener": "^1.2.2",
        "select": "^1.1.2",
        "tiny-emitter": "^2.0.0"
      }
    },
    "combined-stream": {
      "version": "1.0.8",
      "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -4605,6 +4657,11 @@
      "version": "1.0.0",
      "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
    },
    "delegate": {
      "version": "3.2.0",
      "resolved": "https://registry.npmmirror.com/delegate/-/delegate-3.2.0.tgz",
      "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw=="
    },
    "dunder-proto": {
      "version": "1.0.1",
@@ -4848,6 +4905,14 @@
      "requires": {
        "@sec-ant/readable-stream": "^0.4.1",
        "is-stream": "^4.0.1"
      }
    },
    "good-listener": {
      "version": "1.2.2",
      "resolved": "https://registry.npmmirror.com/good-listener/-/good-listener-1.2.2.tgz",
      "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==",
      "requires": {
        "delegate": "^3.1.2"
      }
    },
    "gopd": {
@@ -5323,6 +5388,11 @@
      "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
      "optional": true
    },
    "select": {
      "version": "1.1.2",
      "resolved": "https://registry.npmmirror.com/select/-/select-1.1.2.tgz",
      "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA=="
    },
    "semver": {
      "version": "6.3.1",
      "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz",
@@ -5395,6 +5465,11 @@
      "requires": {
        "copy-anything": "^3.0.2"
      }
    },
    "tiny-emitter": {
      "version": "2.1.0",
      "resolved": "https://registry.npmmirror.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
      "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q=="
    },
    "tinyglobby": {
      "version": "0.2.14",
@@ -5562,6 +5637,14 @@
        "@vue/shared": "3.5.18"
      }
    },
    "vue-clipboard3": {
      "version": "2.0.0",
      "resolved": "https://registry.npmmirror.com/vue-clipboard3/-/vue-clipboard3-2.0.0.tgz",
      "integrity": "sha512-Q9S7dzWGax7LN5iiSPcu/K1GGm2gcBBlYwmMsUc5/16N6w90cbKow3FnPmPs95sungns4yvd9/+JhbAznECS2A==",
      "requires": {
        "clipboard": "^2.0.6"
      }
    },
    "vue-router": {
      "version": "4.5.1",
      "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.1.tgz",
package.json
@@ -20,6 +20,7 @@
    "pinia": "^3.0.3",
    "spark-md5": "^3.0.2",
    "vue": "^3.5.18",
    "vue-clipboard3": "^2.0.0",
    "vue-router": "^4.5.1"
  },
  "devDependencies": {
src/assets/base.css
@@ -3,19 +3,88 @@
   License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
    margin: 0;
    padding: 0;
    border: 0;
@@ -24,22 +93,35 @@
    vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
    display: block;
}
body {
    line-height: 1;
    font-size: 14px;
}
ol, ul {
ol,
ul {
    list-style: none;
}
blockquote, q {
blockquote,
q {
    quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
blockquote:before,
blockquote:after,
q:before,
q:after {
    content: '';
    content: none;
}
src/assets/images/default-book-img.png

src/assets/images/personalCenter/book-cover.png
src/assets/images/personalCenter/collect-click.png
src/assets/images/personalCenter/notification.svg
New file
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1755764010884" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7634" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32"><path d="M580.205386 310.17543C577.496936 310.092291 576.262736 306.37798 573.580048 307.257377 573.585904 308.22928 573.590587 309.203526 573.59527 310.17543L580.205386 310.17543 580.205386 310.17543ZM657.335891 309.950603C620.414092 303.49974 586.829553 311.777314 556.109201 332.861768 528.873652 351.553933 506.245862 375.465104 482.403779 397.969943 453.357912 425.390505 423.285106 451.412932 387.851607 470.399011 355.473164 487.748077 321.502205 499.77392 284.133096 499.172043 279.79583 499.101785 278.890671 500.77744 279.498404 504.416808 279.992552 507.371162 280.274756 510.389918 281.069844 513.265816 286.38721 532.509507 298.574647 546.57752 314.402625 558.145515 320.29962 562.459362 326.251652 564.132676 333.211886 562.520252 341.734194 560.542487 350.440342 559.084632 358.708548 556.32583 399.432481 542.739085 431.48071 517.028136 458.226795 484.096997 486.517388 449.265377 515.366532 414.896289 548.303525 384.312941 582.077762 352.950899 617.321563 323.70948 664.584182 314.737522 665.46475 313.759763 665.474118 312.787859 664.56896 311.819469 662.161452 311.185975 659.783215 310.376836 657.335891 309.950603L657.335891 309.950603ZM761.314359 234.146805C744.82361 234.329476 731.125623 248.228868 731.360986 264.541627 731.597524 280.983192 745.549612 294.701084 761.827239 294.500849 778.284025 294.298271 791.911759 280.405904 791.723233 264.027572 791.527677 247.445492 777.809783 233.965305 761.314359 234.146805L761.314359 234.146805ZM512 0C229.231081 0 0 229.22991 0 512 0 794.768916 229.231081 1024 512 1024 794.768916 1024 1024 794.768916 1024 512 1024 229.231081 794.768916 0 512 0L512 0ZM453.397725 830.388599C445.68924 853.581967 429.74885 867.448573 406.048454 872.682803 398.503905 872.63245 390.959357 872.579755 383.417151 872.529408 360.844396 866.301024 344.659274 853.008193 338.015201 830.015065 331.017495 805.794755 337.720117 785.192742 355.767079 767.915104 359.020029 764.802669 361.357281 764.596579 365.087986 767.587232 391.534302 788.770051 419.481801 807.701094 450.22323 822.176604 454.503118 824.191836 454.688132 826.505667 453.397725 830.388599L453.397725 830.388599ZM822.448265 268.575613C820.650831 269.792248 822.666069 272.811005 821.094625 274.1342 814.626202 305.161344 797.209218 319.368701 758.139863 325.517455 760.587189 333.102988 763.447863 340.590158 765.423288 348.302155 778.817993 400.635067 771.239485 451.135414 751.131621 500.268069 734.028459 542.061096 708.461534 578.369308 676.907456 610.440956 656.386238 631.299413 642.929471 655.974056 634.379061 683.694387 625.959799 710.98497 620.9785 739.12802 613.415216 766.622358 610.289903 777.981918 606.786366 789.334453 599.64346 798.881355 585.05905 818.370956 563.733377 821.321796 541.70161 820.555981 503.900414 819.239813 470.101587 804.811141 438.020571 786.168156 380.617368 752.808441 332.734134 708.20978 289.594494 658.256272 260.649331 624.737307 235.78265 588.550876 218.529604 547.470969 211.226274 530.079749 206.863247 511.922715 203.241443 493.522121 202.918255 493.040853 202.606778 492.820712 202.310523 492.852328 201.952207 490.10992 203.033011 487.2188 201.509581 484.593489L201.509581 483.089965C202.715678 479.580573 202.715678 476.072353 201.509581 472.564133L201.509581 471.06061C202.917085 468.955208 202.023636 466.552381 202.291788 464.30061 202.585702 464.334567 202.892496 464.112084 203.210998 463.624961 203.606785 462.461019 204.188757 461.32635 204.37377 460.127278 207.716884 438.43275 219.102207 423.54506 240.238184 416.148054 259.717241 409.331848 279.967965 406.067188 300.041873 401.918448 326.748144 396.398504 353.488376 391.17013 378.651311 379.939374 400.265043 370.291767 415.909179 353.231931 433.159883 338.025738 474.861575 301.262021 521.378286 273.059251 576.003955 259.926842 600.232459 254.102446 624.749019 251.617651 649.744508 254.206662 667.245799 256.019321 683.975421 260.481881 700.822139 266.97607 702.652368 234.179592 718.561137 213.123242 750.256906 204.86089 752.001652 202.85268 755.679662 205.831623 757.253441 203.388983 761.779234 203.336289 766.303853 203.292963 770.829647 203.442847 795.110844 202.33511 823.773805 230.489872 822.448271 255.574353 822.448265 259.909277 822.448265 264.24186 822.448265 268.575613L822.448265 268.575613Z" fill="#1C9E3A" p-id="7635"></path></svg>
src/assets/js/config.js
@@ -1,4 +1,4 @@
export const requestCtx = 'http://182.92.203.7:5001' // 请求地址
export const requestCtx = 'https://yxjy.pumcp.com/' // 请求地址
// export const appId = 1051;
// export const requestCtx = 'http://172.31.31.145' // 请求地址
@@ -6,15 +6,16 @@
export const appId = 1
export const requestTimeOut = 300000 // 请求超时时间
export const tokenKey = 'jilin-token'
export const userInfoKey = 'jilin-userInfo' // 用户信息key
export const appRefCode = 'jilinWebsite'
export const tokenKey = 'xiehe-token'
export const userInfoKey = 'xiehe-userInfo' // 用户信息key
export const appRefCode = 'PUMC'
export const goodsStore = `defaultGoodsStore${appId}` // 默认商品库(书城)
export const publicStore = `defaultPublicStore${appId}` // 默认资源开放仓储
export const publicRepository = `defaultPublicRepository${appId}` // 默认资源开放库
export const reg_tel =
  /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/ // 电话号正则
export const reg_telphone = /^0\d{2}-\d{8}$|^0\d{3}-\d{7}$/ //座机号正则
// refcode
const refCode = {
@@ -66,7 +67,7 @@
  appid: 'wx2b9d4a6308fd03d6',
  scope: 'snsapi_login',
  logInRedirectURL: encodeURIComponent(requestCtx + '/website'),
  authenRedirectURL:encodeURIComponent(requestCtx + '/website/#/userInfo')
  authenRedirectURL: encodeURIComponent(requestCtx + '/website/#/userInfo'),
}
const config = {
@@ -79,6 +80,7 @@
  reg_tel,
  appId,
  refCode,
  wxLogin
  wxLogin,
  reg_telphone,
}
export default config
src/assets/js/middleGround/api/edu.js
@@ -90,6 +90,24 @@
    })
  },
  // 获取班级详情
  getCourseClass(data) {
    return request({
      url: '/edu/api/ApiGetCourseClass',
      method: 'post',
      data,
    })
  },
  // 获取topic信息
  getClassTopic(data) {
    return request({
      url: '/edu/api/ApiGetClassTopic',
      method: 'post',
      data,
    })
  },
  //创建课程订单
  createCourseOrder(data) {
    return request({
src/assets/js/middleGround/api/identity.js
@@ -1,181 +1,224 @@
import request from "@/plugin/axios/index.ts";
import request from '@/plugin/axios/index.ts'
const identityApi = {
  // 获取图形验证码
  getImgCode() {
    return request({
      url: "/identity/NewCaptcha",
      method: "post",
    });
      url: '/identity/NewCaptcha',
      method: 'post',
    })
  },
  // 验证图形验证码
  verificationImgCode(data) {
    return request({
      url: "/identity/ValidCaptcha",
      method: "post",
      url: '/identity/ValidCaptcha',
      method: 'post',
      data,
    });
    })
  },
  // 获取短信验证码
  getPhoneCode(data) {
    return request({
      url: "/identity/NewSms",
      method: "post",
      url: '/identity/NewSms',
      method: 'post',
      data,
    });
    })
  },
  // 验证短信验证码
  verificationPhoneCode(data) {
    return request({
      url: "/identity/api/ApiValidMobilePhone",
      method: "post",
      url: '/identity/api/ApiValidMobilePhone',
      method: 'post',
      data,
    });
    })
  },
  // 通过手机号注册用户
  registerAppUserWithPhone(data) {
    return request({
      url: "/identity/api/RegisterAppUserWithPhone",
      method: "post",
      url: '/identity/api/RegisterAppUserWithPhone',
      method: 'post',
      data,
    });
    })
  },
  // 账号密码登录
  loginByPassword(data) {
    return request({
      url: "/identity/api/LoginByPassword",
      method: "post",
      url: '/identity/api/LoginByPassword',
      method: 'post',
      data,
    });
    })
  },
  // 短信验证码登录
  loginByMobilePhone(data) {
    return request({
      url: "/identity/api/LoginByMobilePhone",
      method: "post",
      url: '/identity/api/LoginByMobilePhone',
      method: 'post',
      data,
    });
    })
  },
  // 设置用户key
  setUserKey(data) {
    return request({
      url: "/identity/api/ApiAppUserSetKey",
      method: "post",
      url: '/identity/api/ApiAppUserSetKey',
      method: 'post',
      data,
    });
    })
  },
  // 获取用户key
  getUserKey(data) {
    return request({
      url: "/identity/api/ApiGetAppUserKey",
      method: "post",
      url: '/identity/api/ApiGetAppUserKey',
      method: 'post',
      data,
    });
    })
  },
  // 删除用户key
  delUserKey(data) {
    return request({
      url: "/identity/api/ApiDelAppUserKey",
      method: "post",
      url: '/identity/api/ApiDelAppUserKey',
      method: 'post',
      data,
    });
    })
  },
  // 获取去当前用户信息
  getCurrentAppUser() {
    return request({
      url: "/identity/api/GetCurrentAppUser",
      method: "post",
    });
      url: '/identity/api/GetCurrentAppUser',
      method: 'post',
    })
  },
  // 添加用户信息
  setAppUserInfo(data) {
    return request({
      url: "/identity/api/SetAppUserInfoRequest",
      method: "post",
      url: '/identity/api/SetAppUserInfoRequest',
      method: 'post',
      data,
    });
    })
  },
  // 用户更换绑定手机号,如没有绑定手机则自动创建
  userSetPhoneNumber(data) {
    return request({
      url: "/identity/api/ApiUserSetPhoneNumber",
      method: "post",
      url: '/identity/api/ApiUserSetPhoneNumber',
      method: 'post',
      data,
    });
    })
  },
  // 检测用户是否绑定微信
  checkBuildingWeChat(data) {
    return request({
      url: "/identity/api/ApiCheckBuildingWeChat",
      method: "post",
      url: '/identity/api/ApiCheckBuildingWeChat',
      method: 'post',
      data,
    });
    })
  },
  // 通过手机号重置密码
  changePasswordByMobilePhone(data) {
    return request({
      url: "/identity/api/ChangePasswordByMobilePhone",
      method: "post",
      url: '/identity/api/ChangePasswordByMobilePhone',
      method: 'post',
      data,
    });
    })
  },
  // 微信开放平台扫码登录
  loginByWeChatOpenCode(data) {
    return request({
      url: "/identity/api/LoginByWeChatOpenCode",
      method: "post",
      url: '/identity/api/LoginByWeChatOpenCode',
      method: 'post',
      data,
    });
    })
  },
  // 用户绑定微信号
  bindingWeChat(data) {
    return request({
      url: "/identity/api/ApiBindingWeChat",
      method: "post",
      url: '/identity/api/ApiBindingWeChat',
      method: 'post',
      data,
    });
    })
  },
  // 设置登录的用户名和密码,用户名和密码至少6位
  setLoginNameAndPassword(data) {
    return request({
      url: "/identity/api/ApiUserSetLoginNameAndPassword",
      method: "post",
      url: '/identity/api/ApiUserSetLoginNameAndPassword',
      method: 'post',
      data,
    });
    })
  },
  // 获取邮箱验证码
  getEmailCode(data) {
    return request({
      url: "/identity/api/SendVerifyEMail",
      method: "post",
      url: '/identity/api/SendVerifyEMail',
      method: 'post',
      data,
    });
    })
  },
  // 用户绑定邮箱
  bindingEmail(data) {
    return request({
      url: "/identity/api/ApiBindEMail",
      method: "post",
      url: '/identity/api/ApiBindEMail',
      method: 'post',
      data,
    });
    })
  },
};
  // 通过refcode加入班级/组
  joinGroupByRefCode(data) {
    return request({
      url: '/identity/api/ApiJoinGroupByRefCode',
      method: 'post',
      data,
    })
  },
export default identityApi;
  // 获取加入组的列表信息
  joinedGroupByList(data) {
    return request({
      url: '/identity/api/ApiGetJoinedGroupByList',
      method: 'post',
      data,
    })
  },
  // 获取组或班级成员
  getGroupUserList(data) {
    return request({
      url: '/identity/api/ApiGetGroupUserList',
      method: 'post',
      data,
    })
  },
  // 更新组成员或班级成员状态
  updateAppUserGroupLink(data) {
    return request({
      url: '/identity/api/ApiUpdateAppUserGroupLink',
      method: 'post',
      data,
    })
  },
  // 删除组成员或班级成员状态
  removeAppUserFromGroup(data) {
    return request({
      url: '/identity/api/ApiRemoveAppUserFromGroup',
      method: 'post',
      data,
    })
  },
}
export default identityApi
src/assets/main.css
@@ -1,5 +1,14 @@
@import './base.css';
:root {
  --el-color-primary: #019e58 !important;
  --el-color-primary-light-3: #019e58 !important;
  --el-color-primary-light-5: #019e58 !important;
  --el-color-primary-light-7: #019e58 !important;
  --el-color-primary-dark-2: #019e58 !important;
  --el-color-primary-dark-3: #019e58 !important;
}
/* 居中布局 */
.contentBox {
  width: 1200px;
@@ -99,7 +108,7 @@
}
.avatar-uploader .el-upload:hover {
  border-color: #409eff;
  border-color: #019e58;
}
.avatarCover {
@@ -169,16 +178,6 @@
.el-input-group__prepend {
  vertical-align: initial !important;
}
.el-checkbox__inner {
  border: 1px solid #000 !important;
  width: 20px !important;
  height: 20px !important;
}
.el-checkbox__inner::after {
  left: 7px !important;
  top: 4px !important;
}
/* 个人中心页面公共样式 */
.personalPage-title {
@@ -195,7 +194,7 @@
  border-radius: 4px 4px 0px 0px;
}
.personalPage-content {
  padding: 20px 40px;
  padding: 20px 20px;
}
/* 两边对齐 */
@@ -207,3 +206,18 @@
  cursor: pointer;
  transition: all 0.2s;
}
.no {
  color: #ee1818;
}
.yes {
  color: #1fbc1f;
}
.wait,
.main {
  color: #ff6d00;
}
.grey {
  color: #949494;
}
src/router/index.js
@@ -45,7 +45,7 @@
              path: '/myBook',
              name: 'myBook',
              meta: {
                name: '图书',
                name: '书架',
              },
              component: () => import('@/views/personalCenter/myBook.vue'),
            },
@@ -105,6 +105,138 @@
              },
              component: () => import('@/views/personalCenter/activeCode.vue'),
            },
            {
              path: '/courseDetail',
              name: 'courseDetail',
              meta: {
                name: '课程详情',
              },
              component: () => import('@/views/courseManage/index.vue'),
            },
          ],
        },
        // 班级
        {
          path: '/classManage',
          name: 'classManage',
          redirect: '/classHome',
          meta: {
            name: '班级管理',
          },
          component: () => import('../views/classManage/index.vue'),
          children: [
            {
              path: '/classHome',
              name: 'classHome',
              meta: {
                name: '班级首页',
              },
              component: () => import('@/views/classManage/classHome.vue'),
            },
            {
              path: '/studentManage',
              name: 'studentManage',
              meta: {
                name: '学生管理',
              },
              component: () => import('@/views/classManage/studentManage.vue'),
            },
            {
              path: '/teachingPlan',
              name: 'teachingPlan',
              meta: {
                name: '教学计划',
              },
              component: () => import('@/views/classManage/teachingPlan.vue'),
            },
            {
              path: '/prepareLessons',
              name: 'prepareLessons',
              meta: {
                name: '备课',
              },
              component: () => import('@/views/classManage/prepareLessons.vue'),
            },
            {
              path: '/jobManage',
              name: 'jobManage',
              meta: {
                name: '作业管理-教师',
              },
              component: () => import('@/views/classManage/jobManage.vue'),
            },
            {
              path: '/studentJob',
              name: 'studentJob',
              meta: {
                name: '作业管理-学生',
              },
              component: () => import('@/views/classManage/studentJob.vue'),
            },
            {
              path: '/jobDetail',
              name: 'jobDetail',
              meta: {
                name: '作业管理详情',
              },
              component: () => import('@/views/classManage/jobDetail.vue'),
            },
            {
              path: '/testManage',
              name: 'testManage',
              meta: {
                name: '测试管理',
              },
              component: () => import('@/views/classManage/testManage.vue'),
            },
            {
              path: '/jobAnalysis',
              name: 'jobAnalysis',
              meta: {
                name: '作业分析',
              },
              component: () => import('@/views/classManage/jobAnalysis.vue'),
            },
            {
              path: '/teachInteraction',
              name: 'teachInteraction',
              meta: {
                name: '教学互动',
              },
              component: () => import('@/views/classManage/teachInteraction.vue'),
            },
            {
              path: '/interactionDetail',
              name: 'interactionDetail',
              meta: {
                name: '教学互动-详情',
              },
              component: () => import('@/views/classManage/interactionDetail.vue'),
            },
            {
              path: '/talkingPoint',
              name: 'talkingPoint',
              meta: {
                name: '话题',
              },
              component: () => import('@/views/classManage/talkingPoint.vue'),
            },
            {
              path: '/talkDetail',
              name: 'talkDetail',
              meta: {
                name: '话题',
              },
              component: () => import('@/views/classManage/talkDetail.vue'),
            },
            {
              path: '/info',
              name: 'info',
              meta: {
                name: '班级通知列表',
              },
              component: () => import('@/views/classManage/infoList.vue'),
            },
          ],
        },
      ],
src/store/counter.js
File was deleted
src/store/index.ts
New file
@@ -0,0 +1,9 @@
import { createPinia } from "pinia";
// 创建pinia实例
const pinia = createPinia()
export default pinia
export * from './modules/breadcrumb'
export * from './modules/user'
src/store/modules/breadcrumb.ts
New file
@@ -0,0 +1,96 @@
// 面包屑数据
import { defineStore } from 'pinia'
import tool from '@/assets/js/toolClass.js'
export const useBreadcrumbStore = defineStore('breadcrumb', () => {
  const crumbList = localStorage.getItem('crumbs')
    ? JSON.parse(localStorage.getItem('crumbs'))
    : {
        home: {}, // 首页
        bookService: {}, // 图书服务
        digitalCourse: {}, // 数字课程
        digitalBook: {}, // 数字教材
        seminar: {}, // 数字教材
        shoppingCart: {}, // 购物车
        personalCenter: {}, //个人中心
        retrievalPage: {}, //全局检索
        digitalCourses: {}, //数字课程
        digitalTextbooks: {} //数字教材
      }
  // 面包屑
  const changeCrumbs = (data: any) => {
    switch (data.type) {
      case 'home':
        data.data.unshift({
          name: '首页',
          // isCrumbs: true,
          path: '/home'
        })
        break
      case 'bookService':
        data.data.unshift({
          name: '图书服务',
          // isCrumbs: true,
          path: '/bookService'
        })
        break
      case 'personalCenter':
        data.data.unshift({
          name: '个人中心',
          // isCrumbs: true,
          path: '/personalCenter'
        })
        break
      case 'retrievalPage':
        data.data.unshift({
          name: '全局检索',
          // isCrumbs: true,
          path: '/retrievalPage'
        })
        break
      case 'digitalCourses':
        data.data.unshift({
          name: '数字课程',
          // isCrumbs: true,
          path: '/digitalCourses'
        })
        break
      case 'digitalTextbooks':
        data.data.unshift({
          name: '数字教材',
          // isCrumbs: true,
          path: '/digitalTextbooks'
        })
        break
      case 'shoppingCart':
        data.data.unshift({
          name: '购物车',
          path: '/shoppingCart'
        })
    }
    if (!crumbList[data.type].keys) {
      crumbList[data.type].keys = []
    }
    crumbList[data.type].keys.push(data.cid)
    crumbList[data.type][data.cid] = data.data
    if (crumbList[data.type].keys.length > 20) {
      const key = crumbList[data.type].keys[0]
      delete crumbList[data.type][key]
      crumbList[data.type].keys.splice(0, 1)
    }
    localStorage.setItem('crumbs', JSON.stringify(crumbList))
  }
  const setCrumbs = (data: any) => {
    // 存储面包屑
    if (!data.cid) {
      data.cid = tool.uuid(8)
    }
    changeCrumbs(data)
    if (data.callback) data.callback(data.cid)
  }
  return {
    crumbList,
    setCrumbs
  }
})
src/store/modules/user.ts
New file
@@ -0,0 +1,149 @@
// 用户信息
import { defineStore } from 'pinia'
import config from '@/assets/js/config'
import { ref } from 'vue'
interface userInfo {
  userName: string
  userType: string
  roleId?: any
  role?: any
  userId?: number
  name?: string
}
export const useUserStore = defineStore('user', () => {
  const token = localStorage.getItem(config.tokenKey)
    ? ref<string>(localStorage.getItem(config.tokenKey) as string)
    : ref<string>()
  const userInfo = localStorage.getItem(config.userInfoKey)
    ? ref<userInfo>(JSON.parse(localStorage.getItem(config.userInfoKey) as string))
    : ref<userInfo>()
  const setToken = (value: string) => {
    token.value = value
    localStorage.setItem(config.tokenKey, value)
  }
  const setUserInfo = (value: userInfo) => {
    userInfo.value = value
    localStorage.setItem(config.userInfoKey, JSON.stringify(value))
  }
  // 退出登录
  const delteUserInfo = () => {
    ;((token.vlaue = ''), (userInfo.value = { userName: '', userType: '' }))
    localStorage.removeItem(config.tokenKey)
    localStorage.removeItem(config.userInfoKey)
    localStorage.removeItem('xiehe-isUserInfo')
  }
  // 购物车数量
  let cartNum = ref<number>(1)
  // 更新右侧弹出框购物车的数量
  const updateRightPop = () => {
    cartNum.value += 1
  }
  // 购物车商品id
  const shoppingIds = ref<number[]>([])
  const updateShoppingIds = (ids: number[]) => {
    shoppingIds.value = ids
  }
  // 已购买的商品id
  const buyIds = ref<number[]>([])
  const updateBuyIds = (ids: number[]) => {
    buyIds.value = ids
  }
  return {
    token,
    setToken,
    userInfo,
    setUserInfo,
    delteUserInfo,
    cartNum,
    updateRightPop,
    shoppingIds,
    updateShoppingIds,
    buyIds,
    updateBuyIds,
  }
})
export const applyBookStore = defineStore('applyBook', () => {
  //样书申请
  let electronicBookList = localStorage.getItem('electronicBookList')
    ? ref(JSON.parse(localStorage.getItem('electronicBookList')))
    : ref([])
  let paperBookList = localStorage.getItem('paperBookList')
    ? ref(JSON.parse(localStorage.getItem('paperBookList')))
    : ref([])
  //已申请图书
  let alreadyPaperBook = localStorage.getItem('alreadyPaperBook')
    ? ref(JSON.parse(localStorage.getItem('alreadyPaperBook')))
    : ref([])
  let alreadyElectronicBook = localStorage.getItem('alreadyElectronicBook')
    ? ref(JSON.parse(localStorage.getItem('alreadyElectronicBook')))
    : ref([])
  // 添加电子样书列表
  const appplyElectronicBook = (value: data) => {
    electronicBookList.value.push(value)
    localStorage.setItem('electronicBookList', JSON.stringify(electronicBookList.value))
  }
  //添加纸质样式列表
  const appplyPaperBook = (value: data) => {
    paperBookList.value.push(value)
    localStorage.setItem('paperBookList', JSON.stringify(paperBookList.value))
  }
  //删除电子样书列表
  const removeElectronicBook = (value: int) => {
    electronicBookList.value.splice(value, 1)
    localStorage.setItem('electronicBookList', JSON.stringify(electronicBookList.value))
  }
  //删除纸质样式
  const removePaperBook = (value: int) => {
    paperBookList.value.splice(value, 1)
    localStorage.setItem('paperBookList', JSON.stringify(paperBookList.value))
  }
  const emptyBookList = (value: data) => {
    if (value && value.type == 'eBook') {
      electronicBookList.value = []
      localStorage.setItem('electronicBookList', JSON.stringify(electronicBookList.value))
    } else if (value && value.type == 'paperBook') {
      paperBookList.value = []
      localStorage.setItem('paperBookList', JSON.stringify(paperBookList.value))
    } else {
      paperBookList.value = []
      electronicBookList.value = []
      alreadyPaperBook.value = []
      alreadyElectronicBook.value = []
      localStorage.removeItem('electronicBookList')
      localStorage.removeItem('paperBookList')
    }
  }
  //已申请图书
  const alreadyPaperBookList = (value: data) => {
    alreadyPaperBook.value = value.list
    localStorage.setItem('alreadyPaperBook', JSON.stringify(alreadyPaperBook.value))
  }
  const alreadyElectronicBookList = (value: data) => {
    alreadyElectronicBook.value = value.list
    localStorage.setItem('alreadyElectronicBook', JSON.stringify(alreadyElectronicBook.value))
  }
  return {
    electronicBookList,
    paperBookList,
    alreadyPaperBook,
    alreadyElectronicBook,
    appplyElectronicBook,
    appplyPaperBook,
    removeElectronicBook,
    removePaperBook,
    emptyBookList,
    alreadyPaperBookList,
    alreadyElectronicBookList,
  }
})
src/views/classManage/classHome.vue
New file
@@ -0,0 +1,778 @@
<template>
  <div class="classManagePage-box">
    <div class="classManagePage-nav" v-if="classInfo">
      <el-breadcrumb :separator-icon="ArrowRight">
        <el-breadcrumb-item>我的班级</el-breadcrumb-item>
        <el-breadcrumb-item>{{ classInfo?.name }}</el-breadcrumb-item>
        <el-breadcrumb-item>班级首页</el-breadcrumb-item>
      </el-breadcrumb>
    </div>
    <div class="classManagePage-content">
      <div class="content-left fl">
        <div class="classOverview mainBorder">
          <div class="">
            <div class="title mainbg">班级概览</div>
          </div>
          <div class="bodyBox" v-if="currentClass?.name && !classLoading">
            <div class="classInfo">
              <div class="name">{{ currentClass?.name }}</div>
              <!-- <div class="time">报名时间:2024.7.30--2024-8.03</div> -->
              <div class="time">开课时间: {{ currentClass?.classTime }}</div>
            </div>
            <div class="line"></div>
            <div class="classInfoBox">
              <div class="iconBox">
                <img :src="currentClass?.bookIcon ? currentClass?.bookIcon : defaultImg" />
              </div>
              <div class="infoBox">
                <div class="main">{{ currentClass?.bookName }}</div>
                <div class="job">作者:{{ classInfo?.author ?? '-' }}</div>
                <div class="job">ISBN:{{ classInfo?.isbn ?? '-' }}</div>
              </div>
            </div>
            <div class="line"></div>
            <div class="classInfo">
              <div class="name">班级人数</div>
              <div class="con">
                <span class="main">{{ currentClass?.memberCount }}</span
                >/{{ currentClass?.maxUserCount }}
              </div>
            </div>
            <div class="line"></div>
            <div class="classInfo">
              <div class="name">作业次数</div>
              <div class="con">
                <span class="main">{{ homeworkCount }}</span>
              </div>
            </div>
            <!-- <div class="line"></div>
            <div class="classInfo">
              <div class="name">教学进度</div>
              <div class="con"><span class="main">0</span>/0</div>
            </div> -->
          </div>
          <div
            class="bodyBox"
            style="display: flex; justify-content: center; align-items: center"
            v-if="classLoading"
          >
            <div v-loading="classLoading"></div>
          </div>
          <div v-if="!currentClass && !classLoading">
            <el-empty style="padding: 0" :image-size="100" />
          </div>
        </div>
        <div class="classNotice">
          <div class="titleBox">
            <div class="border mainbg"></div>
            <div class="title">
              <span>班级通知</span>
              <el-icon
                style="cursor: pointer"
                color="#FF6D00"
                v-if="noticeList.length > 0 && userInfo.role == 'Teacher'"
                @click="toInfo"
              >
                <ArrowRightBold />
              </el-icon>
            </div>
          </div>
          <div class="noticeList" v-if="noticeList.length > 0 && !noticeLoading">
            <div class="noticeItem" v-for="(item, index) in noticeList" :key="index">
              <div class="noticeContent">
                <span class="title">{{ item.name }}:</span>
                <div class="contentText">{{ item.content }}</div>
              </div>
              <span class="time">{{ item.createDate }}</span>
            </div>
          </div>
          <div v-if="noticeLoading" v-loading="noticeLoading"></div>
          <div v-if="noticeList.length == 0 && !noticeLoading" class="notBox">
            <el-empty :image-size="100" />
          </div>
        </div>
        <div class="classTalk">
          <div class="titleBox">
            <div class="border mainbg"></div>
            <div class="title">
              <span>班组话题</span>
              <el-icon
                style="cursor: pointer"
                color="#FF6D00"
                v-if="messageList.length > 0"
                @click="toTalk"
              >
                <ArrowRightBold />
              </el-icon>
            </div>
          </div>
          <div class="noticeList" v-if="messageList.length > 0 && !messageLoading">
            <div class="noticeItem" v-for="(item, index) in messageList" :key="index">
              <div class="noticeContent">
                <span class="title">{{ item.name }}</span>
                <div class="content" v-if="item.publicText">
                  <span>{{ item.publicText.publishRole }}:</span>
                  <span>{{ item.publicText.publisher }}</span>
                </div>
                <span>最近回复:{{ item.updateDate }}</span>
              </div>
              <span class="time">{{ item.createDate }}</span>
            </div>
          </div>
          <div v-if="messageLoading" v-loading="messageLoading"></div>
          <div v-if="messageList.length == 0 && !messageLoading" class="notBox">
            <el-empty :image-size="100" />
          </div>
        </div>
        <div class="classTask">
          <div class="titleBox" style="margin-bottom: 5px">
            <div class="border mainbg"></div>
            <div class="title">
              <span>班级作业概览</span>
              <!-- <el-icon
                style="cursor: pointer"
                color="#FF6D00"
                v-if="tableData.length > 0"
                @click="toWorkList"
              >
                <ArrowRightBold />
              </el-icon> -->
            </div>
          </div>
          <div class="tableWall">
            <el-table :data="tableData" border style="width: 100%; font-size: 14px" size="small">
              <el-table-column prop="name" label="名称" />
              <el-table-column prop="beginDate" label="开始时间" />
              <el-table-column prop="endDate" label="截止时间" />
              <el-table-column prop="submitCount" label="完成情况(提交人数)" width="180" />
            </el-table>
          </div>
        </div>
        <!-- <div class="classTask">
          <div class="titleBox" style="margin-bottom: 5px">
            <div class="border mainbg"></div>
            <div class="title">
              <span>教学互动</span>
              <el-icon
                style="cursor: pointer"
                color="#FF6D00"
                v-if="tableData.length > 0"
                @click="toTeaching"
              >
                <ArrowRightBold />
              </el-icon>
            </div>
          </div>
          <div class="tableWall">
            <el-table :data="[]" border style="width: 100%; font-size: 14px" size="small">
              <el-table-column prop="index" label="序号" />
              <el-table-column prop="name" label="标题" />
              <el-table-column prop="beginDate" label="互动学生数(已互动/全部学生)" />
              <el-table-column prop="endDate" label="最后提交时间" />
            </el-table>
          </div>
        </div> -->
      </div>
      <div class="content-right fr">
        <div class="assistant">
          <div class="titleBox">
            <div class="border mainbg"></div>
            <div class="title">助教列表</div>
            <div class="options">
              <!-- <div class="optionsItem">移除</div> -->
              <div class="copyIdBtn" @click="copy(currentClass)">复制邀请码</div>
            </div>
          </div>
          <div class="avatarList" style="max-height: 147px; overflow-y: auto">
            <div class="avatarItem" v-for="(item, index) in teacherList" :key="index">
              <el-avatar v-if="item.appUser?.icon" :size="35" :src="item.appUser?.icon" />
              <el-avatar v-else :size="35" :icon="UserFilled" />
              <el-tooltip :content="item.appUser?.name" placement="top" effect="light">
                <span class="userName">{{ item.appUser?.name }}</span>
              </el-tooltip>
            </div>
            <div class="nullBox" v-if="!Sloading && teacherList.length == 0">
              <el-empty :image-size="100" />
            </div>
          </div>
        </div>
        <div class="student">
          <div class="titleBox">
            <div class="border mainbg"></div>
            <div class="title">学生列表</div>
            <div class="options">
              <div class="copyIdBtn" @click="copy(currentClass)">复制邀请码</div>
            </div>
          </div>
          <div class="avatarList" style="max-height: 570px; overflow-y: auto">
            <div class="avatarItem02" v-for="(item, index) in studentList" :key="index">
              <el-avatar v-if="item.appUser?.icon" :size="35" :src="item.appUser?.icon" />
              <el-avatar v-else :size="35" :icon="UserFilled" />
              <el-tooltip :content="item.appUser?.name" placement="top" effect="light">
                <span class="userName">{{ item.appUser?.name }}</span>
              </el-tooltip>
            </div>
            <div class="nullBox" v-if="!Sloading && studentList.length == 0">
              <el-empty :image-size="100" />
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router'
import { getPublicImage } from '@/assets/js/middleGround/tool.js'
import useClipboard from 'vue-clipboard3'
import { inject, onMounted, ref } from 'vue'
import moment from 'moment'
import { ElMessage } from 'element-plus'
import { ArrowRight, UserFilled } from '@element-plus/icons-vue'
import defaultImg from '@/assets/images/default-book-img.png'
const { toClipboard } = useClipboard()
const route: any = useRoute()
const router: any = useRouter()
const classInfo = JSON.parse(route.query.classInfo)
const MG: any = inject('MG')
const config: any = inject('config')
const userInfo: any = ref()
const currentClass = ref()
const classLoading = ref(true)
const messageTopicInfo = ref()
const talkTopicInfo = ref()
const noticeList: any = ref([])
const noticeLoading = ref(true)
const messageList: any = ref([])
const messageLoading = ref(true)
const tableData: any = ref([])
const homeworkCount = ref(0)
const studentList: any = ref([])
const teacherList: any = ref([])
const Sloading = ref(false)
onMounted(() => {
  const userCache: any = localStorage.getItem('jesk-userInfo')
  if (userCache) {
    userInfo.value = JSON.parse(userCache)
  }
  getData()
  getStudentList()
  getTaskList()
})
// 复制
const copy = async (val: any) => {
  try {
    await toClipboard(val.refCode)
    ElMessage({
      message: '复制成功',
      type: 'success'
    })
  } catch (e) {
    console.error(e)
  }
}
// 获取班级
const getData = () => {
  MG.edu
    .getCourseClass({
      ClassIdOrRefCode: String(classInfo.id)
    })
    .then((res: any) => {
      if (res) {
        res.bookName = res.linkProductDto.product.name
        res.bookIcon = getPublicImage(res.linkProductDto.product.icon, 100)
        res.classTime =
          moment(res.beginDate).format('YYYY.MM.DD') +
          '--' +
          moment(res.endDate).format('YYYY.MM.DD')
      }
      currentClass.value = res
      classLoading.value = false
      getTopicInfo()
    })
}
// 获取topic
const getTopicInfo = () => {
  const pramas = {
    classId: classInfo.id,
    refCodes: [config.refCodes.message, config.refCodes.talk]
  }
  MG.edu.getClassTopic(pramas).then((res: any) => {
    const list = res
    messageTopicInfo.value = list.find((item: any) => item.refCode == config.refCodes.message)
    if (messageTopicInfo.value.id) {
      sessionStorage.messageId = messageTopicInfo.value.id
      noticeLoading.value = true
      getNotice()
    }
    talkTopicInfo.value = list.find((item: any) => item.refCode == config.refCodes.talk)
    if (talkTopicInfo.value.id) {
      sessionStorage.talkId = talkTopicInfo.value.id
      messageLoading.value = true
      getMessage()
    }
  })
}
// 获取班级通知
const getNotice = () => {
  const data = {
    start: 0,
    size: 3,
    appRefCode: config.appRefCode,
    topicIdOrRefCode: String(messageTopicInfo.value.id),
    sort: {
      type: 'Desc',
      field: 'CreateDate',
      subSorts: []
    }
  }
  MG.ugc.getTopicMessageList(data).then((res: any) => {
    noticeLoading.value = false
    const list = res.datas
    noticeList.value = list.map((item: any) => {
      return {
        ...item,
        createDate: moment(item.createDate).format('YYYY-MM-DD')
      }
    })
  })
}
// 获取班级话题
const getMessage = () => {
  const data = {
    start: 0,
    size: 3,
    appRefCode: config.appRefCode,
    topicIdOrRefCode: String(talkTopicInfo.value.id),
    sort: {
      type: 'Desc',
      field: 'CreateDate',
      subSorts: []
    }
  }
  MG.ugc.getTopicMessageList(data).then((res: any) => {
    messageLoading.value = false
    const list = res.datas
    messageList.value = list.map((item: any, i: number) => {
      const str = item.content.indexOf('publisher')
      if (str > -1) {
        item.publicText = JSON.parse(item.content)
        if (item.publicText && item.publicText.publishRole) {
          item.publicText.publishRole = item.publicText.publishRole == 'Teacher' ? '助教' : '学生'
        }
      }
      return {
        ...item,
        index: i + 1,
        createDate: moment(item.createDate).format('YYYY-MM-DD HH:mm:ss'),
        updateDate: moment(item.updateDate).format('YYYY-MM-DD HH:mm:ss')
      }
    })
  })
}
// 获取教师/学生列表
const getStudentList = () => {
  Sloading.value = true
  const data = {
    start: 0,
    size: 999,
    groupId: classInfo.id,
    filterList: [
      {
        value: 'Normal',
        field: 'State',
        subFilters: []
      }
    ]
  }
  MG.identity.getGroupUserList(data).then((res: any) => {
    const { datas } = res
    Sloading.value = false
    let list: any = []
    if (datas.length > 0) {
      list = datas.map((item: any, index: number) => {
        if (item.linkType == 'Creator') {
          const userInfo = item.appUser?.infoList?.find((citem: any) => citem.type == 'teacherInfo')
          item.appUser.name = userInfo.name
          item.appUser.icon = userInfo.icon
          if (userInfo?.data) {
            const iconData = JSON.parse(userInfo.data)
            item.appUser.icon = getPublicImage(iconData?.relevantCertificates[0]?.md5, 100) ?? ''
          }
        }
        if (item.linkType == 'RefCode' || item.linkType == 'Teacher') {
          let userInfo = null
          const wechatData = item.appUser?.infoList?.find((citem: any) => citem.type == 'WeChat')
          const defaultData = item.appUser?.infoList?.find((citem: any) => citem.type == 'Default')
          userInfo = defaultData
          if (wechatData?.name) {
            userInfo = wechatData
          }
          item.appUser.name = userInfo.name
          item.appUser.icon = userInfo.icon
        }
        return {
          ...item,
          index: index + 1,
          createDate: moment(item.createDate).format('YYYY-MM-DD')
        }
      })
    }
    teacherList.value = list.filter((item: any) => item.linkType != 'RefCode')
    studentList.value = list.filter((item: any) => item.linkType == 'RefCode')
  })
}
// 前往话题
const toTalk = () => {
  classInfo.index = 5
  if (userInfo.value?.role != 'Teacher') {
    classInfo.index = 3
  }
  router.push({
    path: '/talkingPoint',
    query: {
      classInfo: JSON.stringify(classInfo)
    }
  })
}
// 前往通知
const toInfo = () => {
  classInfo.index = 0
  router.push({
    path: '/info',
    query: {
      classInfo: JSON.stringify(classInfo)
    }
  })
}
// 教学互动
const toTeaching = () => {}
// 班级作业概览
const getTaskList = () => {
  const data = {
    start: 0,
    size: 3,
    sort: {
      type: 'Desc',
      field: 'CreateDate',
      subSorts: []
    },
    filterList: [
      // {
      //   value: 'Normal',
      //   field: 'State',
      //   subFilters: []
      // },
      {
        value: config.taskType.homeWork,
        field: 'Type',
        subFilters: []
      }
    ],
    groupId: classInfo?.id
  }
  MG.edu
    .getTaskList(data)
    .then((res: any) => {
      let list: any = []
      if (res.datas.length > 0) {
        list = res.datas
          ?.map((item: any) => {
            return {
              ...item,
              beginDate: moment(item.beginDate).format('YYYY-MM-DD'),
              endDate: moment(item.endDate).format('YYYY-MM-DD')
            }
          })
          .slice(0, 3)
      }
      tableData.value = list
      homeworkCount.value = res.totalSize
    })
    .catch((e: any) => {
      console.log(e)
    })
}
</script>
<style lang="less" scoped>
.nullBox {
  width: 100%;
  display: flex;
  justify-content: center;
}
.classManagePage-box {
  padding: 20px;
  .classManagePage-nav {
    padding-bottom: 20px;
    border-bottom: 1px solid #e6e8ed;
  }
  .classManagePage-content {
    display: flex;
    justify-content: space-between;
    .content-left {
      width: 70%;
      min-width: 950px;
      .classOverview {
        height: 190px;
        margin-top: 30px;
        border-radius: 10px;
        .title {
          width: 80px;
          text-align: center;
          padding: 7px;
          border-radius: 8px 0 8px 0;
        }
        .bodyBox {
          display: flex;
          justify-content: space-between;
          margin-top: 20px;
          padding: 0 20px;
          .classInfoBox {
            padding: 0 20px;
            display: flex;
            .iconBox {
              width: 90px;
              height: 120px;
              img {
                width: 100%;
                height: 100%;
                object-fit: contain;
              }
            }
            .infoBox {
              flex: 1;
              padding-left: 10px;
              .main {
                font-size: 16px;
                line-height: 20px;
              }
              .job {
                // padding:10px;
                margin-top: 20px;
              }
            }
          }
          .line {
            width: 1px;
            height: 130px;
            background: linear-gradient(
              180deg,
              rgba(255, 255, 255, 0) 0%,
              #b7b7b7 51%,
              rgba(255, 255, 255, 0) 100%
            );
          }
          .classInfo {
            padding: 0 20px;
            font-size: 16px;
            min-width: 120px;
            .name {
              font-weight: bold;
              margin-bottom: 20px;
              text-align: center;
            }
            .con {
              margin-top: 40px;
              font-size: 22px;
              text-align: center;
            }
            .time {
              line-height: 30px;
            }
          }
        }
      }
      .classNotice {
        height: 200px;
        margin-top: 20px;
        border-radius: 20px;
        background: linear-gradient(180deg, #fffcf1 0%, #ffffff 100%);
        border: 1px solid rgba(249, 200, 35, 0.2);
      }
      .classTalk {
        height: 200px;
        margin-top: 20px;
        border-radius: 20px;
        background: linear-gradient(180deg, #edf0ff 0%, #ffffff 100%);
        border: 1px solid rgba(154, 171, 251, 0.2);
      }
      .classTask {
        min-height: 200px;
        margin-top: 20px;
        border-radius: 20px;
        border: 1px solid #e8ebf5;
        .tableWall {
          width: 100%;
          padding: 10px 5px;
          box-sizing: border-box;
        }
      }
      .titleBox {
        display: flex;
        align-items: center;
        margin: 20px 0;
        font-size: 16px;
        .title {
          width: 100%;
          display: flex;
          justify-content: space-between;
          align-items: center;
          padding-right: 10px;
        }
        .border {
          width: 4px;
          height: 23px;
          border-radius: 0px 10px 10px 0px;
          margin-right: 10px;
        }
      }
    }
    .content-right {
      width: 28%;
      margin-left: 20px;
      min-width: 270px;
      .assistant {
        min-height: 160px;
        margin-top: 30px;
        border-radius: 10px;
        border: 1px solid #e8ebf5;
        overflow: hidden;
      }
      .student {
        height: 640px;
        margin-top: 20px;
        border-radius: 10px;
        border: 1px solid #e8ebf5;
        overflow: hidden;
      }
      .titleBox {
        display: flex;
        align-items: center;
        margin: 10px 0;
        .border {
          width: 4px;
          height: 23px;
          border-radius: 0px 10px 10px 0px;
          margin-right: 10px;
        }
      }
    }
    .notBox {
      width: 100%;
      height: 100px;
      display: flex;
      align-items: center;
      justify-content: center;
    }
  }
  .options {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding-right: 10px;
    .optionsItem {
      color: #999;
      margin-right: 10px;
      cursor: pointer;
    }
    .copyIdBtn {
      background-color: #fff;
      color: #3b93fe;
      padding: 0 6px;
      border-radius: 50px;
      overflow: hidden;
      cursor: pointer;
    }
  }
  .avatarList {
    display: flex;
    flex-wrap: wrap;
    width: 100%;
    padding: 0 20px;
    box-sizing: border-box;
    .avatarItem,
    .avatarItem02 {
      width: 68px;
      max-height: 88px;
      display: flex;
      flex-direction: column;
      align-items: center;
      padding: 6px 4px;
      position: relative;
      margin-left: 1px;
      .userName {
        width: 100%;
        margin: 3px 0;
        white-space: nowrap;
        text-overflow: ellipsis;
        overflow: hidden;
        text-align: center;
        min-height: 30px;
        line-height: 30px;
        font-size: 12px;
      }
    }
    // .avatarItem:hover {
    //   background-image: url('@/assets/images/class/avabg.png');
    //   background-size: 100% 100%;
    //   background-position: center;
    //   background-repeat: no-repeat;
    //   cursor: pointer;
    // }
    .avatarItem:hover,
    .avatarItem02:hover {
      background: #eee;
    }
  }
  .noticeList {
    width: 100%;
    height: calc(100% - 63px);
    padding: 5px 25px;
    overflow: auto;
    .noticeItem {
      display: flex;
      justify-content: space-between;
      font-family: PingFang SC;
      font-weight: 400;
      font-size: 14px;
      line-height: 32px;
      .noticeContent {
        display: flex;
        justify-content: space-between;
        color: #000000;
        max-width: 800px;
        white-space: nowrap;
        text-overflow: ellipsis;
        overflow: hidden;
        .title {
          margin-right: 30px;
        }
        .content {
          margin-right: 30px;
        }
      }
      .time {
        color: #b7b7b7;
      }
    }
  }
}
</style>
src/views/classManage/components/headerPage.vue
New file
@@ -0,0 +1,288 @@
<template>
  <div class="pageHeader">
    <div class="headerBox">
      <div class="logoBox">
        <el-image :src="logo" class="logo" @click="goHome" style="cursor: pointer" />
        <div class="titleName"><img :src="titleName" /></div>
      </div>
      <div class="inputBox f1">
        <div class="signIn">
          <p v-if="!userStore.userInfo?.name" class="signInBox">
            <span @click="loginBtn">注册</span>
            <span>|</span>
            <span @click="signupBtn">登录</span>
          </p>
          <!-- 用户名 -->
          <el-dropdown trigger="click" v-else>
            <p class="el-dropdown-link signInBox">
              <span>{{ userStore.userInfo?.name }}</span>
              <el-icon class="el-icon--right">
                <CaretBottom />
              </el-icon>
            </p>
            <template #dropdown>
              <el-dropdown-menu>
                <el-dropdown-item @click="router.push('/personalCenter')"
                  >个人中心</el-dropdown-item
                >
                <el-dropdown-item @click="logOut">退出登录</el-dropdown-item>
              </el-dropdown-menu>
            </template>
          </el-dropdown>
        </div>
      </div>
    </div>
  </div>
  <login ref="loginRef"></login>
  <!-- 导航栏 -->
  <!-- 温馨提示弹窗 -->
  <el-dialog v-model="tipsDialogState" width="420" align-center class="tipsDialog">
    <template #title>温馨提示</template>
    <el-icon color="#e6a23c" size="24">
      <WarningFilled />
    </el-icon>
    <p>
      如果您已使用【京师智教扫一扫】或【云上书房】 微信注册过,请使用微信扫码登录;如果您已在【
      京师智教】PC端注册过,请使用手机号登录!继续注册将创建全新账户!
    </p>
    <template #footer>
      <el-button @click="goPhoneSignup">手机号登录</el-button>
      <el-button @click="goWechatSignUp">微信登录</el-button>
      <el-button type="primary" @click="goLogin">继续注册</el-button>
    </template>
  </el-dialog>
</template>
<script setup lang="ts">
import login from '@/layout/components/login.vue'
import { onMounted, ref, watchEffect, inject, watch } from 'vue'
import { ElMessage, ElMessageBox, type FormInstance } from 'element-plus'
import logo from '@/assets/images/header/logo.png'
import titleName from '@/assets/images/header/titleName.png'
import { useRouter } from 'vue-router'
import { useUserStore, applyBookStore } from '@/store'
const loginRef = ref()
const router = useRouter()
const userStore = useUserStore()
const applyBook = applyBookStore()
const MG = inject('MG')
const config = inject('config')
const tipsDialogState = ref<boolean>(false) // 温馨提示弹窗状态
const wechatTipsState = ref<boolean>(false)
const localData = ref(localStorage.getItem('jsek-token'))
onMounted(() => {
  // 判断是否微信扫码登录
  var url = window.location.href
  if (url.indexOf('WeChatScanningCodeLogin') > -1) {
    var querys = url.substring(url.indexOf('?') + 1).split('&')
    var result = {}
    for (var i = 0; i < querys.length; i++) {
      var temp = querys[i].split('=')
      if (temp.length < 2) {
        result[temp[0]] = ''
      } else {
        result[temp[0]] = temp[1]
      }
    }
    if (result && result.code) {
      MG.identity
        .loginByWeChatOpenCode({
          code: result.code,
          appRefCode: config.appRefCode,
          platform: 'PCWeb'
        })
        .then((res) => {
          if (res && res.status == 'Ok') {
            userStore.setToken(res.token)
            loginRef.value.getUserInfo()
            MG.app.creatUserBehavior({
              refCode:"sign"
            }).then(res => {
              if(res){
              console.log("今日已积分")
              }
            });
          }
        })
    }
  }
})
watchEffect(() => {
  localData.value = localStorage.getItem('jsek-token') || '0'
})
// 注册按钮
const loginBtn = () => {
  tipsDialogState.value = true
}
// 登录按钮
const signupBtn = () => {
  loginRef.value.logIn()
}
// 继续注册
const goLogin = () => {
  tipsDialogState.value = false
  loginRef.value.signUp()
}
// 去微信登录
const goWechatSignUp = () => {
  tipsDialogState.value = false
  // loginRef.value.logIn()
  loginRef.value.setWechatTipsState()
}
// 去手机号登录
const goPhoneSignup = () => {
  tipsDialogState.value = false
  loginRef.value.logIn()
  loginRef.value.changeSignUp('authSignUp')
}
// 退出登录
const logOut = () => {
  router.push('/home')
  userStore.delteUserInfo()
  applyBook.emptyBookList()
  sessionStorage.removeItem('cartNumber')
  localStorage.removeItem('alreadyElectronicBook')
  localStorage.removeItem('alreadyPaperBook')
  ElMessage.success('退出成功')
}
// logo首页定向
const goHome = () => {
  router.push('/home')
}
</script>
<style lang="less">
.tipsDialog {
  .el-dialog__header {
    font-size: 18px;
  }
  .el-dialog__body {
    display: flex;
    align-items: center;
    p {
      margin-left: 10px;
      line-height: 24px;
    }
  }
}
</style>
<style lang="less" scoped>
.pageHeader {
  width: 100%;
  background: #fff;
  // border-bottom:1px solid #C0C0C0;
}
.headerBox {
  width: 1200px;
  margin: 0 auto;
  height: 60px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  .logoBox {
    display: flex;
    align-items: center;
    .titleName {
      font-size: 24px;
      padding: 0 10px;
      margin-left: 15px;
      border-left: 1px solid #C0C0C0;
      color:#181818;
      font-family: Microsoft YaHei, Microsoft YaHei;
      font-weight: 400;
    }
    /deep/.el-image__inner {
      height: 48px;
      border: 0;
      padding: 0;
    }
  }
}
/** 验证码图片 */
.imgCode {
  height: 40px;
  margin-top: 5px;
}
/** 看不清换一张 */
.linkHight {
  height: 20px;
  /deep/ .el-link__inner {
    height: 100%;
  }
}
/** header登录注册文字 */
.signIn {
  height: 40px;
  width: 95px;
  min-width: 90px;
  text-align: right;
  margin-left: 80px;
  font-weight: 400;
  justify-content: space-around;
  color: #000;
  font-size: 14px;
  span:first-child,
  span:last-child {
    cursor: pointer;
  }
  .signInBox {
    height: 100%;
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  span {
    padding: 5px;
  }
  .el-dropdown {
    width: 100%;
    height: 100%;
    span {
      height: 40px;
      line-height: 40px;
      padding: 0;
      display: block;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
  }
}
.selectPhone {
  background: #fff;
}
.el-select {
  width: 100px;
  height: 40px;
  color: red;
  /deep/ .select-trigger {
    height: 100%;
    .el-input--suffix {
      height: 100%;
      background-color: #fff;
    }
  }
}
</style>
src/views/classManage/components/questionDom.vue
New file
@@ -0,0 +1,420 @@
<template>
  <div class="questionDom">
    <div class="list-item" v-for="(pitem, pindex) in questionList" :key="pindex">
      <div class="list-item-title">
        {{ pitem.name }}
        <span v-if="isPreview">(共 {{ pitem.data.length }} 题)</span>
      </div>
      <div class="list-item-box" v-for="(item, index) in pitem.data" :key="index">
        <div class="list-item-box-title-box">
          <el-checkbox
            v-if="noCheckbox"
            v-model="item.isCheck"
            @change="checkItems($event, item)"
            size="large"
          >
            <template #default>
              <i class="el-icon-check"></i>
              <span
                class="custom-checkbox"
                v-html="index + 1 + '、' + item.questionStem?.stemTxt"
              ></span>
            </template>
          </el-checkbox>
          <div class="list-item-box-title" v-else>
            <span>{{ index + 1 }}</span
            >、
            <span class="questionT" v-html="item.questionStem?.stemTxt"></span>
            <span v-if="item.score > 0 && isPreview">({{ item.score }} 分)</span>
            <el-icon v-if="isDelete" @click="deleteItem(item)" class="deletIcon">
              <Delete />
            </el-icon>
          </div>
        </div>
        <div>
          <div class="shortAnswer" v-if="item.questionType == 'shortAnswer' && !isJudge">
            <div class="anSwer" v-if="!isPreview">
              <div class="anSwerText" v-if="item.questionAnswer" style="margin: 15px 0">
                <span style="min-width: 40px">答案:</span
                ><span v-html="item.questionAnswer"></span>
              </div>
              <div :class="isPreview ? 'questionAnalysisCon' : ''" v-if="item.questionAnalysisCon">
                分析:<span v-html="item.questionAnalysisCon"></span>
              </div>
            </div>
            <div v-else>
              <!-- <el-input type="textarea" disabled :placeholder="item.userAnswer"></el-input> -->
              <div v-html="item.userAnswer"></div>
            </div>
          </div>
          <div class="discuss" v-else-if="item.questionType == 'discuss' && !isJudge">
            <div class="anSwer" v-if="!isPreview">
              <div class="anSwerText" v-if="item.questionAnswer" style="margin: 15px 0">
                <span style="min-width: 40px">答案:</span
                ><span v-html="item.questionAnswer"></span>
              </div>
              <div :class="isPreview ? 'questionAnalysisCon' : ''" v-if="item.questionAnalysisCon">
                分析:<span v-html="item.questionAnalysisCon"></span>
              </div>
            </div>
            <div v-else>
              <!-- <el-input type="textarea" disabled :placeholder="item.userAnswer"></el-input> -->
              <div v-html="item.userAnswer"></div>
            </div>
          </div>
          <div v-if="isJudge">
            <div class="shortAnswer anSwerText" v-if="item.answer" style="margin: 15px 0">
              <span v-html="item.answer"></span>
            </div>
            <div class="scoreData">
              <span>得分:</span>
              <el-input-number v-model="item.score" style="width: 100px" />
            </div>
          </div>
        </div>
        <div class="completion" v-if="item.questionType == 'completion'">
          <div class="anSwer" v-if="!isPreview">
            <div class="anSwerText" v-if="item.questionAnswer" style="margin: 15px 0">
              <span style="min-width: 40px">答案:</span><span v-html="item.questionAnswer"></span>
            </div>
            <div :class="isPreview ? 'questionAnalysisCon' : ''" v-if="item.questionAnalysisCon">
              分析:<span v-html="item.questionAnalysisCon"></span>
            </div>
          </div>
          <div v-else>
            <el-input
              style="width: 200px"
              v-model="item.userAnswer"
              disabled
              placeholder="请填写答案"
            ></el-input>
          </div>
        </div>
        <div class="classification" v-if="item.questionType == 'classification'">
          <div class="class-item-box">
            <span
              class="class-item-box-span"
              v-for="citem in item.questionOption"
              :key="citem.index"
            >
              {{ citem.txt }}
            </span>
          </div>
          <div class="anSwer" v-if="!isPreview">
            <div class="anSwerText" v-if="item.questionAnswer" style="margin: 15px 0">
              <span>答案:</span>
              <div class="class-anSwer-box">
                <div
                  class="class-anSwer-box-item"
                  v-for="aitem in item.questionAnswer"
                  :key="aitem.key"
                >
                  <span>{{ aitem.name }}:</span>
                  <div
                    class="class-anSwer-box-item-box"
                    v-for="citem in aitem.option"
                    :key="citem.key"
                  >
                    <span>{{ citem.txt }}</span>
                  </div>
                </div>
              </div>
            </div>
            <div :class="isPreview ? 'questionAnalysisCon' : ''" v-if="item.questionAnalysisCon">
              分析:<span v-html="item.questionAnalysisCon"></span>
            </div>
          </div>
        </div>
        <div class="judge" v-if="item.questionType == 'judge'">
          <el-radio-group v-model="item.customAnswer">
            <el-radio
              v-for="ritem in item.questionOption"
              :key="ritem.index"
              :value="ritem.value"
              :label="ritem.value"
              size="large"
              :disabled="!isPreview"
            >
              {{ ritem.value + '. ' + ritem.txt }}
            </el-radio>
          </el-radio-group>
          <div class="anSwer" v-if="!isPreview">
            <div v-if="item.questionAnswer" style="margin: 15px 0">
              答案:<span v-html="item.questionAnswer"></span>
            </div>
            <div :class="isPreview ? 'questionAnalysisCon' : ''" v-if="item.questionAnalysisCon">
              分析:<span v-html="item.questionAnalysisCon"></span>
            </div>
          </div>
        </div>
        <div class="singleChoice" v-if="item.questionType == 'singleChoice'">
          <el-radio-group v-if="!isInteraction" v-model="item.customAnswer">
            <el-radio
              v-for="ritem in item.questionOption"
              :key="ritem.index"
              :value="ritem.value"
              :label="ritem.value"
              size="large"
              :disabled="!isPreview"
            >
              {{ ritem.value + '. ' + ritem.txt }}
            </el-radio>
          </el-radio-group>
          <el-radio-group v-else v-model="item.userAnswer">
            <el-radio
              v-for="ritem in item.questionOption"
              :key="ritem.index"
              :value="ritem.value"
              :label="ritem.value"
              size="large"
              :disabled="isInteraction"
            >
              {{ ritem.value + '. ' + ritem.txt }}
            </el-radio>
          </el-radio-group>
          <div class="anSwer" v-if="!isPreview">
            <div v-if="item.questionAnswer" style="margin: 15px 0">
              答案:<span v-html="item.questionAnswer"></span>
            </div>
            <div :class="isPreview ? 'questionAnalysisCon' : ''" v-if="item.questionAnalysisCon">
              分析:<span v-html="item.questionAnalysisCon"></span>
            </div>
          </div>
        </div>
        <div class="multipleChoice" v-if="item.questionType == 'multipleChoice'">
          <el-checkbox-group v-if="!isInteraction" v-model="item.customAnswer">
            <el-checkbox
              v-for="mitem in item.questionOption"
              :key="mitem.index"
              :value="mitem.value"
              :label="mitem.value"
              size="large"
              :disabled="!isPreview"
            >
              {{ mitem.value + '. ' + mitem.txt }}
            </el-checkbox>
          </el-checkbox-group>
          <el-checkbox-group v-else v-model="item.userAnswer">
            <el-checkbox
              v-for="mitem in item.questionOption"
              :key="mitem.index"
              :value="mitem.value"
              :label="mitem.value"
              size="large"
              :disabled="isInteraction"
            >
              {{ mitem.value + '. ' + mitem.txt }}
            </el-checkbox>
          </el-checkbox-group>
          <div class="anSwer" v-if="!isPreview">
            <div v-if="item.questionAnswer" style="margin: 15px 0">
              答案:<span v-html="item.questionAnswer"></span>
            </div>
            <div :class="isPreview ? 'questionAnalysisCon' : ''" v-if="item.questionAnalysisCon">
              分析:<span v-html="item.questionAnalysisCon"></span>
            </div>
          </div>
        </div>
      </div>
    </div>
    <div class="jugdeBtn" v-if="isJudge">
      <el-button type="primary" @click="submitScore">提交分数</el-button>
    </div>
  </div>
</template>
<script setup lang="ts">
import { defineProps, onMounted } from 'vue'
const props = defineProps<{
  questionList?: any
  noCheckbox?: boolean
  isDelete?: boolean
  isPreview?: boolean
  isJudge?: boolean
  isInteraction?: boolean
}>()
const emit = defineEmits(['selectQuestion', 'deleteItem', 'judgeUpdate'])
onMounted(() => {})
const checkItems = (e: Event, item: any) => {
  emit('selectQuestion', item)
}
const deleteItem = (item: any) => {
  emit('deleteItem', item)
}
const submitScore = () => {
  const list = [...props.questionList]
  let data: any = []
  list.forEach((item) => {
    data.push(...item.data)
  })
  emit('judgeUpdate', JSON.stringify(data))
}
</script>
<style lang="less" scoped>
.questionDom {
  width: 100%;
  height: 100%;
  overflow: auto;
  padding: 10px;
  box-sizing: border-box;
  font-family: PingFang SC;
  .questionT {
    // display: flex;
    flex-wrap: wrap;
    align-items: baseline;
  }
  .jugdeBtn {
    position: absolute;
    bottom: -40px;
    width: 100%;
    display: flex;
    justify-content: center;
  }
  .list-item {
    width: 100%;
    margin-bottom: 20px;
    .list-item-title {
      padding: 4px 0;
      padding-left: 10px;
      border-left: 4px solid #ff6c00;
      color: #000;
      margin: 15px 0;
    }
  }
  .list-item-box {
    margin-bottom: 20px;
  }
  ::v-deep(.list-item-box-title-box) {
    margin-bottom: 10px;
    .el-checkbox {
      display: flex;
      align-items: flex-start;
    }
    .el-checkbox__label {
      line-height: 22px;
      color: #000;
    }
    .el-checkbox__input {
      margin-top: 3px;
    }
    .el-checkbox__input.is-checked + .el-checkbox__label {
      color: #ff6c00;
    }
  }
  .list-item-box-title {
    font-weight: 400;
    font-size: 13px;
    color: #000000;
    display: flex;
    width: 100%;
    align-items: baseline;
    justify-content: flex-start;
    line-height: 25px;
    .deletIcon {
      color: red;
      margin-left: 10px;
      font-size: 15px;
      cursor: pointer;
    }
  }
  .anSwer {
    font-family: PingFang SC;
    font-weight: 400;
    font-size: 13px;
    color: #000000;
    line-height: 22px;
    padding-left: 20px;
    box-sizing: border-box;
    .anSwerText {
      display: flex;
      justify-content: flex-start;
      .class-anSwer-box {
        margin-left: 10px;
        .class-anSwer-box-item {
          display: flex;
          justify-content: flex-start;
          align-items: center;
          .class-anSwer-box-item-box span {
            margin-left: 10px;
            margin-right: 20px;
          }
        }
      }
    }
    .questionAnalysisCon {
      min-height: 40px;
      padding: 10px;
      background-color: #ff6a00a8;
    }
  }
  .shortAnswer {
    padding: 20px;
    line-height: 22px;
    border: 1px solid #eee;
    border-radius: 5px;
    margin: 10px 0;
  }
  ::v-deep(.judge),
  ::v-deep(.singleChoice) {
    width: 100%;
    margin-bottom: 20px;
    .el-radio-group {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: flex-start;
      padding-left: 20px;
      box-sizing: border-box;
    }
  }
  ::v-deep(.multipleChoice) {
    width: 100%;
    margin-bottom: 20px;
    .el-checkbox-group {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: flex-start;
      padding-left: 20px;
      box-sizing: border-box;
    }
  }
  .class-item-box {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    flex-wrap: wrap;
    .class-item-box-span {
      margin-left: 50px;
    }
  }
  .scoreData {
    display: flex;
    justify-content: flex-start;
    align-items: center;
    span {
      white-space: nowrap;
    }
  }
  ::v-deep(.custom-checkbox) {
    display: inline-flex;
    justify-content: flex-start;
    p {
      white-space: pre-line !important;
    }
  }
}
</style>
src/views/classManage/config.ts
New file
@@ -0,0 +1,106 @@
export const menu: any = [
  {
    label: '班级首页',
    key: '1',
    path: 'classHome',
    icon: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16.002" viewBox="0 0 16 16.002">
      <path class="a"
        d="M62.384,58.423a1.166,1.166,0,0,1,1.469,0l6.818,5.5a1.22,1.22,0,0,1,.193,1.692,1.178,1.178,0,0,1-.644.423l-.063.014v6.164a1.937,1.937,0,0,1-1.748,1.941l-.081.006-.084,0H57.994A1.932,1.932,0,0,1,56.083,72.3V66.052l-.042-.008a1.2,1.2,0,0,1-.919-1.108l0-.068a1.216,1.216,0,0,1,.449-.945Zm.8.853a.1.1,0,0,0-.124,0l-6.811,5.509a.1.1,0,0,0-.038.08.1.1,0,0,0,.1.1h.321a.547.547,0,0,1,.541.552v6.75a.838.838,0,0,0,.828.8h10.28a.839.839,0,0,0,.786-.846V65.521a.547.547,0,0,1,.541-.553h.321a.1.1,0,0,0,.078-.038.106.106,0,0,0-.016-.144ZM65.535,68.9a.545.545,0,0,1-.064.769,3.18,3.18,0,0,1-4.343,0,.546.546,0,0,1,.705-.833,2.092,2.092,0,0,0,2.931,0,.545.545,0,0,1,.769.063Z"
        transform="translate(-55.12 -58.163)" />
    </svg>`
  },
  {
    label: '班级管理',
    key: '2',
    path: 'studentManage',
    icon: `<svg xmlns="http://www.w3.org/2000/svg" width="16.2" height="17.885" viewBox="0 0 16.2 17.885">
  <g transform="translate(-106.5 -63.9)">
    <path class="a"
      d="M414.049,453.35a2.947,2.947,0,1,0-3.4,0,5.05,5.05,0,0,0-3.329,4.291.424.424,0,0,0,.42.464.413.413,0,0,0,.416-.369,4.21,4.21,0,0,1,8.388,0,.413.413,0,0,0,.416.369.423.423,0,0,0,.42-.464A5.05,5.05,0,0,0,414.049,453.35Zm-1.7-.3a2.106,2.106,0,1,1,2.106-2.106A2.109,2.109,0,0,1,412.348,453.052Z"
      transform="translate(-294.781 -376.422)" />
    <path class="a"
      d="M121.759,64H107.443a.842.842,0,0,0-.843.843v16a.842.842,0,0,0,.843.843h2.947a.42.42,0,0,0,0-.841h-2.526a.421.421,0,0,1-.42-.42V65.263a.421.421,0,0,1,.42-.42h13.473a.421.421,0,0,1,.42.42v5.052a.42.42,0,0,0,.841,0V64.843a.839.839,0,0,0-.839-.843Z" />
    <path class="a"
      d="M256.42,235.541h9.262a.42.42,0,1,0,0-.841H256.42a.42.42,0,1,0,0,.841Zm0,2.526h3.369a.42.42,0,1,0,0-.841H256.42a.42.42,0,1,0,0,.841Z"
      transform="translate(-146.451 -167.331)" />
  </g>
</svg>`
  },
  {
    label: '教学计划',
    key: '3',
    path: 'teachingPlan',
    icon: `<svg xmlns="http://www.w3.org/2000/svg" width="16.2" height="16.2" viewBox="0 0 16.2 16.2">
  <path class="a"
    d="M79.6,67.2a.4.4,0,0,0,.4-.4v-2a.8.8,0,0,0-.8-.8H64.8a.8.8,0,0,0-.8.8V79.2a.8.8,0,0,0,.8.8H79.2a.8.8,0,0,0,.8-.8V70a.4.4,0,1,0-.8,0v8.8a.4.4,0,0,1-.4.4H65.2a.4.4,0,0,1-.4-.4V65.2a.4.4,0,0,1,.4-.4H78.8a.4.4,0,0,1,.4.4v1.6A.4.4,0,0,0,79.6,67.2ZM76,72.8H68a.4.4,0,1,0,0,.8h8a.4.4,0,1,0,0-.8Zm0-3.2H73.959a2,2,0,0,0-3.918,0H68a.4.4,0,1,0,0,.8h2.8V70a1.2,1.2,0,1,1,2.4,0v.4H76a.4.4,0,1,0,0-.8Zm0,6H68a.4.4,0,1,0,0,.8h8a.4.4,0,0,0,0-.8Z"
    transform="translate(-63.9 -63.9)" />
</svg>`
  },
  // {
  //   label: '备课',
  //   key: '4',
  //   path: 'prepareLessons',
  //   icon: beike
  // },
  {
    label: '作业管理',
    key: '5',
    path: 'jobManage',
    icon: `<svg xmlns="http://www.w3.org/2000/svg" width="16.326" height="16.2" viewBox="0 0 16.326 16.2">
    <g transform="translate(-63.9 -63.9)">
      <path class="a"
        d="M485.31,298.51a2,2,0,0,0-2.829,0l-4.525,4.527a2,2,0,0,0-.254,2.518l-.311.311a.4.4,0,0,0,.566.566l.311-.311a2,2,0,0,0,2.518-.254l4.525-4.525A2.007,2.007,0,0,0,485.31,298.51Zm-5.091,6.789a1.2,1.2,0,0,1-1.7-1.7l2.263-2.263,1.7,1.7Zm4.525-4.525-1.7,1.7-1.7-1.7,1.7-1.7a1.2,1.2,0,1,1,1.7,1.7Z"
        transform="translate(-405.895 -229.748)" />
      <path class="a"
        d="M76,78.8a.4.4,0,0,1-.4.4H65.2a.4.4,0,0,1-.4-.4V65.2a.4.4,0,0,1,.4-.4H75.6a.4.4,0,0,1,.4.4V68l.8-.8V64.8A.8.8,0,0,0,76,64H64.8a.8.8,0,0,0-.8.8V79.2a.8.8,0,0,0,.8.8H76a.8.8,0,0,0,.8-.8V76l-.8.8Z" />
      <path class="a"
        d="M204.8,266a.4.4,0,0,0-.4-.4h-5.6a.4.4,0,1,0,0,.8h5.6A.4.4,0,0,0,204.8,266Zm-6,2a.4.4,0,1,0,0,.8H202a.4.4,0,0,0,0-.8Z"
        transform="translate(-132 -198)" />
    </g>
  </svg>`
  },
  // {
  //   label: '测试管理',
  //   key: '6',
  //   path: 'testManage',
  //   icon: ceshi
  // },
  {
    label: '作业分析',
    key: '7',
    path: 'jobAnalysis',
    icon: `<svg xmlns="http://www.w3.org/2000/svg" width="16.2" height="16.2" viewBox="0 0 16.2 16.2">
    <path class="a"
      d="M69.2,67.2a.4.4,0,0,0,.4-.4V64.4a.4.4,0,1,0-.8,0v2.4A.4.4,0,0,0,69.2,67.2Zm2.8,0a.4.4,0,0,0,.4-.4V64.4a.4.4,0,1,0-.8,0v2.4A.4.4,0,0,0,72,67.2Zm2.8,0a.4.4,0,0,0,.4-.4V64.4a.4.4,0,0,0-.8,0v2.4A.4.4,0,0,0,74.8,67.2Zm4.4-2H76.8a.4.4,0,1,0,0,.8h2a.4.4,0,0,1,.4.4v2.4H64.8V66.4a.4.4,0,0,1,.4-.4h2a.4.4,0,0,0,0-.8H64.8a.8.8,0,0,0-.8.8V79.2a.8.8,0,0,0,.8.8H79.2a.8.8,0,0,0,.8-.8V66A.8.8,0,0,0,79.2,65.2Zm0,13.6a.4.4,0,0,1-.4.4H65.2a.4.4,0,0,1-.4-.4V69.6H79.2Zm-11.466-2a.4.4,0,0,0,.282-.118l2.941-2.941,2.12,1.766a.4.4,0,0,0,.543-.029l2.934-3.034a.4.4,0,1,0-.575-.557L73.3,74.655l-2.114-1.762a.4.4,0,0,0-.539.025l-3.2,3.2a.4.4,0,0,0,0,.566A.4.4,0,0,0,67.734,76.8Z"
      transform="translate(-63.9 -63.9)" />
  </svg>`
  },
  {
    label: '教学互动',
    key: '9',
    path: 'teachInteraction',
    icon: `<svg xmlns="http://www.w3.org/2000/svg" width="16.2" height="16.2" viewBox="0 0 1024 1024">
  <path
    d="M447.766594 480.256747H252.078775C126.599562 480.256747 24.274252 377.931437 24.274252 252.452225S125.852662 24.647702 252.078775 24.647702 479.883297 126.973012 479.883297 252.452225V448.140044c0 17.925602-14.191101 32.116703-32.116703 32.116703zM252.078775 88.134209c-90.374909 0-164.318016 73.943107-164.318016 164.318016s73.943107 164.318016 164.318016 164.318016h164.318016v-164.318016c0-90.374909-73.943107-164.318016-164.318016-164.318016zM251.331875 999.352298C125.852662 999.352298 23.527352 897.026988 23.527352 771.547775s102.32531-227.804522 227.804523-227.804522H447.766594c17.178702 0 32.116703 14.191101 32.116703 32.116703v196.434719c-0.7469 124.732312-103.07221 227.057622-228.551422 227.057623z m0-392.122539c-90.374909 0-164.318016 73.943107-164.318017 164.318016s73.943107 164.318016 164.318017 164.318016 164.318016-73.943107 164.318016-164.318016v-164.318016h-164.318016zM773.415026 999.352298c-125.479212 0-227.804522-102.32531-227.804523-227.804523V575.113056c0-17.178702 14.191101-32.116703 32.116703-32.116703h196.43472c125.479212 0 227.804522 102.32531 227.804522 227.804522 0 36.598104-8.962801 72.449307-25.394603 104.56601-8.215901 15.684902-26.888403 21.660102-42.573304 13.444202-15.684902-8.215901-21.660102-26.888403-13.444201-42.573304 11.950401-23.153902 18.672502-49.295405 18.672502-75.436908 0-90.374909-73.943107-164.318016-164.318016-164.318016h-164.318016v164.318016c0 90.374909 73.943107 164.318016 164.318016 164.318016 17.178702 0 34.357403-2.9876 50.042305-8.2159 16.431802-5.228301 34.357403 3.7345 39.585703 20.166301 5.228301 16.431802-3.7345 34.357403-20.166302 39.585704-23.900802 8.962801-47.054705 12.697301-70.955506 12.697302zM773.415026 480.256747H577.727206c-17.178702 0-32.116703-14.191101-32.116703-32.116703V252.452225c0-125.479212 102.32531-227.804522 227.804523-227.804523s227.804522 102.32531 227.804522 227.804523-102.32531 227.804522-227.804522 227.804522z m-164.318017-63.486506h164.318017c90.374909 0 164.318016-73.943107 164.318016-164.318016s-73.943107-164.318016-164.318016-164.318016-164.318016 73.943107-164.318017 164.318016v164.318016z" />
</svg>`
  },
  {
    label: '话题',
    key: '8',
    path: 'talkingPoint',
    icon: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="15.97" viewBox="0 0 16 15.97">
    <g transform="translate(-58.026 -58.026)">
      <path class="a"
        d="M66.026,74a8.075,8.075,0,0,1-3.049-.6,7.7,7.7,0,0,1-1.3-.694,15.665,15.665,0,0,0-2.294.513.505.505,0,0,1-.453-.121.483.483,0,0,1-.121-.453,11.77,11.77,0,0,0,.513-2.294,6.444,6.444,0,0,1-.694-1.268,8,8,0,1,1,15.4-3.049,7.988,7.988,0,0,1-8,7.97Zm-4.317-2.2a.767.767,0,0,1,.362.091,6.09,6.09,0,0,0,1.238.664A7.093,7.093,0,1,0,66.026,58.9,7.125,7.125,0,0,0,58.932,66a7.109,7.109,0,0,0,1.208,3.955c.121.181.242.332-.242,2.2a8.636,8.636,0,0,1,1.811-.362Z" />
      <path class="a" d="M332.766,395.146h-6.34a.453.453,0,0,1,0-.906h6.34a.453.453,0,0,1,0,.906Z"
        transform="translate(-263.207 -330.267)" />
      <path class="a"
        d="M375.27,315.381h-.091a.426.426,0,0,1-.362-.513l1.087-6.249a.444.444,0,0,1,.875.151l-1.057,6.219A.433.433,0,0,1,375.27,315.381Zm2.657,0h-.091a.426.426,0,0,1-.362-.513l1.087-6.249a.444.444,0,0,1,.875.151l-1.087,6.249A.423.423,0,0,1,377.927,315.381Z"
        transform="translate(-311.176 -245.793)" />
      <path class="a" d="M290.1,569.226h-6.34a.453.453,0,1,1,0-.906h6.34a.453.453,0,0,1,0,.906Z"
        transform="translate(-221.296 -501.268)" />
    </g>
  </svg>`
  }
]
src/views/classManage/index.vue
New file
@@ -0,0 +1,283 @@
<template>
  <div class="layoutBox">
    <Header class="header"></Header>
    <div class="layoutContentBox clear">
      <div class="classContentBox clear">
        <div class="leftList fl">
          <!-- <div class="main title" @click="goBack()"> -->
          <div class="main title">
            <!-- <el-icon><ArrowLeft /></el-icon> -->
            <span>我的班级</span>
          </div>
          <div class="classInfo-box">
            <div class="iconBox"><img :src="classIcon" /></div>
            <div class="infoBox">
              <div class="main">{{ classInfo?.name }}</div>
              <div class="job" v-if="userData && userData.role == 'Teacher'">
                <span class="mainbg">助教</span>
              </div>
              <div class="job" v-else><span class="mainbg">学生</span></div>
            </div>
          </div>
          <div class="line"></div>
          <ul class="menu">
            <li
              v-for="(item, index) in listMenu"
              :key="index"
              @click="goRouter(item, index)"
              :class="activeIndex == index ? 'activeItem mainbg hover' : 'menuItem hover'"
            >
              <span
                :style="{
                  fill: activeIndex == index ? '#fff' : '#000'
                }"
                v-html="item.icon"
              >
              </span>
              <span>{{ item.label }}</span>
            </li>
          </ul>
        </div>
        <div class="rightContent">
          <div>
            <!-- 让主体做子路由的显示 -->
            <router-view />
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script setup lang="ts">
import { ref, watch, provide, onMounted, inject } from 'vue'
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router'
import Header from './components/headerPage.vue'
import { menu } from './config'
import { getPublicImage } from '@/assets/js/middleGround/tool.js'
import defaultImg from '@/assets/images/default-book-img.png'
const router: any = useRouter()
const route: any = useRoute()
const MG: any = inject('MG')
const config: any = inject('config')
const routerVal = router.currentRoute.value
const path = ref(routerVal.path)
const label = ref('')
const classInfo: any = ref()
const classIcon: any = ref()
const listMenu = ref([])
const activeIndex = ref(0)
const userData = ref()
watch(router.currentRoute, (val) => {
  path.value = val.path
  if (val.query.classInfo) {
    const data: any = val.query.classInfo
    const routerInfo: any = JSON.parse(data)
    activeIndex.value = routerInfo.index
  }
})
watch(classInfo, (val) => {
  if (val) {
    activeIndex.value = val.index
  }
  if (!val.index) {
    activeIndex.value = 0
  }
})
onBeforeRouteUpdate(async (to, from) => {
  path.value = to.path
})
onMounted(() => {
  classInfo.value = JSON.parse(route.query.classInfo)
  classIcon.value = classInfo.value.icon ? getPublicImage(classInfo.value.icon, 200) : defaultImg
  menu.forEach((item) => {
    if ('/' + item.path === path.value) {
      label.value = item.label
    }
  })
  const userCache: any = localStorage.getItem('jesk-userInfo')
  const userInfo = JSON.parse(userCache)
  userData.value = userInfo
  if (!userInfo) {
    router.push({
      path: '/'
    })
    return false
  }
  if (userInfo.role != 'Teacher') {
    const data: any = menu.filter(
      (item: any) => item.path != 'studentManage' && item.path != 'jobAnalysis'
    )
    listMenu.value = data
  } else {
    const data: any = menu
    // if (!classInfo.bookRefCode) {
    //   // 融媒体图书,无数字阅读,无法跳转
    //   const list: any = menu.filter((item: any) => item.path != 'teachingPlan')
    //   listMenu.value = list;
    //   return false
    // }
    listMenu.value = data
  }
})
const goRouter = (item: any, index: number) => {
  activeIndex.value = index
  if (!localStorage.getItem('jsek-token') || localStorage.getItem('jsek-token') == null) {
    router.push({
      path: '/home',
      query: {
        showLogin: '1'
      }
    })
  } else {
    label.value = item.label
    classInfo.value.index = index
    if (item.key == 5) {
      router.push({
        path: userData.value.role != 'Teacher' ? '/studentJob' : '/jobManage',
        query: {
          classInfo: JSON.stringify(classInfo.value)
        }
      })
    } else {
      router.push({
        path: item.path,
        query: {
          classInfo: JSON.stringify(classInfo.value)
        }
      })
    }
  }
}
const goBack = () => {
  router.go(-1)
}
</script>
<style lang="less" scoped>
.layoutBox {
  width: 100%;
  height: 100vh;
  display: flex;
  flex-direction: column;
  background-color: #fff;
  .layoutContentBox {
    flex: 1;
    height: auto;
  }
  .classContentBox {
    padding: 10px 40px;
    display: flex;
    height: 100%;
    .leftList {
      padding: 20px;
      width: 280px;
      min-width: 230px;
      box-shadow: 0px 0px 20px 1px #ccc;
      border-radius: 3px;
      background-color: #fff;
      .title {
        font-size: 16px;
        display: flex;
        align-items: center;
        cursor: pointer;
        span {
          margin-left: 10px;
        }
      }
      .classInfo-box {
        padding: 20px 10px;
        margin-bottom: 10px;
        display: flex;
        .iconBox {
          width: 90px;
          height: 120px;
          img {
            width: 100%;
            height: 100%;
            object-fit: contain;
          }
        }
        .infoBox {
          flex: 1;
          padding-left: 10px;
          .main {
            font-size: 16px;
            line-height: 20px;
          }
          .job {
            // padding:10px;
            margin-top: 20px;
            span {
              padding: 5px 15px;
              border-radius: 20px;
            }
          }
        }
      }
      .line {
        width: 100%;
        height: 1px;
        background: linear-gradient(63deg, #ffffff 0%, #e0f2ff 51%, #ffffff 100%);
      }
      .menu {
        margin-top: 20px;
        li {
          height: 36px;
          padding: 0 40px;
          margin: 5px 0;
          font-size: 16px;
          display: flex;
          align-items: center;
          position: relative;
          img {
            width: 18px;
            height: 18px;
          }
          span {
            margin-left: 10px;
            font-size: 15px;
          }
        }
        .activeItem {
          background-size: 100% 100%;
        }
      }
    }
    .rightContent {
      flex: 1;
      overflow: auto;
      min-width: 800px;
      margin-left: 15px;
      box-shadow: 0px 0px 20px 1px #ccc;
      border-radius: 3px;
      background-color: #fff;
    }
  }
  .header {
    flex-shrink: 0;
    width: 100%;
  }
}
@media screen and (min-width: 1200px) {
  .layoutContentBox {
    flex: 1;
    overflow: auto;
    // display: flex;
    // flex-direction: column;
  }
}
</style>
src/views/classManage/infoList.vue
New file
@@ -0,0 +1,254 @@
<template>
  <div class="classManagePage-box">
    <div class="classManagePage-nav">
      <el-breadcrumb :separator-icon="ArrowRight">
        <el-breadcrumb-item>我的班级</el-breadcrumb-item>
        <el-breadcrumb-item>{{ classInfo?.name }}</el-breadcrumb-item>
        <el-breadcrumb-item>班级通知审核</el-breadcrumb-item>
      </el-breadcrumb>
    </div>
    <div class="classManagePage-content">
      <div class="backBtn">
        <el-button @click="goBack()" type="primary" link>
          <el-icon style="margin-right: 5px"><ArrowLeftBold /></el-icon> 返回
        </el-button>
      </div>
      <div class="contentBox">
        <div class="titleOptions">
          <span>班级通知审核列表</span>
        </div>
        <div class="content-tab-box">
          <div class="content-list-box">
            <el-table
              :header-cell-style="{ background: '#eee' }"
              :data="dataList"
              max-height="600px"
              style="width: 100%"
              v-loading="pages.loading"
            >
              <el-table-column prop="id" label="序号" width="70" />
              <el-table-column prop="name" label="通知" width="500" />
              <el-table-column prop="state" label="状态" #default="scope">
                <span style="color: #ff6d00" v-if="scope.row.state == 'WaitAudit'">待审核</span>
                <span style="color: #67c23a" v-if="scope.row.state == 'Normal'">已通过</span>
                <span style="color: red" v-if="scope.row.state == 'Reject'">已拒绝</span>
              </el-table-column>
              <el-table-column prop="createDate" label="创建时间" />
              <el-table-column label="操作" #default="scope">
                <el-button
                  link
                  v-if="scope.row.state != 'Normal'"
                  type="success"
                  size="small"
                  @click="successMessage(scope.row)"
                >
                  发布
                </el-button>
                <el-button link type="danger" size="small" @click="removeMessage(scope.row)">
                  删除
                </el-button>
              </el-table-column>
            </el-table>
            <div class="pageBox">
              <el-pagination
                style="float: right"
                v-model:current-page="pages.currentPage"
                :page-size="pages.pageSize"
                :size="'small'"
                :disabled="pages.count <= 1"
                layout="total, prev, pager, next"
                :total="pages.count"
                @size-change="handleSizeChange"
                @current-change="handleCurrentChange"
              />
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router'
import { inject, onMounted, reactive, ref } from 'vue'
import { Search, UserFilled, ArrowRight } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import moment from 'moment'
const route: any = useRoute()
const router = useRouter()
const classInfo = JSON.parse(route.query.classInfo)
const MG: any = inject('MG')
const config: any = inject('config')
const tool: any = inject('toolClass')
let pages = reactive({
  currentPage: 1,
  page: 1,
  pageSize: 15,
  count: 0,
  loading: true
})
const dataList: any = ref([])
// 获取班级通知
const getNotice = () => {
  const data = {
    start: (pages.page - 1) * pages.pageSize,
    size: pages.pageSize,
    appRefCode: config.appRefCode,
    topicIdOrRefCode: String(sessionStorage.messageId),
    sort: {
      type: 'Desc',
      field: 'CreateDate',
      subSorts: []
    }
  }
  MG.ugc
    .getTopicMessageList(data)
    .then((res: any) => {
      pages.loading = false
      pages.count = res.totalSize
      const list = res.datas
      dataList.value = list.map((item: any) => {
        return {
          ...item,
          createDate: moment(item.createDate).format('YYYY-MM-DD')
        }
      })
    })
    .catch((err: any) => {
      pages.loading = false
      console.log(err)
    })
}
// 通过
const successMessage = (item: any) => {
  const data = {
    id: item.id,
    name: item.name,
    description: item.description,
    icon: item.icon,
    type: item.type,
    state: 'Normal',
    content: JSON.stringify(item.publicText),
    newDataRequests: [],
    updateDataRequests: []
  }
  MG.ugc
    .updateTopicMessage(data)
    .then((res: any) => {
      if (res) {
        ElMessage({
          type: 'success',
          message: '已发布'
        })
        getNotice()
      }
    })
    .catch((err: any) => {
      console.log(err, '审核话题')
    })
}
// 删除
const removeMessage = (item: any) => {
  const data = {
    messageIds: [item.id]
  }
  MG.ugc
    .delTopicMessage(data)
    .then((res: any) => {
      if (res) {
        ElMessage.success('已删除')
        getNotice()
      }
    })
    .catch((err: any) => {
      ElMessage.error('删除失败,请稍后再试')
      console.log(err)
    })
}
//  分页
const handleSizeChange = (val: number) => {
  pages.pageSize = val
  getNotice()
}
const handleCurrentChange = (val: number) => {
  pages.page = val
  pages.currentPage = val
  getNotice()
}
const goBack = () => {
  router.go(-1)
}
onMounted(() => {
  getNotice()
})
</script>
<style lang="less" scoped>
.classManagePage-box {
  background: #fff;
  .classManagePage-nav {
    width: 100%;
    padding: 0 20px;
    height: 40px;
    border-bottom: 1px solid #e6e8ed;
    position: sticky;
    top: 0;
    z-index: 999;
    display: flex;
    align-items: center;
    background: #fff;
  }
  .classManagePage-content {
    width: 100%;
    position: relative;
    .backBtn {
      width: 100%;
      height: 45px;
      margin-bottom: 10px;
      padding: 0 20px;
      display: flex;
      align-items: center;
      position: sticky;
      top: 40px;
      z-index: 99;
      background: #fff;
      box-shadow: 0px 0px 20px 1px #eeeeee83;
    }
    .contentBox {
      width: 100%;
      padding: 0 20px;
      box-sizing: border-box;
      .titleOptions {
        width: 160px;
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 20px;
        span {
          height: 30px;
          font-family: PingFang SC;
          font-weight: bold;
          font-size: 16px;
          color: #333;
          line-height: 30px;
          text-align: left;
          border-left: 6px solid #ff6c00;
          padding-left: 10px;
        }
      }
    }
  }
}
</style>
src/views/classManage/interactionDetail.vue
New file
@@ -0,0 +1,373 @@
<template>
  <div class="classManagePage-box">
    <div class="classManagePage-nav">
      <el-breadcrumb :separator-icon="ArrowRight">
        <el-breadcrumb-item>我的班级</el-breadcrumb-item>
        <el-breadcrumb-item>{{ classInfo?.name }}</el-breadcrumb-item>
        <el-breadcrumb-item>互动学生列表</el-breadcrumb-item>
      </el-breadcrumb>
    </div>
    <div class="classManagePage-content">
      <div class="backBtn">
        <el-button @click="goBack()" type="primary" link>
          <el-icon style="margin-right: 5px"><ArrowLeftBold /></el-icon> 返回
        </el-button>
      </div>
      <div class="contentBox">
        <div class="titleOptions">
          <span>互动学生列表</span>
        </div>
        <div class="content-tab-box">
          <div class="content-list-box">
            <el-table
              :header-cell-style="{ background: '#eee' }"
              :data="dataList"
              max-height="600px"
              style="width: 100%"
              v-loading="pages.loading"
            >
              <el-table-column prop="index" label="序号" width="70" />
              <el-table-column prop="userName" label="姓名" width="500" />
              <el-table-column label="状态">
                <el-button link type="success">已完成</el-button>
              </el-table-column>
              <el-table-column prop="questionTime" label="答题时间" />
              <el-table-column label="操作" #default="scoped">
                <el-button link type="primary" @click="getQuestions(scoped.row)">
                  答题详情
                </el-button>
              </el-table-column>
            </el-table>
            <div class="pageBox">
              <el-pagination
                style="float: right"
                v-model:current-page="pages.currentPage"
                :page-size="pages.pageSize"
                :size="'small'"
                :disabled="pages.count <= 1"
                layout="total, prev, pager, next"
                :total="pages.count"
                @size-change="handleSizeChange"
                @current-change="handleCurrentChange"
              />
            </div>
          </div>
        </div>
      </div>
      <!-- 浏览答题 -->
      <el-dialog
        class="customDialog"
        title="浏览答题"
        v-model="visible"
        destroy-on-close
        width="1000"
        align-center
      >
        <div class="pubContent" v-if="dialogList.length > 0 && !dialogLLoading">
          <div v-for="(item, index) in dialogList" :key="index">
            <span class="userName">答题人:{{ item.userName ?? '-' }}</span>
            <question-dom
              v-if="item.questionTypeList.length > 0"
              :questionList="item.questionTypeList"
              :noCheckbox="false"
              :is-preview="true"
              :is-interaction="true"
            />
          </div>
        </div>
        <div class="pubContent" v-if="dialogLLoading" v-loading="dialogLLoading"></div>
        <div class="pubContent noData" v-if="dialogList.length == 0 && !dialogLLoading">
          <el-empty></el-empty>
        </div>
        <template #footer>
          <div class="selectedFooter" style="padding: 0">
            <el-button type="primary" @click="visible = false"> 关闭 </el-button>
          </div>
        </template>
      </el-dialog>
    </div>
  </div>
</template>
<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router'
import { inject, onMounted, reactive, ref } from 'vue'
import { Search, UserFilled, ArrowRight } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import moment from 'moment'
import questionDom from './components/questionDom.vue'
const route: any = useRoute()
const router = useRouter()
const classInfo = JSON.parse(route.query.classInfo)
const MG: any = inject('MG')
const config: any = inject('config')
const tool: any = inject('toolClass')
const dataList = ref([])
let pages = reactive({
  currentPage: 1,
  page: 1,
  pageSize: 15,
  count: 0,
  loading: false
})
const visible = ref(false)
const dialogList: any = ref([])
const dialogLLoading = ref(false)
const defaultCmsPath = ref('')
const handleDelete = (item: any) => {
  const data = {
    messageIds: [item.id]
  }
  MG.ugc.delTopicMessage(data).then((res: any) => {
    ElMessage.success('删除成功')
    getMessage()
  })
}
// 获取当前话题
const getMessage = () => {
  pages.loading = true
  const data = {
    start: (pages.page - 1) * pages.pageSize,
    size: pages.pageSize,
    appRefCode: config.appRefCode,
    topicIdOrRefCode: String(classInfo.teachInteractionId),
    sort: {
      type: 'Desc',
      field: 'CreateDate'
    },
    searchList: [
      {
        keywords: classInfo.questionName,
        field: 'Name',
        compareType: 'Contains'
      }
    ]
  }
  MG.ugc.getTopicMessageList(data).then((res: any) => {
    pages.loading = false
    pages.count = res.totalSize
    dataList.value = res.datas.map((item: any, i: number) => {
      item.question = []
      item.bookId = null
      item.path = ''
      item.index = i + 1
      try {
        const obj = JSON.parse(item.content)
        if (obj.bookId) {
          item.question = obj.content
          item.bookId = obj.bookId
          item.path = obj.path
          item.userName = obj.userName ?? '-'
        }
      } catch (error) {
        console.log(item)
      }
      return {
        ...item,
        questionTime: moment(item.updateDate).format('YYYY-MM-DD HH:mm:ss')
      }
    })
  })
}
//  分页
const handleSizeChange = (val: number) => {
  pages.pageSize = val
  getMessage()
}
const handleCurrentChange = (val: number) => {
  pages.page = val
  pages.currentPage = val
  getMessage()
}
onMounted(() => {
  defaultCmsPath.value = classInfo.bookRefCode ? 'jsek_digitalTextbooks' : 'defaultGoodsStore3'
  getMessage()
})
// 获取题目列表
const getQuestions = (item: any) => {
  visible.value = true
  dialogLLoading.value = true
  MG.store
    .getProductDetail({
      path: defaultCmsPath.value,
      queryType: '*',
      productId: classInfo.bookId,
      storeInfo: defaultCmsPath.value,
      cmsPath: item.path,
      itemFields: {
        Embedded_QuestionBank_AnalysisCon: [],
        Embedded_QuestionBank_Answer: [],
        Embedded_QuestionBank_Difficulty: [],
        Embedded_QuestionBank_KnowledgePoint: [],
        Embedded_QuestionBank_Option: [],
        Embedded_QuestionBank_OptionStyle: [],
        Embedded_QuestionBank_QuestionType: [],
        Embedded_QuestionBank_Score: [],
        Embedded_QuestionBank_Stem: [],
        Embedded_QuestionBank_StemStyle: []
      }
    })
    .then((res: any) => {
      try {
        let list = []
        list = res.datas.cmsDatas[0]?.datas.map((item: any) => {
          try {
            if (item.Embedded_QuestionBank_Stem) {
              item.questionStem = JSON.parse(item.Embedded_QuestionBank_Stem)
            }
            if (
              item.Embedded_QuestionBank_Option &&
              item.Embedded_QuestionBank_Option.indexOf('[') > -1
            ) {
              item.questionOption = JSON.parse(item.Embedded_QuestionBank_Option)
            }
            if (
              item.Embedded_QuestionBank_Answer &&
              item.Embedded_QuestionBank_Answer.indexOf('[') > -1
            ) {
              item.Embedded_QuestionBank_Answer = JSON.parse(item.Embedded_QuestionBank_Answer)
            }
            return {
              ...item,
              questionType: item.Embedded_QuestionBank_QuestionType,
              questionAnalysisCon: item.Embedded_QuestionBank_AnalysisCon,
              questionAnswer: item.Embedded_QuestionBank_Answer,
              customAnswer: null
            }
          } catch (error) {
            console.log(item)
          }
        })
        dialogList.value = chageData([item], list)
        dialogLLoading.value = false
      } catch (error) {
        dialogList.value = []
        dialogLLoading.value = false
      }
    })
}
// 处理数据结构
const chageData = (arr: any, zrr: any) => {
  let newData = []
  // 题库题目类型
  const questionTypeList = [
    { name: '单选题', value: 'singleChoice', data: [] },
    { name: '多选题', value: 'multipleChoice', data: [] },
    { name: '判断题', value: 'judge', data: [] },
    { name: '简答题', value: 'shortAnswer', data: [] },
    { name: '论述题', value: 'discuss', data: [] },
    { name: '填空题', value: 'completion', data: [] },
    { name: '连线题', value: 'matching', data: [] },
    { name: '分类题', value: 'classification', data: [] }
  ]
  for (let i = 0; i < arr.length; i++) {
    const item = arr[i]
    item.questionTypeList = JSON.parse(JSON.stringify(questionTypeList))
    for (let j = 0; j < zrr.length; j++) {
      const ele = zrr[j]
      const qusObj = item.question.find((citem: any) => citem.cmsItemId == ele.id)
      if (qusObj?.cmsItemId) {
        ele.userAnswer = qusObj.answer
        const index = findIndexByValue(questionTypeList, ele.questionType)
        if (index > -1) {
          item.questionTypeList[index].data.push(ele)
        }
      }
    }
    item.questionTypeList = item.questionTypeList.filter((item: any) => item.data.length > 0)
    newData.push(item)
  }
  return newData.filter((item) => item.questionTypeList.length > 0)
}
const findIndexByValue = (res: any, type: string) => {
  for (let i = 0; i < res.length; i++) {
    if (res[i].value == type) {
      return i
    }
  }
  return -1 // 如果未找到,则返回 -1
}
// 作业搜索
const searchData = () => {}
const goBack = () => {
  router.go(-1)
}
</script>
<style lang="less" scoped>
.classManagePage-box {
  background: #fff;
  .classManagePage-nav {
    width: 100%;
    padding: 0 20px;
    height: 40px;
    border-bottom: 1px solid #e6e8ed;
    position: sticky;
    top: 0;
    z-index: 999;
    display: flex;
    align-items: center;
    background: #fff;
  }
  .classManagePage-content {
    width: 100%;
    position: relative;
    .backBtn {
      width: 100%;
      height: 45px;
      margin-bottom: 10px;
      padding: 0 20px;
      display: flex;
      align-items: center;
      position: sticky;
      top: 40px;
      z-index: 99;
      background: #fff;
      box-shadow: 0px 0px 20px 1px #eeeeee83;
    }
    .contentBox {
      width: 100%;
      padding: 0 20px;
      box-sizing: border-box;
      .titleOptions {
        width: 160px;
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 20px;
        span {
          height: 30px;
          font-family: PingFang SC;
          font-weight: bold;
          font-size: 16px;
          color: #333;
          line-height: 30px;
          text-align: left;
          border-left: 6px solid #ff6c00;
          padding-left: 10px;
        }
      }
    }
    .pubContent {
      padding: 10px;
      box-sizing: border-box;
      min-height: 750px;
      .userName {
        color: #ff6d00;
      }
    }
  }
}
</style>
src/views/classManage/jobAnalysis.vue
New file
@@ -0,0 +1,331 @@
<template>
  <div class="classManagePage-box">
    <el-breadcrumb :separator-icon="ArrowRight">
      <el-breadcrumb-item>我的班级</el-breadcrumb-item>
      <el-breadcrumb-item>{{ classInfo?.name }}</el-breadcrumb-item>
      <el-breadcrumb-item>作业分析</el-breadcrumb-item>
    </el-breadcrumb>
    <div class="classManagePage-content" v-loading="pageLoading">
      <div class="staticBox">
        <div class="pub_staticItem">
          <img src="@/assets/images/class/zuoyecishu@2x.png" alt="" />
          <div class="staticText">
            <el-statistic title="作业次数" :value="outputValue" />
          </div>
        </div>
        <!-- <div class="pub_staticItem">
          <img src="@/assets/images/class/jinxing@2x.png" alt="" />
          <div class="staticText">
            <el-statistic title="进行中" :value="8" />
          </div>
        </div>
        <div class="pub_staticItem">
          <img src="@/assets/images/class/jieshu@2x.png" alt="" />
          <div class="staticText">
            <el-statistic title="已结束" :value="8" />
          </div>
        </div> -->
      </div>
      <div class="chartsBox">
        <div id="chartsContent" v-show="charstData.length > 0"></div>
      </div>
      <div v-if="charstData.length == 0 && !pageLoading">
        <el-empty />
      </div>
      <div class="listContent" v-if="tabHeader.length > 0">
        <div class="tableTitle">班级成绩详情</div>
        <div class="tableContent">
          <el-table
            v-loading="pages.loading"
            :data="tableData"
            border
            style="width: 100%"
            :cell-style="{ textAlign: 'center' }"
            :header-cell-style="{ textAlign: 'center' }"
            size="small"
          >
            <el-table-column prop="index" label="序号" width="70" />
            <el-table-column label="姓名" #default="scope">
              <span>{{ scope.row.appUser?.userInfo?.name }}</span>
            </el-table-column>
            <el-table-column label="学号" #default="scope">
              <span>{{ scope.row.appUser?.appUserId }}</span>
            </el-table-column>
            <el-table-column label="班级作业得分" v-if="tabHeader.length > 0">
              <el-table-column v-for="(item, index) in tabHeader" :key="index" :label="item">
                <template #default="scope">
                  <span v-if="scope.row.results[index]?.result >= 0">
                    {{ scope.row.results[index]?.result }}
                  </span>
                  <span v-else> -- </span>
                </template>
              </el-table-column>
            </el-table-column>
          </el-table>
          <el-pagination
            style="float: right"
            v-model:current-page="pages.page"
            :page-size="pages.pageSize"
            layout="total, prev, pager, next"
            :total="pages.count"
            @size-change="handleSizeChange"
            @current-change="handleCurrentChange"
          />
        </div>
      </div>
    </div>
  </div>
</template>
<script setup lang="ts">
import * as echarts from 'echarts'
import { ArrowRight } from '@element-plus/icons-vue'
import { useRoute } from 'vue-router'
import { useTransition } from '@vueuse/core'
import { inject, onMounted, onUnmounted, reactive, ref } from 'vue'
const route: any = useRoute()
const MG: any = inject('MG')
const classInfo: any = JSON.parse(route.query.classInfo)
var chartDom = null
var myChart: any = {}
var option = null
const nameList: any = ref([])
const charstData: any = ref([])
const count = ref(0)
const pageLoading = ref(true)
const outputValue = useTransition(count, {
  duration: 1000
})
const tabHeader: any = ref([])
const tableData: any = ref([])
let pages = reactive({
  page: 1,
  pageSize: 5,
  count: 0,
  loading: false
})
// 图表初始化
const initCharts = () => {
  option = {
    legend: {
      bottom: 0
    },
    tooltip: {
      trigger: 'axis',
      showContent: true
    },
    toolbox: {
      feature: {
        saveAsImage: {},
        magicType: {
          type: ['bar']
        },
        restore: {}
      }
    },
    title: {
      text: '班级成绩概览',
      textStyle: {
        fontFamily: 'PingFang SC',
        color: '#000000',
        fontWeight: 'bold',
        fontSize: '16px'
      }
    },
    dataset: {
      source: charstData.value
    },
    xAxis: { type: 'category' },
    yAxis: { gridIndex: 0 },
    grid: {
      left: '3%',
      right: '4%',
      containLabel: true
    },
    series: [
      {
        type: 'line',
        smooth: true,
        seriesLayoutBy: 'row',
        emphasis: { focus: 'series' }
      },
      {
        type: 'line',
        smooth: true,
        seriesLayoutBy: 'row',
        emphasis: { focus: 'series' }
      },
      {
        type: 'line',
        smooth: true,
        seriesLayoutBy: 'row',
        emphasis: { focus: 'series' }
      }
    ]
  }
  option && myChart.setOption(option)
}
onMounted(() => {
  getData()
  getUserTaskList()
})
// 获取统计数据
const getData = () => {
  const data = {
    classId: classInfo?.id
  }
  MG.edu
    .getTaskStatistics(data)
    .then((res: any) => {
      count.value = res.length
      if (res.length > 0) {
        let dataX = res.map((item: any) => item.task.name)
        dataX.unshift('homeWork')
        nameList.value = dataX
        let avg = ['班级平均分']
        let max = ['最高分']
        let min = ['最低分']
        res.forEach((item: any) => {
          avg.push(item.avgScore)
          max.push(item.maxScore)
          min.push(item.minScore)
        })
        charstData.value = [dataX, avg, max, min]
      } else {
        charstData.value = []
        nameList.value = []
      }
      setTimeout(() => {
        pageLoading.value = false
        chartDom = document.getElementById('chartsContent')
        myChart = echarts.init(chartDom)
        initCharts()
      }, 1000)
    })
    .catch((err: any) => {
      pageLoading.value = false
      console.log(err)
    })
}
// 获取班级作业成绩详情
const getUserTaskList = () => {
  pages.loading = true
  const data = {
    start: (pages.page - 1) * pages.pageSize,
    size: pages.pageSize,
    classId: classInfo?.id
  }
  MG.edu
    .getUserTaskList(data)
    .then((res: any) => {
      let headerList: any = []
      pages.count = res.totalSize
      if (res.datas.length > 0) {
        res.datas.forEach((item: any, index: number) => {
          item.index = index + 1
          const hitem = item.results.map((citem: any) => citem.name)
          headerList.push(...hitem)
        })
        tableData.value = res.datas
        tabHeader.value = Array.from(new Set(headerList))
      } else {
        tableData.value = []
        tabHeader.value = []
      }
      pages.loading = false
    })
    .catch((err: any) => {
      pages.loading = false
      console.log(err)
    })
}
const handleSizeChange = (val: number) => {
  pages.pageSize = val
  getUserTaskList()
}
const handleCurrentChange = (val: number) => {
  pages.page = val
  getUserTaskList()
}
</script>
<style lang="less" scoped>
.classManagePage-box {
  width: 100%;
  padding: 20px;
  .classManagePage-nav {
    padding-bottom: 20px;
    border-bottom: 1px solid #e6e8ed;
  }
  .classManagePage-content {
    width: 100%;
    padding: 30px 0;
    .staticBox {
      width: 100%;
      height: 60px;
      display: flex;
      justify-content: flex-start;
      align-items: center;
      margin-bottom: 40px;
      .pub_staticItem {
        display: flex;
        img {
          width: 60px;
          height: 60px;
          background: #ebf4ff;
          border-radius: 8px 8px 8px 8px;
          margin-right: 15px;
        }
        .staticText {
          display: flex;
          flex-direction: column;
          justify-content: space-between;
          span:nth-child(1) {
            font-family: PingFang SC;
            font-weight: bold;
            font-size: 14px;
            color: #a5a7ab;
          }
          span:nth-child(2) {
            font-family: DIN;
            font-weight: 500;
            font-size: 20px;
            color: #000000;
          }
        }
      }
    }
    .chartsBox {
      margin-bottom: 40px;
      #chartsContent {
        height: 600px;
      }
    }
    .listContent {
      width: 100%;
      .tableTitle {
        font-family: PingFang SC;
        font-weight: bold;
        font-size: 16px;
        color: #000000;
        margin-bottom: 22px;
      }
    }
  }
}
</style>
src/views/classManage/jobDetail.vue
New file
@@ -0,0 +1,840 @@
<template>
  <div class="classManagePage-box">
    <div class="classManagePage-nav">
      <el-breadcrumb :separator-icon="ArrowRight">
        <el-breadcrumb-item>我的班级</el-breadcrumb-item>
        <el-breadcrumb-item>{{ classInfo?.name }}</el-breadcrumb-item>
        <el-breadcrumb-item>作业管理详情</el-breadcrumb-item>
      </el-breadcrumb>
    </div>
    <div class="classManagePage-content">
      <div class="backBtn">
        <el-button @click="goBack()" type="primary" link>
          <el-icon style="margin-right: 5px"><ArrowLeftBold /></el-icon> 返回
        </el-button>
      </div>
      <div class="contentBox">
        <div class="content-tab-box">
          <div class="content-header">
            <div class="selectState">
              <span>状态:</span>
              <el-select placeholder="全部" v-model="workState" @change="selectChangeState">
                <el-option
                  v-for="item in options"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
              </el-select>
            </div>
            <div class="searchBox" v-if="workState != 'WaitSubmit'">
              <!-- <el-input
                v-model="searchKey"
                clearable
                @clear="searchData"
                placeholder="请输入关键字"
              >
                <template #append>
                  <el-button type="primary" @click="searchData" class="searchBtn" :icon="Search" />
                </template>
              </el-input> -->
            </div>
          </div>
          <div class="content-list-box">
            <el-table
              :data="tableData"
              border
              max-height="600px"
              style="width: 100%"
              :header-cell-class-name="'headerCellClassName'"
              v-loading="tableLoading"
              v-if="workState != 'WaitSubmit'"
            >
              <el-table-column label="序号" prop="id" width="60"> </el-table-column>
              <el-table-column label="姓名" #default="scope" width="150">
                <span>{{ scope.row.appUser?.userInfo?.name }}</span>
              </el-table-column>
              <el-table-column label="状态" prop="state" #default="scope">
                <span v-if="scope.row.state == 'WaitCheck'" style="color: #409eff">未批改</span>
                <span v-if="scope.row.state == 'Normal'" style="color: #67c23a">已批改</span>
                <span v-if="scope.row.state == 'WaitSubmit'" style="color: red">未提交</span>
              </el-table-column>
              <el-table-column label="得分" #default="scope">
                <span v-if="scope.row.result > 0">{{ scope.row.result }}</span>
                <span v-else>{{ scope.row.totalScore }}</span>
              </el-table-column>
              <el-table-column label="单选题" v-if="singleChoiceLength > 0">
                <el-table-column
                  v-for="(item, index) in singleChoiceLength"
                  :key="index"
                  :label="item"
                >
                  <template #default="scope">
                    <span v-if="scope.row.singleChoiceArr[index]?.score == 0" style="color: red">
                      {{ scope.row.singleChoiceArr[index]?.answer }}
                    </span>
                    <span v-else style="color: #67c23a">
                      {{ scope.row.singleChoiceArr[index]?.answer }}
                    </span>
                  </template>
                </el-table-column>
              </el-table-column>
              <el-table-column label="多选题" v-if="multipleChoiceLength > 0">
                <el-table-column
                  v-for="(item, index) in multipleChoiceLength"
                  :key="index"
                  :label="item"
                >
                  <template #default="scope">
                    <span v-if="scope.row.judgeArr[index]?.score == 0" style="color: red">
                      {{ scope.row.multipleChoiceArr[index]?.answer.join(',') }}
                    </span>
                    <span v-else style="color: #67c23a">
                      {{ scope.row.multipleChoiceArr[index]?.answer.join(',') }}
                    </span>
                  </template>
                </el-table-column>
              </el-table-column>
              <el-table-column label="判断题" v-if="judgeLength > 0">
                <el-table-column v-for="(item, index) in judgeLength" :key="index" :label="item">
                  <template #default="scope">
                    <span v-if="scope.row.judgeArr[index]?.score == 0" style="color: red">
                      {{ scope.row.judgeArr[index]?.answer }}
                    </span>
                    <span v-else style="color: #67c23a">
                      {{ scope.row.judgeArr[index]?.answer }}
                    </span>
                  </template>
                </el-table-column>
              </el-table-column>
              <el-table-column label="填空题(分)" v-if="completionLength > 0">
                <el-table-column
                  v-for="(item, index) in completionLength"
                  :key="index"
                  :label="item"
                >
                  <template #default="scope">
                    <span v-if="scope.row.completionArr[index]?.score == 0" style="color: red">
                      {{ scope.row.completionArr[index]?.score }}
                    </span>
                    <span v-else style="color: #67c23a">
                      {{ scope.row.completionArr[index]?.score }}
                    </span>
                  </template>
                </el-table-column>
              </el-table-column>
              <el-table-column label="主观题(分)" v-if="otherLength > 0">
                <el-table-column v-for="(item, index) in otherLength" :key="index" :label="item">
                  <template #default="scope">
                    <span v-if="scope.row.otherArr[index]?.score == 0" style="color: red">
                      {{ scope.row.otherArr[index]?.score }}
                    </span>
                    <span v-else style="color: #67c23a">
                      {{ scope.row.otherArr[index]?.score }}
                    </span>
                  </template>
                </el-table-column>
              </el-table-column>
              <el-table-column label="操作" width="150" #default="scope">
                <el-button
                  link
                  v-if="scope.row.state != 'Normal'"
                  style="color: #409eff"
                  @click="openDialog(scope.row)"
                >
                  判断主观题
                </el-button>
                <el-button link v-if="scope.row.state == 'Normal'"> -- </el-button>
              </el-table-column>
            </el-table>
            <el-table
              :data="dataList"
              border
              v-else
              max-height="600px"
              style="width: 100%"
              :header-cell-class-name="'headerCellClassName'"
              v-loading="pages.loading"
            >
              <el-table-column prop="index" label="序号" width="70" />
              <el-table-column prop="appUserId" label="学号" width="150" />
              <el-table-column label="姓名" width="400">
                <template #default="scope">
                  <div class="userBox">
                    <el-avatar
                      style="margin-right: 10px"
                      v-if="scope.row.appUser.icon"
                      :size="35"
                      :src="scope.row.appUser.icon"
                    />
                    <el-avatar
                      style="margin-right: 10px"
                      :size="35"
                      v-if="!scope.row.appUser.icon"
                      :icon="UserFilled"
                    />
                    <span v-if="scope.row.appUser.name">{{ scope.row.appUser.name }}</span>
                  </div>
                </template>
              </el-table-column>
              <el-table-column prop="createDate" label="加入班级时间" />
              <el-table-column label="操作" width="220" #default="scope">
                <el-button link style="color: red" @click="remindWork(scope.row)">
                  催交作业
                </el-button>
              </el-table-column>
            </el-table>
            <div class="pageBox">
              <el-pagination
                v-model:current-page="pages.currentPage"
                :page-size="pages.pageSize"
                :size="'small'"
                :disabled="pages.count <= 1"
                layout="total, prev, pager, next"
                :total="pages.count"
                @size-change="handleSizeChange"
                @current-change="handleCurrentChange"
              />
            </div>
          </div>
        </div>
        <!-- 主观题判断 -->
        <el-dialog
          class="customDialog"
          title="判断主观题"
          v-model="otherVisible"
          destroy-on-close
          width="800"
          :before-close="close"
        >
          <div class="pubContent">
            <questionDom
              :question-list="otherQuestions"
              :is-judge="true"
              @judge-update="judgeUpdate"
            />
          </div>
        </el-dialog>
      </div>
    </div>
  </div>
</template>
<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router'
import { inject, onMounted, reactive, ref } from 'vue'
import { Search, UserFilled, ArrowRight } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import questionDom from './components/questionDom.vue'
import moment from 'moment'
const route: any = useRoute()
const router = useRouter()
const classInfo = JSON.parse(route.query.classInfo)
const MG: any = inject('MG')
const config: any = inject('config')
const tool: any = inject('toolClass')
// 作业详情
const workState = ref('all')
const searchKey = ref('')
const cmsDatas: any = ref([])
const tableData: any = ref([])
const tableLoading = ref(true)
const singleChoiceLength = ref(0)
const multipleChoiceLength = ref(0)
const judgeLength = ref(0)
const completionLength = ref(0)
const otherLength = ref(0)
const otherVisible = ref(false)
const otherQuestions: any = ref([]) // 主观题
const cacheDate: any = ref([])
const currentWorkId = ref(null) // 当前作业id
const dataList: any = ref([]) // 未提交列表
const options = ref([
  {
    label: '全部',
    value: 'all'
  },
  {
    label: '未批改',
    value: 'WaitCheck'
  },
  {
    label: '已批改',
    value: 'Normal'
  },
  {
    label: '未提交',
    value: 'WaitSubmit'
  }
])
let pages = reactive({
  currentPage: 1,
  page: 1,
  pageSize: 15,
  count: 0,
  loading: true
})
const questionKey = [
  'Name',
  'Embedded_QuestionBank_AnalysisCon',
  'Embedded_QuestionBank_Answer',
  'Embedded_QuestionBank_Difficulty',
  'Embedded_QuestionBank_KnowledgePoint',
  'Embedded_QuestionBank_Option',
  'Embedded_QuestionBank_OptionStyle',
  'Embedded_QuestionBank_QuestionType',
  'Embedded_QuestionBank_Score',
  'Embedded_QuestionBank_Stem',
  'Embedded_QuestionBank_StemStyle'
]
//  分页
const handleSizeChange = (val: number) => {
  pages.pageSize = val
  if (workState.value != 'WaitSubmit') {
    getTaskDetail()
  } else {
    getUnSubmitList()
  }
}
const handleCurrentChange = (val: number) => {
  pages.page = val
  pages.currentPage = val
  if (workState.value != 'WaitSubmit') {
    getTaskDetail()
  } else {
    getUnSubmitList()
  }
}
onMounted(() => {
  getTaskCmsList()
})
// 状态改变
const selectChangeState = (item: any) => {
  workState.value = item
  if (item == 'WaitSubmit') {
    // const data = [{ field: 'State', value: 'Normal', subFilters: [] }]
    getUnSubmitList()
  } else {
    const data = item != 'all' ? [{ field: 'State', value: item, subFilters: [] }] : []
    getTaskDetail(data)
  }
}
const openDialog = (item: any) => {
  otherVisible.value = true
  currentWorkId.value = item.id
}
// 获取详情
const getTaskDetail = (filter?: any, search?: any) => {
  const filterList = filter ?? []
  const searchList = search ?? []
  tableLoading.value = true
  const data = {
    start: (pages.page - 1) * pages.pageSize,
    size: pages.pageSize,
    taskId: classInfo?.taskWorkId,
    classId: classInfo?.id,
    filterList,
    searchList
  }
  MG.edu
    .getTaskSubmitList(data)
    .then((res: any) => {
      pages.loading = false
      pages.count = res.totalSize
      cacheDate.value = []
      // 题库题目类型
      const questionTypeList = [
        { name: '简答题', totalScore: 0, value: 'shortAnswer', data: [] },
        { name: '论述题', totalScore: 0, value: 'discuss', data: [] },
        { name: '连线题', totalScore: 0, value: 'matching', data: [] },
        { name: '分类题', totalScore: 0, value: 'classification', data: [] }
      ]
      try {
        let list: any = []
        res.datas.forEach((item: any) => {
          const parentData = {
            feedBack: item.feedBack,
            id: item.id,
            result: item.result,
            type: item.type,
            state: item.state,
            submit: item.submit,
            updateTaskSubmitCmsItemRequests: []
          }
          item.singleChoiceName = '单选题'
          item.singleChoiceArr = []
          item.multipleChoiceName = '多选题'
          item.multipleChoiceArr = []
          item.judgeName = '判断题'
          item.judgeArr = []
          item.completionName = '填空题'
          item.completionArr = []
          item.otherName = '主观题'
          item.otherArr = []
          item.totalScore = 0
          const submitData = deduplicateArray(item.submitAndCmsItemLinks, 'cmsItemId')
          submitData.forEach((citem: any) => {
            const obj = cmsDatas.value.find((i: any) => i.id == citem.cmsItemId)
            item.totalScore += citem.score
            if (citem.answer != '') {
              if (obj.questionType == 'singleChoice') {
                citem.answer = JSON.parse(citem.answer) ?? '-'
                item.singleChoiceArr.push(citem)
              } else if (obj.questionType == 'multipleChoice') {
                citem.answer = JSON.parse(citem.answer) ?? '-'
                item.multipleChoiceArr.push(citem)
              } else if (obj.questionType == 'judge') {
                citem.answer = JSON.parse(citem.answer) ?? '-'
                item.judgeArr.push(citem)
              } else if (obj.questionType == 'completion') {
                citem.answer = JSON.parse(citem.answer) ?? '-'
                item.completionArr.push(citem)
              } else {
                obj.score = citem.score
                obj.answer = citem.answer
                obj.updateId = citem.id
                obj.parentId = item.id
                item.otherArr.push(citem)
                list.push(obj)
              }
            }
          })
          cacheDate.value.push(parentData)
        })
        list.forEach((item: any) => {
          const index = findIndexByValue(questionTypeList, item.questionType)
          if (index > -1) {
            questionTypeList[index].data.push(item)
            if (item.score) {
              questionTypeList[index].totalScore += item.score
            }
          }
        })
        otherQuestions.value = questionTypeList.filter((item) => item.data.length > 0)
        const lenghtArr = [...res.datas]
        singleChoiceLength.value = lenghtArr.reduce(
          (max: any, obj: any) => Math.max(max, obj.singleChoiceArr.length),
          0
        )
        multipleChoiceLength.value = lenghtArr.reduce(
          (max: any, obj: any) => Math.max(max, obj.multipleChoiceArr.length),
          0
        )
        judgeLength.value = lenghtArr.reduce(
          (max: any, obj: any) => Math.max(max, obj.judgeArr.length),
          0
        )
        completionLength.value = lenghtArr.reduce(
          (max: any, obj: any) => Math.max(max, obj.completionArr.length),
          0
        )
        otherLength.value = lenghtArr.reduce(
          (max: any, obj: any) => Math.max(max, obj.otherArr.length),
          0
        )
        tableLoading.value = false
        tableData.value = res.datas
      } catch (err) {
        tableLoading.value = false
        console.log(err)
      }
    })
    .catch((err: any) => {
      console.log(err)
    })
}
// 获取任务下的资源列表
const getTaskCmsList = () => {
  tableLoading.value = true
  const data = {
    start: 0,
    size: 999,
    searchList: [],
    taskId: classInfo?.taskWorkId, // taskData?.id
    path: String(classInfo?.taskCmsId), //taskData?.rootCmsItemId
    type: '*',
    keys: questionKey
  }
  MG.edu
    .getTaskCmsItem(data)
    .then((res: any) => {
      for (let i = 0; i < res.datas.length; i++) {
        let item = res.datas[i]
        item.index = i + 1
        // 处理字段
        if (questionKey != null) {
          for (let fieldKey of questionKey) {
            if (item.datas[fieldKey]) {
              const values = JSON.parse(item.datas[fieldKey])
              if (values.length > 0) {
                // 用字段名处理返回的字段值
                if (values[0].Value) {
                  item[fieldKey] = values[0].Value
                } else if (values[0].Data) {
                  item[fieldKey] = values[0].Data.Value
                } else if (!values[0].Value && values[0].FileList?.length > 0) {
                  item[fieldKey] = values[0].FileList
                } else {
                  item[fieldKey] = '-'
                }
              }
            }
          }
        }
      }
      cmsDatas.value = changeQuestionData(res.datas)
      tableLoading.value = false
    })
    .catch((e: any) => {
      ElMessage({
        message: '列表获取失败',
        type: 'error'
      })
      tableLoading.value = false
      console.log(e)
    })
}
// 处理题目数据结构
const changeQuestionData = (res: any) => {
  try {
    let list = []
    list = res?.map((item: any) => {
      try {
        if (item.Embedded_QuestionBank_Stem) {
          item.questionStem = JSON.parse(item.Embedded_QuestionBank_Stem)
        }
        if (
          item.Embedded_QuestionBank_Option &&
          item.Embedded_QuestionBank_Option.indexOf('[') > -1
        ) {
          item.questionOption = JSON.parse(item.Embedded_QuestionBank_Option)
        }
        if (
          item.Embedded_QuestionBank_Answer &&
          item.Embedded_QuestionBank_Answer.indexOf('[') > -1
        ) {
          item.Embedded_QuestionBank_Answer = JSON.parse(item.Embedded_QuestionBank_Answer)
        }
        return {
          ...item,
          questionType: item.Embedded_QuestionBank_QuestionType,
          questionAnalysisCon: item.Embedded_QuestionBank_AnalysisCon,
          questionAnswer: item.Embedded_QuestionBank_Answer,
          customAnswer: null
        }
      } catch (error) {
        console.log(item)
      }
    })
    getTaskDetail()
    return list
  } catch (error) {
    console.log(error)
    return []
  }
}
const findIndexByValue = (res: any, type: string) => {
  for (let i = 0; i < res.length; i++) {
    if (res[i].value == type) {
      return i
    }
  }
  return -1 // 如果未找到,则返回 -1
}
// 获取未提交任务列表
const getUnSubmitList = (filter?: any, search?: any) => {
  const filterList = filter ?? []
  const searchList = search ?? []
  const data = {
    start: (pages.page - 1) * pages.pageSize,
    size: pages.pageSize,
    taskId: classInfo?.taskWorkId,
    classId: classInfo?.id,
    filterList,
    searchList
  }
  MG.edu
    .getUnSubmitList(data)
    .then((res: any) => {
      const { datas, totalSize } = res
      pages.loading = false
      pages.count = totalSize
      if (datas.length > 0) {
        dataList.value = datas.map((item: any, index: number) => {
          const userInfo = item.appUser?.infoList[0] ?? null
          item.appUser.name = userInfo.name
          item.appUser.icon = userInfo.icon
          return {
            ...item,
            index: index + 1,
            createDate: moment(item.createDate).format('YYYY-MM-DD'),
            appUserId: item.appUser.id
          }
        })
        pages.count = totalSize
      }
    })
    .catch((err: any) => {
      console.log(err)
    })
}
// 关闭
const close = () => {
  otherVisible.value = false
}
// 主观题提交
const judgeUpdate = (item: any) => {
  const listData = [...cacheDate.value]
  let judgeScore = 0
  if (item) {
    const requestData = {
      classId: classInfo?.id,
      requests: []
    }
    const data = JSON.parse(item)
    listData.forEach((citem: any) => {
      const strData = data.find((i: any) => i.parentId == citem.id)
      if (strData) {
        citem.updateTaskSubmitCmsItemRequests = data.map((uitem: any) => {
          judgeScore += uitem.score
          return {
            linkId: uitem.updateId,
            score: uitem.score,
            answer: uitem.answer,
            state: 'Normal',
            comments: 'judge'
          }
        })
      }
    })
    let dataobj = null
    dataobj = listData.find((litem) => litem.id == currentWorkId.value)
    dataobj.state = 'Normal'
    dataobj.result = dataobj.result + judgeScore
    requestData.requests = [dataobj]
    MG.edu
      .updateTaskSubmit(requestData)
      .then((res: any) => {
        if (res) {
          otherVisible.value = false
          getTaskDetail()
        }
      })
      .catch((err: any) => {
        console.log(err)
      })
  }
}
//数组去重
const deduplicateArray = (arr: any, idKey: string) => {
  const seen: any = {}
  const deduplicatedArray = arr.filter((item: any) => {
    const id = item[idKey]
    if (!seen[id]) {
      seen[id] = true
      return true
    }
    return false
  })
  return deduplicatedArray
}
// 催交作业
const remindWork = (item: any) => {
  const content = classInfo?.name + '班' + item.appUser.name + '同学,请及时提交作业'
  const data = {
    description: '',
    icon: '',
    state: 'Normal',
    topicIdOrRefCode: String(sessionStorage.messageId),
    name: '未交作业人员',
    content,
    type: 'Normal',
    cmsTypeRefCode: '',
    newDataListRequest: []
  }
  MG.ugc
    .newTopicMessage(data)
    .then((res: any) => {
      if (res) {
        ElMessage({
          type: 'success',
          message: '已催交'
        })
      }
    })
    .catch((err: any) => {
      console.log(err)
    })
}
// 作业搜索
const searchData = () => {
  const data = [
    {
      compareType: 'Contains',
      keywords: searchKey.value,
      field: 'Name'
    }
  ]
  pages.page = 1
  if (workState.value != 'WaitSubmit') {
    getTaskDetail(undefined, searchKey.value ? data : [])
  } else {
    getUnSubmitList(undefined, searchKey.value ? data : [])
  }
}
const goBack = () => {
  router.go(-1)
}
</script>
<style lang="less" scoped>
.classManagePage-box {
  background: #fff;
  .classManagePage-nav {
    width: 100%;
    padding: 0 20px;
    height: 40px;
    border-bottom: 1px solid #e6e8ed;
    position: sticky;
    top: 0;
    z-index: 999;
    display: flex;
    align-items: center;
    background: #fff;
  }
  .classManagePage-content {
    width: 100%;
    position: relative;
    .backBtn {
      width: 100%;
      height: 45px;
      margin-bottom: 10px;
      padding: 0 20px;
      display: flex;
      align-items: center;
      position: sticky;
      top: 40px;
      z-index: 99;
      background: #fff;
      box-shadow: 0px 0px 20px 1px #eeeeee83;
    }
    .contentBox {
      width: 100%;
      padding: 0 20px;
      box-sizing: border-box;
      .content-title-box {
        padding: 0 10px;
        display: flex;
        justify-content: flex-start;
        align-items: center;
        border-left: 4px solid #ff6d00;
        span {
          margin-right: 15px;
        }
      }
      .content-user-box {
        width: 100%;
        padding: 10px 10px;
        box-sizing: border-box;
        display: flex;
        overflow-x: auto;
        .user-info-box {
          width: auto;
          display: flex;
          align-items: center;
          padding: 10px 15px;
          .user-info-data-box {
            flex: 1;
            height: 100%;
            display: flex;
            flex-direction: column;
            align-items: flex-start;
            justify-content: space-between;
            margin-left: 10px;
            .user-name {
              padding: 6px 0;
            }
            .user-num {
              padding: 6px 0;
              color: #666;
              font-size: 12px;
              white-space: nowrap;
            }
          }
        }
      }
      .content-user-box::-webkit-scrollbar {
        height: 3px;
      }
      .content-user-box::-webkit-scrollbar-thumb {
        background-color: #ff6d00;
        cursor: pointer;
      }
      .content-user-box::-webkit-scrollbar-track-piece {
        background: #eee;
      }
      .content-tab-box {
        .content-header {
          display: flex;
          align-items: center;
          justify-content: space-between;
          padding: 10px 0;
          .searchBox {
            width: 300px;
            float: left;
            padding: 10px 0;
            .searchBtn {
              background-color: var(--el-color-primary);
              color: #fff;
              border-top-left-radius: 0;
              border-bottom-left-radius: 0;
            }
          }
        }
        .content-list-box {
          .headerCellClassName {
            .cell {
              text-align: center;
            }
          }
        }
      }
    }
  }
  ::v-deep(.customDialog) {
    border-radius: 5px;
    margin: 10px auto;
    padding-bottom: 20px;
    .pubContent {
      position: relative;
      height: 80vh;
    }
  }
  .pageBox {
    padding: 10px 0;
    display: flex;
    justify-content: flex-end;
  }
}
</style>
src/views/classManage/jobManage.vue
New file
@@ -0,0 +1,1895 @@
<template>
  <div class="classManagePage-box">
    <div class="classManagePage-nav">
      <el-breadcrumb :separator-icon="ArrowRight">
        <el-breadcrumb-item>我的班级</el-breadcrumb-item>
        <el-breadcrumb-item>{{ classInfo?.name }}</el-breadcrumb-item>
        <el-breadcrumb-item>作业管理</el-breadcrumb-item>
      </el-breadcrumb>
    </div>
    <div class="classManagePage-content">
      <div class="cartClass">
        <!-- <el-tabs v-model="jobState" @tab-click="tabCart">
          <el-tab-pane label="全部" name="all"></el-tab-pane>
          <el-tab-pane label="进行中" name="payment"></el-tab-pane>
          <el-tab-pane label="已过期" name="complete"></el-tab-pane>
        </el-tabs> -->
        <div class="titleOptions">
          <span>作业管理</span>
        </div>
      </div>
      <div class="headerBox">
        <div class="searchBox">
          <el-input
            v-model="searchKey"
            clearable
            @clear="searchTask"
            @keydown.enter="searchTask"
            placeholder="请输入关键字"
          >
            <template #append>
              <el-button type="primary" @click="searchTask" class="searchBtn" :icon="Search" />
            </template>
          </el-input>
        </div>
        <el-button
          style="float: right"
          v-if="userInfo?.role == 'Teacher'"
          @click="openWork()"
          type="primary"
          round
        >
          <el-icon size="large" style="margin-right: 3px"><Document /></el-icon>
          新建作业
        </el-button>
      </div>
      <div class="listBox">
        <el-table
          :data="tableData"
          :header-cell-style="{ background: '#eee' }"
          max-height="600px"
          style="width: 100%"
          v-loading="pages.loading"
        >
          <el-table-column label="序号" prop="id" width="70"> </el-table-column>
          <el-table-column label="名称" prop="name"> </el-table-column>
          <el-table-column label="作业开始日期" prop="beginDate"> </el-table-column>
          <el-table-column label="作业结束日期" prop="endDate"> </el-table-column>
          <el-table-column label="作业完成情况(人数)" prop="submitCount"></el-table-column>
          <el-table-column label="操作" width="200" #default="scope">
            <el-button
              link
              style="color: #409eff"
              v-if="scope.row.state == 'Normal'"
              @click="detail(scope.row)"
              >详情</el-button
            >
            <!-- v-if="scope.row.state != 'Normal'" -->
            <el-button
              link
              type="primary"
              v-if="scope.row.state != 'Normal'"
              @click="edit(scope.row)"
              >编辑</el-button
            >
            <el-button
              link
              style="color: #67c23a"
              v-if="scope.row.state == 'Waiting'"
              @click="releaseWork(scope.row)"
              >发布</el-button
            >
            <el-button link type="danger" @click="removeTaskItem(scope.row)">移除</el-button>
          </el-table-column>
        </el-table>
        <div class="pageBox">
          <el-pagination
            v-model:current-page="pages.currentPage"
            :page-size="pages.pageSize"
            :size="'small'"
            :disabled="pages.count <= 1"
            layout="total, prev, pager, next"
            :total="pages.count"
            @size-change="handleSizeChange"
            @current-change="handleCurrentChange"
          />
        </div>
      </div>
      <!-- 新建作业 -->
      <el-dialog
        class="customDialog"
        destroy-on-close
        v-model="visible"
        :title="visibleEdit ? '更新作业' : '新建作业'"
        width="1650"
        :before-close="close"
        align-center
      >
        <div class="stepBox">
          <el-steps
            style="max-width: 600px; margin: auto"
            :active="stepActive"
            finish-status="success"
            align-center
          >
            <el-step :title="visibleEdit ? '更新作业' : '新建作业'" />
            <el-step title="添加题目" />
            <el-step title="设置分数" />
            <el-step title="完成" />
          </el-steps>
        </div>
        <div class="newTaskBox" v-if="stepActive == 0">
          <el-row class="row-center">
            <el-col :span="2" class="labelItem">
              <div class="grid-content ep-bg-purple" />
              名称
            </el-col>
            <el-col :span="20">
              <div class="grid-content ep-bg-purple-light" />
              <el-input
                v-model="taskItem.homeworkName"
                placeholder="请填写名称"
                size="large"
                clearable
                style="width: 600px"
              />
            </el-col>
          </el-row>
          <el-row class="row-center">
            <el-col :span="2" class="labelItem">
              <div class="grid-content ep-bg-purple" />
              开始日期
            </el-col>
            <el-col :span="20">
              <div class="grid-content ep-bg-purple-light" />
              <!-- <el-input v-model="taskItem.beginDate" placeholder="请选择开始日期" size="large" clearable /> -->
              <el-date-picker
                v-model="taskItem.homeworkStartingDate"
                type="date"
                placeholder="请选择开始日期"
                size="large"
                style="width: 600px"
              />
            </el-col>
          </el-row>
          <el-row class="row-center">
            <el-col :span="2" class="labelItem">
              <div class="grid-content ep-bg-purple" />
              结束日期
            </el-col>
            <el-col :span="20">
              <div class="grid-content ep-bg-purple-light" />
              <el-date-picker
                v-model="taskItem.homeworkSubmissionDate"
                type="date"
                placeholder="请选择结束日期"
                size="large"
                style="width: 600px"
              />
            </el-col>
          </el-row>
          <el-row>
            <el-col :span="2" class="labelItem">
              <div class="grid-content ep-bg-purple" />
              描述
            </el-col>
            <el-col :span="20">
              <div class="grid-content ep-bg-purple-light" />
              <el-input
                v-model="taskItem.explain"
                placeholder="请填写说明"
                type="textarea"
                :rows="12"
                size="large"
                clearable
                style="width: 600px"
              />
            </el-col>
          </el-row>
        </div>
        <div class="workBox" v-if="stepActive == 1">
          <div class="workLeft">
            <el-tabs
              v-model="activeTabs"
              style="margin: auto"
              class="demo-tabs"
              @tab-change="handleClick"
            >
              <el-tab-pane label="系统" name="1"></el-tab-pane>
              <!-- <el-tab-pane label="教材" name="2"></el-tab-pane> -->
            </el-tabs>
            <div class="workList" v-if="workLeftList.length > 0 && !leftLoading">
              <!-- <div class="searchBox" v-if="activeTabs == '1'">
                <el-input v-model="sysWorkKey" clearable @clear="searchWork" placeholder="请输入关键字">
                  <template #append>
                    <el-button type="primary" class="searchBtn" @click="searchWork" :icon="Search" />
                  </template>
                </el-input>
              </div> -->
              <div class="work-list-item" v-for="(item, index) in workLeftList" :key="index">
                <el-tooltip :content="item.name" placement="bottom-end" effect="light">
                  <p
                    :class="
                      index == sysIndex ? 'work-list-item-text leftBg' : 'work-list-item-text'
                    "
                    @click="clickRightItem(item, index)"
                  >
                    {{ item.name }}
                  </p>
                </el-tooltip>
              </div>
            </div>
            <div
              class="workList"
              v-if="workLeftList.length == 0 && !leftLoading"
              style="display: flex; align-items: center; justify-content: center"
            >
              <el-empty image-size="100px" />
            </div>
            <div
              class="workList"
              v-if="leftLoading"
              style="
                display: flex;
                align-items: center;
                justify-content: center;
                background-color: none;
              "
              v-loading="leftLoading"
            ></div>
          </div>
          <div class="workRight">
            <div class="right-list-title">
              <span>题目列表</span>
              <!-- <el-button type="primary" round size="small" icon="Plus">新建</el-button> -->
            </div>
            <div class="right-list-components">
              <questionDom
                v-if="workRightList.length > 0 && !rightLoading"
                :questionList="workRightList"
                @selectQuestion="selectQuestion"
                :noCheckbox="true"
              />
              <div v-if="rightLoading" v-loading="rightLoading"></div>
              <el-empty v-if="workRightList.length == 0 && !rightLoading" />
            </div>
          </div>
        </div>
        <div class="workBox" v-if="stepActive == 2">
          <div class="workLeft" style="width: 230px">
            <div class="scoreTitle">
              <span>题目类型</span>
            </div>
            <div class="workList" v-if="scoreLeftList.length > 0">
              <div class="work-list-item" v-for="(item, index) in scoreLeftList" :key="index">
                <p
                  :class="
                    index == scoreIndex ? 'work-list-item-text leftBg' : 'work-list-item-text'
                  "
                  @click="scoreItem(item, index)"
                >
                  {{ item.name }} ({{ item.data.length }})
                </p>
              </div>
            </div>
            <div
              class="workList"
              v-if="scoreLeftList.length == 0"
              style="display: flex; align-items: center; justify-content: center"
            >
              <el-empty image-size="100px" />
            </div>
          </div>
          <div class="workRight">
            <div class="right-list-title">
              <span>题目列表</span>
            </div>
            <div class="right-score-components">
              <div class="scoreBtn">
                <!-- <el-button size="small">上移</el-button>
                <el-button size="small">下移</el-button> -->
                <div class="setScore" v-if="scoreLeftList.length > 0">
                  <span>批量设置分数:</span>
                  <el-input-number
                    v-model="scoreValue"
                    :min="0"
                    :max="100"
                    controls-position="right"
                    @change="changeScore"
                  />
                  <el-button v-if="scoreValue > 0" style="margin-left: 10px" @click="setScoreArr"
                    >确认</el-button
                  >
                </div>
              </div>
              <el-table
                :data="scoreDataList"
                max-height="500px"
                :header-cell-style="{ background: '#eee' }"
                style="width: 100%"
                v-loading="selectedLoading"
              >
                <el-table-column prop="index" label="序号" width="100" />
                <el-table-column prop="name" label="题目">
                  <template #default="scope">
                    <span>{{ scope.row.questionStem.stemTxt }}</span>
                  </template>
                </el-table-column>
                <el-table-column label="分数" width="120">
                  <template #default="scope">
                    <span style="color: #ff6c00">{{ scope.row.score }}</span>
                  </template>
                </el-table-column>
                <el-table-column label="设置" width="260">
                  <template #default="scope">
                    <el-button
                      link
                      v-if="!scope.row.isInput"
                      type="primary"
                      size="small"
                      @click="openSetScore(scope.row)"
                      >设置分数</el-button
                    >
                    <el-input-number
                      v-if="scope.row.isInput"
                      v-model="scoreItemValue"
                      :min="0"
                      :max="100"
                      :controls="false"
                      size="small"
                      @blur="setScore(scope.row)"
                    />
                  </template>
                </el-table-column>
              </el-table>
            </div>
          </div>
        </div>
        <div class="workBox" style="height: 720px" v-if="stepActive == 3">
          <div class="finishBg">
            <img src="@/assets//images/class/finish.png" alt="" />
            <span>作业已创建完成</span>
          </div>
        </div>
        <template #footer v-if="stepActive == 0">
          <div style="width: 100%; display: flex; justify-content: flex-end">
            <el-button @click="nextStep" v-if="visibleEdit"> 跳过 </el-button>
            <el-button type="primary" :loading="newLoading" @click="newTask"> 确认 </el-button>
          </div>
        </template>
        <template #footer v-if="stepActive != 3 && stepActive != 0">
          <div class="leftFooter" :style="{ width: stepActive == 2 ? '230px' : '' }"></div>
          <div class="dialog-footer">
            <div class="check-list-box" v-if="stepActive == 1">
              <el-checkbox v-model="checkAll" @change="handleCheckAllChange"> 全选 </el-checkbox>
            </div>
            <div class="btnGroup" v-if="stepActive == 1">
              <el-button plain :disabled="checkData.length == 0" @click="addCmsitem">
                加入作业
              </el-button>
              <el-button plain @click="selectedData"> 查看已选 </el-button>
              <el-button type="primary" @click="nextStep"> 下一步 </el-button>
            </div>
            <div class="btnGroup" v-if="stepActive == 2">
              <el-button type="primary" plain @click="preStep"> 上一步 </el-button>
              <el-button plain @click="lookWork"> 预览作业 </el-button>
              <el-button type="primary" @click="nextStep"> 下一步 </el-button>
            </div>
          </div>
        </template>
        <template #footer v-if="stepActive == 3">
          <div style="width: 100%; display: flex; justify-content: flex-end">
            <el-button type="primary" @click="close"> 完 成 </el-button>
          </div>
        </template>
      </el-dialog>
      <!-- 查看已选 -->
      <el-dialog
        class="customDialog"
        align-center
        v-model="visibleView"
        destroy-on-close
        width="900"
      >
        <template #header>
          <div class="viewTitle">已选题目</div>
        </template>
        <div class="viewContent">
          <div class="workBox">
            <div class="workLeft" style="width: 200px">
              <div class="workList" style="height: 100%" v-if="selectedTypeList.length > 0">
                <div class="work-list-item" v-for="(item, index) in selectedTypeList" :key="index">
                  <p
                    :class="
                      index == selectedIndex ? 'work-list-item-text leftBg' : 'work-list-item-text'
                    "
                    @click="selectedItems(item, index)"
                  >
                    {{ item.name }}<span style="margin-left: 5px">({{ item.data.length }})</span>
                  </p>
                </div>
              </div>
              <div
                class="workList"
                v-if="selectedTypeList.length == 0"
                style="display: flex; align-items: center; justify-content: center"
              >
                <el-empty image-size="100px" />
              </div>
            </div>
            <div class="workRight">
              <div class="right-list-components">
                <questionDom
                  v-if="selectedDataList.length > 0 && !selectedLoading"
                  :questionList="selectedDataList"
                  @selectQuestion="selectQuestion"
                  @delete-item="deleteCmsItem"
                  :noCheckbox="false"
                  :isDelete="true"
                />
                <div v-if="selectedLoading" v-loading="selectedLoading"></div>
                <el-empty v-if="selectedDataList.length == 0 && !selectedLoading" />
              </div>
            </div>
          </div>
        </div>
        <template #footer>
          <div class="selectedFooter">
            <el-button type="primary" @click="visibleView = false"> 确认 </el-button>
          </div>
        </template>
      </el-dialog>
      <!-- 预览作业 -->
      <el-dialog
        class="customDialog"
        align-center
        v-model="visibleLook"
        destroy-on-close
        width="900"
      >
        <template #header>
          <div class="viewTitle">预览作业</div>
        </template>
        <div class="viewContent">
          <div class="workBox">
            <div class="workLeft" style="width: 200px">
              <div class="workList" style="height: 100%" v-if="lookLeftList.length > 0">
                <div class="work-list-item" v-for="(item, index) in lookLeftList" :key="index">
                  <p
                    :class="
                      index == lookIndex ? 'work-list-item-text leftBg' : 'work-list-item-text'
                    "
                    @click="lookItems(item, index)"
                  >
                    {{ item.name }}<span style="margin-left: 5px">({{ item.data.length }})</span>
                  </p>
                </div>
              </div>
              <div
                class="workList"
                v-if="lookLeftList.length == 0"
                style="display: flex; align-items: center; justify-content: center"
              >
                <el-empty image-size="100px" />
              </div>
            </div>
            <div class="workRight">
              <div class="right-list-components">
                <questionDom
                  v-if="lookDataList.length > 0 && !selectedLoading"
                  :questionList="lookDataList"
                  @selectQuestion="selectQuestion"
                  @delete-item="deleteCmsItem"
                  :is-preview="visibleLook"
                />
                <div v-if="selectedLoading" v-loading="selectedLoading"></div>
                <el-empty v-if="lookDataList.length == 0 && !selectedLoading" />
              </div>
            </div>
          </div>
        </div>
        <template #footer>
          <div class="selectedFooter">
            <el-button type="primary" @click="visibleLook = false"> 确认 </el-button>
          </div>
        </template>
      </el-dialog>
      <!-- 发布作业 -->
      <el-dialog
        class="customDialog"
        title="发布作业"
        v-model="visiblePub"
        destroy-on-close
        width="500"
        align-center
        :before-close="close"
      >
        <div class="pubContent">
          <span>发布日期:</span>
          <el-date-picker
            v-model="pubValue"
            type="daterange"
            range-separator="--"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            @change="selectPubDate"
          />
        </div>
        <template #footer>
          <div class="selectedFooter" style="padding: 0">
            <el-button type="primary" @click="submitWork"> 确认 </el-button>
          </div>
        </template>
      </el-dialog>
    </div>
  </div>
</template>
<script setup lang="ts">
import { reactive, ref, onMounted, inject, watch } from 'vue'
import { Search, ArrowRight } from '@element-plus/icons-vue'
import { useRoute, useRouter } from 'vue-router'
import questionDom from './components/questionDom.vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import moment from 'moment'
const route: any = useRoute()
const router = useRouter()
const MG: any = inject('MG')
const config: any = inject('config')
const tool: any = inject('toolClass')
const classInfo = JSON.parse(route.query.classInfo)
const jobState = ref('all')
const searchKey = ref('')
const dataList = ref([])
const userInfo = ref()
const defaultCmsPath = ref('')
// 获取task参数
const filterData = ref([
  {
    value: config.taskType.homeWork,
    field: 'Type',
    subFilters: []
  }
])
// task
const taskData = reactive({
  id: '',
  description: '',
  rootCmsItemId: '',
  name: '',
  type: '',
  state: '',
  groupId: '',
  beginDate: '',
  endDate: ''
})
// question Key
const questionKey = [
  'Name',
  'Embedded_QuestionBank_AnalysisCon',
  'Embedded_QuestionBank_Answer',
  'Embedded_QuestionBank_Difficulty',
  'Embedded_QuestionBank_KnowledgePoint',
  'Embedded_QuestionBank_Option',
  'Embedded_QuestionBank_OptionStyle',
  'Embedded_QuestionBank_QuestionType',
  'Embedded_QuestionBank_Score',
  'Embedded_QuestionBank_Stem',
  'Embedded_QuestionBank_StemStyle'
]
// dialognew
const visible = ref(false)
const stepActive = ref(0)
const activeTabs = ref('1')
// 新建作业
const newLoading = ref(false)
const taskItem = reactive({
  homeworkName: '',
  homeworkStartingDate: '',
  homeworkSubmissionDate: '',
  explain: ''
})
let pages = reactive({
  currentPage: 1,
  page: 1,
  pageSize: 15,
  count: 0,
  loading: false
})
// dialogedit
const visibleEdit = ref(false)
// dialogView
const visibleView = ref(false)
// dialogLook
const visibleLook = ref(false)
// left
const workLeftList: any = ref([])
const leftLoading = ref(false)
const sysIndex = ref(0)
// 分数
const scoreValue = ref(0) // 批量
const scoreItemValue = ref('') // 单一
const scoreLeftList: any = ref([])
const scoreDataList: any = ref([])
const scoreIndex = ref(0)
const scoreData: any = ref([]) // 自存储题目分数
const isSet = ref(false)
// right
const workRightList: any = ref([])
const rightLoading = ref(false)
const checkAll = ref(false)
const selectCache: any = ref([])
const checkData: any = ref([])
// 作业列表
const tableData: any = ref([])
// 已选作业资源
const selectedTypeList: any = ref([])
const selectedDataList: any = ref([])
const selectedLoading = ref(false)
const selectedIndex = ref(0)
//预览作业
const lookLeftList: any = ref([])
const lookDataList: any = ref([])
const lookIndex = ref(0)
// 发布作业
const visiblePub = ref(false)
const pubValue = ref(null)
onMounted(() => {
  const userCache: any = localStorage.getItem('jesk-userInfo')
  if (userCache) {
    userInfo.value = JSON.parse(userCache)
  }
  defaultCmsPath.value = classInfo.bookRefCode ? 'jsek_digitalTextbooks' : 'defaultGoodsStore3'
  getTaskList()
})
// 发布
const selectPubDate = (item: any) => {
  if (item.length > 0) {
    taskItem.homeworkStartingDate = moment(item[0]).format('YYYY-MM-DDTHH:mm:ss')
    taskItem.homeworkSubmissionDate = moment(item[1]).format('YYYY-MM-DDTHH:mm:ss')
  }
}
//  分页
const handleSizeChange = (val: number) => {
  pages.pageSize = val
  getTaskList()
}
const handleCurrentChange = (val: number) => {
  pages.page = val
  pages.currentPage = val
  getTaskList()
}
// 新建任务{Waiting:未开始,Normal:进行中,Ended:已结束}
const newTask = () => {
  newLoading.value = true
  if (!taskItem.homeworkName) {
    ElMessage({
      type: 'warning',
      message: '请填写作业名称 '
    })
    newLoading.value = false
    return false
  }
  if (!taskItem.homeworkStartingDate) {
    ElMessage({
      type: 'warning',
      message: '请填写作业开始时间 '
    })
    newLoading.value = false
    return false
  }
  if (!taskItem.homeworkSubmissionDate) {
    ElMessage({
      type: 'warning',
      message: '请填写作业结束时间'
    })
    newLoading.value = false
    return false
  }
  if (!visibleEdit.value) {
    const data = {
      name: taskItem.homeworkName,
      description: taskItem.explain,
      icon: '',
      type: config.taskType.homeWork,
      state: 'Waiting',
      groupId: classInfo?.id,
      order: 0,
      beginDate: taskItem.homeworkStartingDate,
      endDate: taskItem.homeworkSubmissionDate,
      duration: 0,
      config: JSON.stringify({ scoreData: [] })
    }
    MG.edu
      .newTask(data)
      .then((res: any) => {
        taskData.id = res
      })
      .catch((e: any) => {
        newLoading.value = false
        console.log(e)
      })
  } else {
    updateTask()
  }
  setTimeout(() => {
    newLoading.value = false
    stepActive.value++
    getTaskList()
  }, 600)
}
// 更新任务
const updateTask = () => {
  const data = {
    id: taskData.id,
    name: taskItem.homeworkName,
    description: taskItem.explain,
    icon: '',
    type: taskData.type,
    state: taskData.state,
    order: 0,
    beginDate: moment(taskItem.homeworkStartingDate).format('YYYY-MM-DDTHH:mm:ss'),
    endDate: moment(taskItem.homeworkSubmissionDate).format('YYYY-MM-DDTHH:mm:ss'),
    duration: 0,
    config: JSON.stringify({ scoreData: scoreData.value })
  }
  MG.edu
    .updateTask(data)
    .then((res: any) => {
      if (res) {
        if (stepActive.value == 2) {
          getTaskCmsList()
        }
        getTaskList()
      }
    })
    .catch((err: any) => {
      console.log(err, 'updateTask')
    })
}
// 获取任务列表
const getTaskList = (filter?: any[], search?: any[]) => {
  const filterList = filter ?? filterData.value
  const searchList = search ?? []
  pages.loading = true
  const data = {
    start: (pages.page - 1) * pages.pageSize,
    size: pages.pageSize,
    sort: {
      type: 'Desc',
      field: 'CreateDate'
    },
    filterList,
    searchList,
    groupId: classInfo?.id
  }
  MG.edu
    .getTaskList(data)
    .then((res: any) => {
      pages.count = res.totalSize
      if (res.datas.length > 0) {
        tableData.value = res.datas?.map((item: any) => {
          //  const currTime = Number(sessionStorage.currentDate);
          //  const taskTime = moment(item.endDate).valueOf()
          //   if(taskTime < currTime){
          //     console.log(taskTime,item.name,'7878')
          //   }
          return {
            ...item,
            beginDate: moment(item.beginDate).format('YYYY-MM-DD'),
            endDate: moment(item.endDate).format('YYYY-MM-DD')
          }
        })
        if (taskData.id) {
          scoreData.value = []
          const obj = res.datas.find((item: any) => item.id == taskData.id)
          taskData.id = obj.id
          taskData.rootCmsItemId = obj.rootCmsItemId
          if (obj.config) {
            scoreData.value = JSON.parse(obj.config).scoreData
          }
          taskData.name = obj.name
          taskData.groupId = classInfo?.id
          taskData.state = obj.state
          taskData.type = obj.type
          taskData.beginDate = moment(obj.beginDate).format('YYYY-MM-DDTHH:mm:ss')
          taskData.endDate = moment(obj.endDate).format('YYYY-MM-DDTHH:mm:ss')
        }
      } else {
        tableData.value = []
      }
      pages.loading = false
    })
    .catch((e: any) => {
      pages.loading = false
      console.log(e)
    })
}
// 搜索任务
const searchTask = () => {
  const data = [
    {
      compareType: 'Contains',
      keywords: searchKey.value,
      field: 'Name'
    }
  ]
  pages.page = 1
  pages.currentPage = 1
  getTaskList(undefined, data)
}
// 删除资源
const removeTaskItem = (item: any) => {
  const data = {
    ids: [item.id]
  }
  MG.edu
    .delTask(data)
    .then((res: any) => {
      if (res) {
        ElMessage({
          message: '删除成功',
          type: 'success'
        })
        getTaskList()
      }
    })
    .catch((e: any) => {
      ElMessage({
        message: '删除失败',
        type: 'error'
      })
    })
}
// 为任务添加资源
const addCmsitem = () => {
  const data = {
    taskId: taskData.id,
    requests: checkData.value?.map((item: any) => {
      return {
        path: String(taskData.rootCmsItemId),
        cmsItemId: item.id
      }
    })
  }
  MG.edu
    .addTaskCmsItemList(data)
    .then((res: any) => {
      if (res) {
        ElMessage({
          message: '已加入',
          type: 'success'
        })
        selectCache.value = []
        checkData.value = []
        checkAll.value = false
        workRightList.value?.forEach((citem: any) => {
          citem.data.forEach((titem: any) => (titem.isCheck = false))
        })
      }
    })
    .catch((e: any) => {
      ElMessage({
        message: '加入失败',
        type: 'error'
      })
    })
}
// 获取任务下的资源列表
const getTaskCmsList = () => {
  selectedLoading.value = true
  const data = {
    start: 0,
    size: 999,
    searchList: [],
    taskId: taskData?.id, // taskData?.id
    path: String(taskData?.rootCmsItemId), //taskData?.rootCmsItemId
    type: '*',
    keys: questionKey
  }
  MG.edu
    .getTaskCmsItem(data)
    .then((res: any) => {
      selectedLoading.value = false
      for (let i = 0; i < res.datas.length; i++) {
        let item = res.datas[i]
        item.index = i + 1
        const scoreItem = scoreData.value.find((i: any) => i.id == item.id)
        item.score = scoreItem?.score ?? 0
        item.isInput = false
        // 处理字段
        if (questionKey != null) {
          for (let fieldKey of questionKey) {
            if (item.datas[fieldKey]) {
              const values = JSON.parse(item.datas[fieldKey])
              if (values.length > 0) {
                // 用字段名处理返回的字段值
                if (values[0].Value) {
                  item[fieldKey] = values[0].Value
                } else if (values[0].Data) {
                  item[fieldKey] = values[0].Data.Value
                } else if (!values[0].Value && values[0].FileList?.length > 0) {
                  item[fieldKey] = values[0].FileList
                } else {
                  item[fieldKey] = '-'
                }
              }
            }
          }
        }
      }
      if (stepActive.value == 2) {
        scoreDataList.value = []
        scoreLeftList.value = changeQuestionData(res.datas)
        if (scoreLeftList.value.length > 0) {
          scoreDataList.value = scoreLeftList.value[scoreIndex.value].data
        }
        scoreValue.value = 0
      } else {
        selectedDataList.value = []
        selectedTypeList.value = changeQuestionData(res.datas)
        if (selectedTypeList.value.length > 0) {
          selectedDataList.value = [selectedTypeList.value[0]]
        }
      }
    })
    .catch((e: any) => {
      ElMessage({
        message: '列表获取失败',
        type: 'error'
      })
      console.log(e)
    })
}
// 删除已选的题目
const deleteCmsItem = (item: any) => {
  ElMessageBox.confirm('是否确认删除?', '提示', {
    confirmButtonText: '是',
    cancelButtonText: '否',
    type: 'warning'
  }).then(() => {
    const data = {
      taskId: taskData.id,
      requests: [
        {
          cmsItemId: item.id,
          path: String(taskData.rootCmsItemId)
        }
      ]
    }
    MG.edu
      .removeTaskCmsItemList(data)
      .then((res: any) => {
        if (res) {
          ElMessage({
            type: 'success',
            message: '已删除'
          })
          selectedIndex.value = 0
          getTaskCmsList()
        }
      })
      .catch((err: any) => {
        ElMessage({
          type: 'error',
          message: '删除失败,请稍后再试'
        })
      })
  })
}
// 题目分数列表切换
const scoreItem = (item: any, index: number) => {
  scoreIndex.value = index
  scoreDataList.value = item.data
}
// 打开设置文本框
const openSetScore = (item: any) => {
  scoreItemValue.value = ''
  item.isInput = true
}
// 预览作业
const lookWork = () => {
  visibleLook.value = true
  lookIndex.value = 0
  lookLeftList.value = [...scoreLeftList.value]
  lookDataList.value = [lookLeftList.value[0]]
}
// 批量设置分数
const changeScore = () => {
  isSet.value = true
}
// 批量设置分数确认
const setScoreArr = () => {
  let data = [...scoreData.value]
  let idsData = scoreDataList.value.map((item: any) => {
    return { id: item.id, score: scoreValue.value }
  })
  if (data.length == 0) {
    data = [...idsData]
  } else {
    data = updateArr(data, idsData)
  }
  scoreData.value = data
  updateTask()
  isSet.value = false
}
// 题目分数设置
const setScore = (item: any) => {
  item.score = Number(scoreItemValue.value)
  const data = [...scoreData.value.filter((citem: any) => citem.id != item.id)]
  data.push({ id: item.id, score: item.score })
  scoreData.value = [...data]
  item.isInput = false
  updateTask()
}
//  lookItems
const lookItems = (item: any, index: number) => {
  lookIndex.value = index
  lookDataList.value = [item]
}
// 已选题目切换
const selectedItems = (item: any, index: number) => {
  selectedIndex.value = index
  selectedDataList.value = [item]
}
// 表格tab事件{Waiting:未开始,Normal:进行中,Ended:已结束}
const tabCart = (event: Event | any) => {
  let queryFilter: any = []
  jobState.value = event.props.name
  pages.page = 1
  dataList.value = []
  if (jobState.value == 'all') {
    queryFilter = [...filterData.value]
  }
  if (jobState.value == 'payment') {
    queryFilter = [...filterData.value, { field: 'State', value: 'Normal', subFilters: [] }]
  }
  if (jobState.value == 'complete') {
    queryFilter = [...filterData.value, { field: 'State', value: 'Ended', subFilters: [] }]
  }
  getTaskList(queryFilter)
}
// 题目tab事件
const handleClick = (val: any) => {
  activeTabs.value = val
  workLeftList.value = []
  if (val == '1') {
    getBookQuestionList()
  } else {
    workLeftList.value = []
  }
}
// 下一步
const nextStep = () => {
  if (stepActive.value++ > 2) stepActive.value = 0
  if (stepActive.value == 2) getTaskCmsList()
}
// 上一步
const preStep = () => {
  if (stepActive.value-- == 0) stepActive.value = 0
}
// 选择题目
const selectQuestion = (item: any) => {
  selectCache.value.push(item)
  const list = selectCache.value.filter((citem: any) => citem.isCheck == true)
  let dataT: any = []
  workRightList.value?.forEach((citem: any) => {
    const itemArr = citem.data
    dataT.push(...itemArr)
  })
  checkData.value = [...new Set(list)]
  if (dataT?.length > 0) {
    checkAll.value = dataT.length == checkData.value.length
  }
}
// 全选
const handleCheckAllChange = (val: boolean) => {
  const data = workRightList.value
  data.forEach((ele: any) => {
    ele.data.forEach((item: any) => {
      item.isCheck = val
    })
  })
  let list: any = []
  data.forEach((citem: any) => {
    const itemArr = citem.data.filter((titem: any) => titem.isCheck == true)
    list.push(...itemArr)
  })
  checkData.value = list
}
// 查看已选
const selectedData = () => {
  visibleView.value = true
  getTaskCmsList()
}
// 新建作业打开
const openWork = () => {
  visible.value = true
  checkAll.value = false
  activeTabs.value = '1'
  stepActive.value = 0
  selectCache.value = []
  workRightList.value = []
  checkData.value = []
  getBookQuestionList()
}
// 关闭新建作业
const close = () => {
  taskItem.homeworkName = ''
  taskItem.homeworkStartingDate = ''
  taskItem.homeworkSubmissionDate = ''
  taskItem.explain = ''
  visible.value = false
  visibleEdit.value = false
  visiblePub.value = false
  getTaskList()
}
// 发布作业
const releaseWork = (item: any) => {
  visiblePub.value = true
  taskData.id = item.id
  taskData.rootCmsItemId = item.rootCmsItemId
  if (item.config) {
    scoreData.value = JSON.parse(item.config).scoreData
  }
  taskData.groupId = classInfo?.id
  taskData.state = 'Normal'
  taskData.type = item.type
  taskItem.homeworkName = item.name
  taskItem.explain = item.description
}
// 请确认发布
const submitWork = () => {
  visiblePub.value = false
  updateTask()
  submitNewMessage()
}
// 发布作业通知
const submitNewMessage = () => {
  const date = moment(taskItem.homeworkStartingDate).format('YYYY-MM-DD')
  const content = date + ',发布作业,' + taskItem.homeworkName
  const data = {
    description: '',
    icon: '',
    state: 'Normal',
    topicIdOrRefCode: String(sessionStorage.messageId),
    name: '老师发布作业',
    content,
    type: 'Normal',
    cmsTypeRefCode: '',
    newDataListRequest: []
  }
  MG.ugc
    .newTopicMessage(data)
    .then((res: any) => {
      if (res) {
        ElMessage({
          type: 'success',
          message: '已发布'
        })
      }
    })
    .catch((err: any) => {
      console.log(err)
    })
}
// 编辑
const edit = (item: any) => {
  scoreData.value = []
  visibleEdit.value = true
  taskData.id = item.id
  taskData.rootCmsItemId = item.rootCmsItemId
  if (item.config) {
    scoreData.value = JSON.parse(item.config).scoreData
  }
  taskData.groupId = classInfo?.id
  taskData.state = item.state
  taskData.type = item.type
  taskItem.homeworkName = item.name
  taskItem.homeworkStartingDate = item.beginDate
  taskItem.homeworkSubmissionDate = item.endDate
  taskItem.explain = item.description
  openWork()
}
// 详情
const detail = (item: any) => {
  const obj = classInfo
  obj.taskCmsId = item.rootCmsItemId
  obj.taskWorkId = item.id
  router.push({
    path: '/jobDetail',
    query: {
      classInfo: JSON.stringify(obj)
    }
  })
}
// 作业选中left
const clickRightItem = (item: any, index: number) => {
  workRightList.value = []
  checkAll.value = false
  sysIndex.value = index
  getBookQuestionContentList(item.productLinkPath, item)
}
// 搜索题干
const searchWork = () => {
  workLeftList.value = []
  getBookQuestionList()
}
// 获取系统题干列表
const getBookQuestionList = () => {
  leftLoading.value = true
  let query = {
    path: defaultCmsPath.value,
    queryType: '*',
    productId: String(classInfo?.bookId),
    storeInfo: defaultCmsPath.value,
    cmsPath: classInfo?.rootCmsItemId
  }
  MG.store
    .getProductDetail(query)
    .then(async (res: any) => {
      const { cmsDatas } = res.datas
      leftLoading.value = false
      const listQusetion = cmsDatas[0]?.datas
      const obj = listQusetion?.find((item: any) => item.type == 'questionBankFolder')
      if (obj && obj.productLinkPath) {
        let query = {
          path: defaultCmsPath.value,
          queryType: '*',
          productId: String(classInfo?.bookId),
          storeInfo: defaultCmsPath.value,
          cmsPath: obj.productLinkPath
        }
        MG.store
          .getProductDetail(query)
          .then(async (cres: any) => {
            const { cmsDatas } = cres.datas
            const list: any = cmsDatas[0]?.datas
            workLeftList.value = list
            if (list?.length > 0) {
              const cmsPath = list[0].productLinkPath
              getBookQuestionContentList(cmsPath, list[0])
            }
          })
          .catch((err: any) => {
            leftLoading.value = false
            workLeftList.value = []
            console.log(err)
          })
      }
    })
    .catch((err: any) => {
      leftLoading.value = false
      workLeftList.value = []
      console.log(err)
    })
}
// 递归获取
const getDataCms = async (res: any) => {
  for (const item of res) {
    const data = await MG.store.getProductDetail({
      path: defaultCmsPath.value,
      queryType: '*',
      productId: String(classInfo?.bookId),
      storeInfo: defaultCmsPath.value,
      cmsPath: item.productLinkPath,
      itemFields: {
        Embedded_QuestionBank_AnalysisCon: [],
        Embedded_QuestionBank_Answer: [],
        Embedded_QuestionBank_Difficulty: [],
        Embedded_QuestionBank_KnowledgePoint: [],
        Embedded_QuestionBank_Option: [],
        Embedded_QuestionBank_OptionStyle: [],
        Embedded_QuestionBank_QuestionType: [],
        Embedded_QuestionBank_Score: [],
        Embedded_QuestionBank_Stem: [],
        Embedded_QuestionBank_StemStyle: []
      }
    })
    if (item.type == 'questionBankFolder' && item.childrenFolderCount == 0) {
      return data.datas.cmsDatas
    } else {
      const { cmsDatas } = data.datas
      if (cmsDatas?.length > 0) {
        const list: any = await getDataCms(cmsDatas[0].datas)
        return list
      }
    }
  }
}
// 获取系统题目列表
const getBookQuestionContentList = (cmsPath: string, item: any) => {
  rightLoading.value = true
  workRightList.value = []
  if (item.type == 'questionBankFolder' && item.childrenFolderCount == 0) {
    let query = {
      path: defaultCmsPath.value,
      queryType: '*',
      productId: String(classInfo?.bookId),
      storeInfo: defaultCmsPath.value,
      cmsPath,
      itemFields: {
        Embedded_QuestionBank_AnalysisCon: [],
        Embedded_QuestionBank_Answer: [],
        Embedded_QuestionBank_Difficulty: [],
        Embedded_QuestionBank_KnowledgePoint: [],
        Embedded_QuestionBank_Option: [],
        Embedded_QuestionBank_OptionStyle: [],
        Embedded_QuestionBank_QuestionType: [],
        Embedded_QuestionBank_Score: [],
        Embedded_QuestionBank_Stem: [],
        Embedded_QuestionBank_StemStyle: []
      }
    }
    MG.store
      .getProductDetail(query)
      .then(async (cres: any) => {
        rightLoading.value = false
        const { cmsDatas } = cres.datas
        const list: any = cmsDatas[0]?.datas
        workRightList.value = changeQuestionData(list)
        if (workRightList.value?.length > 0) {
          const ids = workRightList.value.map((item: any) => item.id)
          checkAll.value = getInclude3(checkData.value, ids)
        }
      })
      .catch((err: any) => {
        rightLoading.value = false
        workRightList.value = []
        console.log(err)
      })
  } else {
    let query = {
      path: defaultCmsPath.value,
      queryType: '*',
      productId: String(classInfo?.bookId),
      storeInfo: defaultCmsPath.value,
      cmsPath: item.productLinkPath
    }
    MG.store
      .getProductDetail(query)
      .then(async (res: any) => {
        const { cmsDatas } = res.datas
        const cmsList = await getDataCms(cmsDatas[0]?.datas)
        const list = cmsList[0]?.datas
        workRightList.value = changeQuestionData(list)
        rightLoading.value = false
        if (workRightList.value?.length > 0) {
          const ids = workRightList.value.map((item: any) => item.id)
          checkAll.value = getInclude3(checkData.value, ids)
        }
      })
      .catch((err: any) => {
        rightLoading.value = false
        workRightList.value = []
        console.log(err)
      })
  }
}
// // 获取教材题目章节
// const getChapter = async () => {
//   const url = config.questionUrl + classInfo?.bookRefCode + '/information.json'
//   const { data } = await axios.get(url)
//   let dataList: any = []
//   if (data?.data?.length > 0) {
//     dataList = data.data.map((item: any) => {
//       return {
//         ...item,
//         name: item.label
//       }
//     })
//   }
//   workLeftList.value = dataList?.filter((item: any) => item.page != '')
// }
// // 获取章节题目列表
// const getChapterQeustion = async (item: any) => {
//   // 题库题目类型
//   const questionTypeList = [
//     { name: '单选题', totalScore: 0, value: 'singleChoice', data: [] },
//     { name: '多选题', totalScore: 0, value: 'multipleChoice', data: [] },
//     { name: '判断题', totalScore: 0, value: 'judge', data: [] },
//     { name: '简答题', totalScore: 0, value: 'shortAnswer', data: [] },
//     { name: '论述题', totalScore: 0, value: 'discuss', data: [] },
//     { name: '填空题', totalScore: 0, value: 'completion', data: [] },
//     { name: '连线题', totalScore: 0, value: 'matching', data: [] },
//     { name: '分类题', totalScore: 0, value: 'classification', data: [] }
//   ]
//   try {
//     let list: any = []
//     const { data } = await axios.get(
//       config.questionUrl + classInfo?.bookRefCode + '/question-' + item.chapter + '.json'
//     )
//     list = data.data.map((item: any) => {
//       if (item.type == 'material') {
//         item.questionType = 'shortAnswer'
//       }
//       return {
//         ...item,
//         analysisCon: item.Embedded_QuestionBank_AnalysisCon,
//         questionAnswer: item.answer,
//         questionStem: item.stem,
//         questionOption: item.option,
//         customAnswer: null
//       }
//     })
//     console.log(list, 'list')
//     list.forEach((item: any) => {
//       const index = findIndexByValue(questionTypeList, item.questionType)
//       if (index > -1) {
//         questionTypeList[index].data.push(item)
//         if (item.score) {
//           questionTypeList[index].totalScore += item.score
//         }
//       }
//     })
//     console.log(questionTypeList)
//   } catch (error) {
//     console.log(error)
//   }
// }
// 数组中是否包含另一个数组
const getInclude3 = (arr1: any[], arr2: any[]) => {
  let temp = []
  temp = arr1.filter((item) => arr2.indexOf(item.id) > -1)
  return temp.length ? true : false
}
// 处理题目数据结构
const changeQuestionData = (res: any) => {
  // 题库题目类型
  const questionTypeList = [
    { name: '单选题', totalScore: 0, value: 'singleChoice', data: [] },
    { name: '多选题', totalScore: 0, value: 'multipleChoice', data: [] },
    { name: '判断题', totalScore: 0, value: 'judge', data: [] },
    { name: '简答题', totalScore: 0, value: 'shortAnswer', data: [] },
    { name: '论述题', totalScore: 0, value: 'discuss', data: [] },
    { name: '填空题', totalScore: 0, value: 'completion', data: [] },
    { name: '连线题', totalScore: 0, value: 'matching', data: [] },
    { name: '分类题', totalScore: 0, value: 'classification', data: [] }
  ]
  try {
    let list = []
    list = res?.map((item: any) => {
      try {
        if (item.Embedded_QuestionBank_Stem) {
          item.questionStem = JSON.parse(item.Embedded_QuestionBank_Stem)
        }
        if (
          item.Embedded_QuestionBank_Option &&
          item.Embedded_QuestionBank_Option.indexOf('[') > -1
        ) {
          item.questionOption = JSON.parse(item.Embedded_QuestionBank_Option)
        }
        if (
          item.Embedded_QuestionBank_Answer &&
          item.Embedded_QuestionBank_Answer.indexOf('[') > -1
        ) {
          item.Embedded_QuestionBank_Answer = JSON.parse(item.Embedded_QuestionBank_Answer)
        }
        const checkObj = checkData.value.find((citem: any) => citem.id == item.id)
        item.isCheck = checkObj ? true : false
        return {
          ...item,
          questionType: item.Embedded_QuestionBank_QuestionType,
          questionAnalysisCon: item.Embedded_QuestionBank_AnalysisCon,
          questionAnswer: item.Embedded_QuestionBank_Answer,
          customAnswer: null
        }
      } catch (error) {
        console.log(item)
      }
    })
    list.forEach((item: any) => {
      const index = findIndexByValue(questionTypeList, item.questionType)
      if (index > -1) {
        questionTypeList[index].data.push(item)
        if (item.score) {
          questionTypeList[index].totalScore += item.score
        }
      }
    })
    return questionTypeList.filter((item) => item.data.length > 0)
  } catch (error) {
    console.log(error)
    return []
  }
}
const findIndexByValue = (res: any, type: string) => {
  for (let i = 0; i < res.length; i++) {
    if (res[i].value == type) {
      return i
    }
  }
  return -1 // 如果未找到,则返回 -1
}
// 合并更新数组
const updateArr = (arr: any, brr: any) => {
  // 使用 Map 来存储合并后的结果
  const mergedMap = new Map()
  // 将第一个数组的对象加入到 Map 中
  arr.forEach((item: any) => {
    mergedMap.set(item.id, { ...item })
  })
  // 遍历第二个数组,更新 Map 中相同 id 的对象的 score
  brr.forEach((item: any) => {
    if (mergedMap.has(item.id)) {
      mergedMap.get(item.id).score = item.score
    } else {
      mergedMap.set(item.id, { ...item })
    }
  })
  // 将 Map 转换为数组
  const mergedArray = Array.from(mergedMap.values())
  return mergedArray
}
</script>
<style lang="less" scoped>
.classManagePage-box {
  padding: 20px;
  .classManagePage-nav {
    padding-bottom: 20px;
    border-bottom: 1px solid #e6e8ed;
  }
  .classManagePage-content {
    .cartClass {
      .titleOptions {
        width: 160px;
        display: flex;
        justify-content: space-between;
        align-items: center;
        span {
          height: 30px;
          font-weight: bold;
          font-size: 16px;
          color: #333;
          line-height: 30px;
          text-align: left;
          border-left: 6px solid #ff6c00;
          padding-left: 10px;
        }
      }
      margin-top: 20px;
      ::v-deep .el-tabs__nav-wrap::after {
        background-color: #ff6d00;
        height: 1px;
      }
      ::v-deep .el-tabs__item {
        width: 100px;
        padding: 0;
        color: #545c63;
      }
      ::v-deep .is-active {
        background-color: #ff6d00;
        color: #fff;
        border-radius: 3px 3px 0 0;
      }
    }
    .headerBox {
      padding: 20px 0;
      overflow: hidden;
      .searchBox {
        width: 300px;
        float: left;
        .searchBtn {
          background-color: var(--el-color-primary);
          color: #fff;
          border-top-left-radius: 0;
          border-bottom-left-radius: 0;
        }
      }
    }
    ::v-deep(.customDialog) {
      border-radius: 5px;
      .stepBox {
        padding: 15px 0;
      }
      padding-bottom: 20px;
      .el-dialog__body {
        padding: 0 20px;
      }
      .el-dialog__footer {
        width: calc(100% - 40px);
        margin: auto;
        display: flex;
        padding: 0;
        .leftFooter {
          height: 55px;
          width: 286px;
          border: 1px solid #f4f4f4;
          background: #e0f2ff;
          margin-right: 20px;
        }
        .dialog-footer {
          flex: 1;
          display: flex;
          align-items: center;
          justify-content: flex-start;
          border: 1px solid #f4f4f4;
          border-top: 0;
          padding: 0 10px;
          .check-list-box {
            width: 220px;
            display: flex;
            justify-content: flex-start;
          }
          .btnGroup {
            flex: 1;
            display: flex;
            align-items: center;
            justify-content: flex-end;
          }
        }
        .selectedFooter {
          width: 100%;
          display: flex;
          justify-content: flex-end;
          padding: 10px 0;
        }
      }
      .viewTitle {
        width: 100%;
        text-align: center;
        font-size: 16px;
      }
    }
    .workTitle {
      margin: 20px 0;
      font-size: 16px;
      color: #333;
    }
    .viewContent {
      width: 100%;
      .right-list-components {
        height: calc(100% - 5px) !important;
      }
    }
    .workBox {
      width: 100%;
      display: flex;
      .workLeft {
        width: 286px;
        height: 700px;
        border-radius: 10px 10px 0px 0px;
        border: 1px solid #f4f4f4;
        border-bottom: 0;
        .scoreTitle {
          display: flex;
          align-items: center;
          justify-content: flex-start;
          line-height: 39px;
          padding-left: 10px;
          box-sizing: border-box;
        }
        ::v-deep(.demo-tabs) {
          .el-tabs__header {
            margin: 0;
          }
          .el-tabs__nav {
            width: 100%;
            justify-content: center;
            padding: 8px 0;
            .el-tabs__item {
              flex: 1;
              height: 0px;
              padding: 12px 0;
            }
            .el-tabs__item {
              border-right: 1px solid #eee;
            }
            .el-tabs__item:last-child {
              border: 0;
            }
            .el-tabs__active-bar {
              display: none;
              // width: 60px !important;
              // left: 40px !important;
            }
          }
          .el-tabs__nav-wrap::after {
            height: 0px !important;
            background: none;
          }
        }
        .workList {
          width: 100%;
          height: calc(100% - 39px);
          background: #e0f2ff;
          overflow: auto;
          padding: 10px 18px;
          .searchBox {
            position: sticky;
            top: -10px;
            z-index: 9999999;
          }
          .work-list-item {
            background: #ffffff;
            border-radius: 5px 5px 5px 5px;
            margin: 10px 0;
            .work-list-item-text {
              font-family: PingFang SC;
              font-weight: 400;
              font-size: 13px;
              color: #000000;
              line-height: 22px;
              max-width: 248px;
              white-space: nowrap;
              text-overflow: ellipsis;
              overflow: hidden;
              padding: 6px 10px;
            }
            .work-list-item-text:hover {
              cursor: pointer;
            }
          }
        }
      }
      .workRight {
        flex: 1;
        height: 700px;
        margin-left: 20px;
        border-radius: 10px 10px 0px 0px;
        border: 1px solid #f4f4f4;
        .right-list-title {
          display: flex;
          justify-content: space-between;
          align-items: center;
          width: 100%;
          height: 39px;
          border-bottom: 1px solid #f4f4f4;
          line-height: 39px;
          padding: 0 10px;
        }
        .right-list-components {
          width: 100%;
          height: calc(100% - 40px);
          display: flex;
          align-items: center;
          justify-content: center;
        }
        .right-score-components {
          padding: 10px;
          .scoreBtn {
            margin-bottom: 20px;
            display: flex;
            justify-content: flex-start;
            align-items: center;
            // .setScore {
            //   margin-left: 20px;
            // }
          }
        }
      }
      .finishBg {
        width: 100%;
        height: 100%;
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        img {
          width: 300px;
          margin-bottom: 20px;
        }
        span {
          font-family: PingFang SC;
          font-weight: 500;
          font-size: 18px;
          color: #999999;
        }
      }
    }
    .newTaskBox {
      width: 750px;
      height: 700px;
      font-family: PingFang SC;
      font-weight: 400;
      font-size: 14px;
      color: #333333;
      margin: auto;
      padding-top: 60px;
      display: flex;
      flex-direction: column;
      box-sizing: border-box;
      .el-row {
        margin-bottom: 20px;
        display: flex;
        align-items: flex-start;
        .labelItem {
          text-align: right;
          margin-right: 10px;
          padding-top: 2px;
        }
        .selectBox {
          border: 1px solid #ddd;
          border-radius: 5px;
          padding: 20px 30px;
        }
        .selectMarBot {
          margin-bottom: 10px;
        }
        .btngroup {
          position: absolute;
          top: 20px;
          right: 27px;
        }
        .el-upload-list__item-name {
          line-height: 22px;
        }
      }
      .row-center {
        align-items: center;
      }
    }
    .leftBg {
      background-color: rgba(255, 173, 65, 0.1);
      border-radius: 5px 5px 5px 5px;
      color: #ff6c00 !important;
    }
    .pageBox {
      padding: 10px 0;
      display: flex;
      justify-content: flex-end;
    }
    .pubContent {
      display: flex;
      justify-content: center;
      align-items: center;
      padding: 20px 0;
    }
  }
}
</style>
src/views/classManage/prepareLessons.vue
New file
@@ -0,0 +1,22 @@
<template>
  <div class="classManagePage-box">
    <div class="classManagePage-nav">我的班级>{{ classInfo.name }}>备课</div>
    <div class="classManagePage-content"></div>
  </div>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router'
const route = useRoute()
const classInfo = JSON.parse(route.query.classInfo)
</script>
<style lang="less" scoped>
.classManagePage-box {
  padding: 20px;
  .classManagePage-nav {
    padding-bottom: 20px;
    border-bottom: 1px solid #e6e8ed;
  }
}
</style>
src/views/classManage/studentJob.vue
New file
@@ -0,0 +1,357 @@
<template>
  <div class="classManagePage-box">
    <div class="classManagePage-nav">
      <el-breadcrumb :separator-icon="ArrowRight">
        <el-breadcrumb-item>我的班级</el-breadcrumb-item>
        <el-breadcrumb-item>{{ classInfo?.name }}</el-breadcrumb-item>
        <el-breadcrumb-item>作业管理</el-breadcrumb-item>
      </el-breadcrumb>
    </div>
    <div class="classManagePage-content">
      <div class="cartClass">
        <!-- <el-tabs v-model="jobState" @tab-click="tabCart">
          <el-tab-pane label="全部" name="all"></el-tab-pane>
          <el-tab-pane label="进行中" name="payment"></el-tab-pane>
          <el-tab-pane label="已过期" name="complete"></el-tab-pane>
        </el-tabs> -->
        <div class="titleOptions">
          <span>作业管理</span>
        </div>
      </div>
      <div class="headerBox">
        <div class="searchBox">
          <el-input
            v-model="searchKey"
            clearable
            @clear="searchTask"
            @keydown.enter="searchTask"
            placeholder="请输入关键字"
          >
            <template #append>
              <el-button type="primary" @click="searchTask" class="searchBtn" :icon="Search" />
            </template>
          </el-input>
        </div>
      </div>
      <div class="listBox">
        <el-table
          :data="tableData"
          :header-cell-style="{ background: '#eee' }"
          max-height="600px"
          style="width: 100%"
          v-loading="pages.loading"
        >
          <el-table-column label="序号" prop="id" width="70"> </el-table-column>
          <el-table-column label="名称" prop="name"> </el-table-column>
          <el-table-column label="作业完成情况" #default="scope">
            <span v-if="scope.row.submitState == 'WaitCheck'" style="color: #ff6d00">待批改</span>
            <span v-if="scope.row.submitState == 'Normal'" style="color: #67c23a">已批改</span>
            <span v-if="!scope.row.submitState" style="color: red">未提交</span>
          </el-table-column>
          <!-- <el-table-column label="状态" prop="state" #default="scope">
            <span v-if="scope.row.state == 'Waiting'" style="color: red">未开始</span>
            <span v-if="scope.row.state == 'Normal'" style="color: #67c23a">进行中</span>
            <span v-if="scope.row.state == 'Ended'" style="color: #999">已结束</span>
          </el-table-column> -->
          <el-table-column label="作业开始日期" prop="beginDate"> </el-table-column>
          <el-table-column label="作业结束日期" prop="endDate"> </el-table-column>
          <el-table-column label="主观题得分" prop="judgeScore"> </el-table-column>
          <el-table-column label="客观题得分" prop="otherScore"> </el-table-column>
          <el-table-column label="总分" prop="totalScore"> </el-table-column>
          <el-table-column label="操作" width="200" #default="scope">
            <el-button link style="color: #409eff" @click="preview(scope.row)">预览</el-button>
            <el-button link v-if="!scope.row.submitState" type="primary" @click="answer(scope.row)">
              答题
            </el-button>
          </el-table-column>
        </el-table>
        <div class="pageBox">
          <el-pagination
            v-model:current-page="pages.currentPage"
            :page-size="pages.pageSize"
            :size="'small'"
            :disabled="pages.count <= 1"
            layout="total, prev, pager, next"
            :total="pages.count"
            @size-change="handleSizeChange"
            @current-change="handleCurrentChange"
          />
        </div>
      </div>
    </div>
  </div>
</template>
<script setup lang="ts">
import { reactive, ref, onMounted, inject, watch } from 'vue'
import { Search, ArrowRight } from '@element-plus/icons-vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import moment from 'moment'
const route: any = useRoute()
const router = useRouter()
const MG: any = inject('MG')
const config: any = inject('config')
const tool: any = inject('toolClass')
const classInfo = JSON.parse(route.query.classInfo)
const jobState = ref('all')
const searchKey = ref('')
const dataList = ref([])
const userInfo = ref()
// 获取task参数
const filterData = ref([
  {
    value: config.taskType.homeWork,
    field: 'Type',
    subFilters: []
  }
])
// task
const taskData = reactive({
  id: '',
  description: '',
  rootCmsItemId: '',
  name: '',
  type: '',
  state: '',
  groupId: '',
  beginDate: '',
  endDate: ''
})
const visiblePub = ref(false)
const questionData: any = ref([])
const scoreData: any = ref([])
// question Key
const questionKey = [
  'Name',
  'Embedded_QuestionBank_AnalysisCon',
  'Embedded_QuestionBank_Answer',
  'Embedded_QuestionBank_Difficulty',
  'Embedded_QuestionBank_KnowledgePoint',
  'Embedded_QuestionBank_Option',
  'Embedded_QuestionBank_OptionStyle',
  'Embedded_QuestionBank_QuestionType',
  'Embedded_QuestionBank_Score',
  'Embedded_QuestionBank_Stem',
  'Embedded_QuestionBank_StemStyle'
]
let pages = reactive({
  currentPage: 1,
  page: 1,
  pageSize: 15,
  count: 0,
  loading: false
})
// 作业列表
const tableData: any = ref([])
onMounted(() => {
  const userCache: any = localStorage.getItem('jesk-userInfo')
  if (userCache) {
    userInfo.value = JSON.parse(userCache)
  }
  getTaskList()
})
//  分页
const handleSizeChange = (val: number) => {
  pages.pageSize = val
  getTaskList()
}
const handleCurrentChange = (val: number) => {
  pages.page = val
  pages.currentPage = val
  getTaskList()
}
// 获取任务列表
const getTaskList = (filter?: any[], search?: any[]) => {
  const filterList = filter ?? filterData.value
  const searchList = search ?? []
  pages.loading = true
  const data = {
    start: (pages.page - 1) * pages.pageSize,
    size: pages.pageSize,
    sort: {
      type: 'Desc',
      field: 'CreateDate'
    },
    filterList,
    searchList,
    groupId: classInfo?.id
  }
  MG.edu
    .getTaskList(data)
    .then((res: any) => {
      pages.count = res.totalSize
      if (res.datas.length > 0) {
        tableData.value = res.datas?.map((item: any) => {
          item.judgeScore = 0
          item.otherScore = 0
          if (item.submit?.id) {
            item.submit?.submitAndCmsItemLinks?.forEach((citem: any) => {
              if (citem.comments == 'judge') {
                item.judgeScore += citem.score
              }
              if (citem.comments != 'judge') {
                item.otherScore += citem.score
              }
            })
          }
          return {
            ...item,
            totalScore: item.judgeScore + item.otherScore,
            beginDate: moment(item.beginDate).format('YYYY-MM-DD'),
            endDate: moment(item.endDate).format('YYYY-MM-DD')
          }
        })
      } else {
        tableData.value = []
      }
      pages.loading = false
    })
    .catch((e: any) => {
      pages.loading = false
      console.log(e)
    })
}
// 搜索任务
const searchTask = () => {
  const data = [
    {
      compareType: 'Contains',
      keywords: searchKey.value,
      field: 'Name'
    }
  ]
  pages.page = 1
  pages.currentPage = 1
  getTaskList(undefined, data)
}
// 表格tab事件{Waiting:未开始,Normal:进行中,Ended:已结束}
const tabCart = (event: Event | any) => {
  let queryFilter: any = []
  jobState.value = event.props.name
  pages.page = 1
  dataList.value = []
  if (jobState.value == 'all') {
    queryFilter = [...filterData.value]
  }
  if (jobState.value == 'payment') {
    queryFilter = [...filterData.value, { field: 'State', value: 'Normal', subFilters: [] }]
  }
  if (jobState.value == 'complete') {
    queryFilter = [...filterData.value, { field: 'State', value: 'Ended', subFilters: [] }]
  }
  getTaskList(queryFilter)
}
// 答题
const answer = (item: any) => {
  router.push({
    path: '/bookService/details/answer',
    query: {
      answerTitle: item.name,
      taskId: item.id,
      groupId: classInfo?.id,
      answerType: 'task'
    }
  })
}
// 预览
const preview = (item: any) => {
  router.push({
    path: '/bookService/details/answer',
    query: {
      answerTitle: item.name,
      taskId: item.id,
      groupId: classInfo?.id,
      answerType: 'task',
      isPreview: 'true'
    }
  })
}
</script>
<style lang="less" scoped>
.classManagePage-box {
  padding: 20px;
  .classManagePage-nav {
    padding-bottom: 20px;
    border-bottom: 1px solid #e6e8ed;
  }
  .classManagePage-content {
    .cartClass {
      .titleOptions {
        width: 160px;
        display: flex;
        justify-content: space-between;
        align-items: center;
        span {
          height: 30px;
          font-weight: bold;
          font-size: 16px;
          color: #333;
          line-height: 30px;
          text-align: left;
          border-left: 6px solid #ff6c00;
          padding-left: 10px;
        }
      }
      margin-top: 20px;
      ::v-deep .el-tabs__nav-wrap::after {
        background-color: #ff6d00;
        height: 1px;
      }
      ::v-deep .el-tabs__item {
        width: 100px;
        padding: 0;
        color: #545c63;
      }
      ::v-deep .is-active {
        background-color: #ff6d00;
        color: #fff;
        border-radius: 3px 3px 0 0;
      }
    }
    .headerBox {
      padding: 20px 0;
      overflow: hidden;
      .searchBox {
        width: 300px;
        float: left;
        .searchBtn {
          background-color: var(--el-color-primary);
          color: #fff;
          border-top-left-radius: 0;
          border-bottom-left-radius: 0;
        }
      }
    }
    ::v-deep(.customDialog) {
      border-radius: 5px;
      margin: 10px auto;
      padding-bottom: 20px;
      .pubContent {
        height: 800px;
      }
    }
    .pageBox {
      padding: 10px 0;
      display: flex;
      justify-content: flex-end;
    }
  }
}
</style>
src/views/classManage/studentManage.vue
New file
@@ -0,0 +1,475 @@
<template>
  <div class="classManagePage-box">
    <div class="classManagePage-nav">
      <el-breadcrumb :separator-icon="ArrowRight">
        <el-breadcrumb-item>我的班级</el-breadcrumb-item>
        <el-breadcrumb-item>{{ classInfo?.name }}</el-breadcrumb-item>
        <el-breadcrumb-item>班级管理</el-breadcrumb-item>
      </el-breadcrumb>
    </div>
    <div class="classManagePage-content">
      <div class="headerBox">
        <div class="btnBox">
          <!-- 批量通过按钮 -->
          <el-button
            v-if="multipleSelection.length > 0"
            type="success"
            size="small"
            @click="updateStateNormalDatas"
            >批量通过</el-button
          >
          <!-- 批量移除按钮 -->
          <el-button
            v-if="multipleSelection.length > 0"
            type="danger"
            size="small"
            @click="removeStudentDatas"
            >批量移除</el-button
          >
          <!-- 批量拒绝 -->
          <el-button
            v-if="multipleSelection.length > 0"
            type="warning"
            size="small"
            @click="updateStateRejectDatas"
            >批量拒绝</el-button
          >
        </div>
        <!-- <div class="searchBox">
          <el-input v-model="searchKey" clearable @clear="searchData()" placeholder="请输入关键字">
            <template #append>
              <el-button type="primary" @click="searchData()" class="searchBtn" :icon="Search" />
            </template>
          </el-input>
        </div> -->
      </div>
      <div class="listBox">
        <el-table
          :header-cell-style="{ background: '#eee' }"
          :data="dataList"
          max-height="600px"
          style="width: 100%"
          v-loading="pages.loading"
          @selection-change="handleSelectionChange"
        >
          <el-table-column type="selection" :selectable="selectable" width="55" />
          <el-table-column prop="index" label="序号" width="70" />
          <el-table-column label="姓名" width="400">
            <template #default="scope">
              <div class="userBox">
                <el-avatar
                  style="margin-right: 10px"
                  v-if="scope.row.appUser.icon"
                  :size="35"
                  :src="scope.row.appUser.icon"
                />
                <el-avatar
                  style="margin-right: 10px"
                  :size="35"
                  v-if="!scope.row.appUser.icon"
                  :icon="UserFilled"
                />
                <span v-if="scope.row.appUser.name">{{ scope.row.appUser.name }}</span>
              </div>
            </template>
          </el-table-column>
          <el-table-column prop="createDate" label="加入班级时间" />
          <el-table-column label="状态">
            <template #default="scope">
              <span v-if="scope.row.state == 'WaitValid'" style="color: #ef9f29"> 审核中 </span>
              <span v-if="scope.row.state == 'Normal'" style="color: #1dbd11"> 已通过 </span>
              <span v-if="scope.row.state == 'Reject'" style="color: #fc425d"> 未通过 </span>
            </template>
          </el-table-column>
          <el-table-column label="班级成员身份">
            <template #default="scope">
              <span v-if="scope.row.linkType == 'RefCode'"> 学生 </span>
              <span v-if="scope.row.linkType == 'Teacher'"> 助教 </span>
              <span v-if="scope.row.linkType == 'Creator'"> 创建人 </span>
            </template>
          </el-table-column>
          <el-table-column label="操作" width="200">
            <template #default="scope">
              <el-button
                link
                v-if="scope.row.linkType != 'Creator' && scope.row.state == 'Normal'"
                type="danger"
                size="small"
                @click="removeStudent(scope.row)"
              >
                移除
              </el-button>
              <el-button link v-if="scope.row.linkType == 'Creator'" type="info" size="small">
                ---
              </el-button>
              <el-button
                link
                v-if="scope.row.state != 'Normal'"
                type="success"
                size="small"
                @click="selectIdentity(scope.row)"
              >
                通过
              </el-button>
              <el-button
                link
                v-if="scope.row.state != 'Normal' && scope.row.state != 'Reject'"
                type="warning"
                size="small"
                @click="updateStateReject(scope.row)"
              >
                拒绝
              </el-button>
            </template>
          </el-table-column>
        </el-table>
        <el-pagination
          style="float: right"
          v-model:current-page="pages.page"
          :page-size="pages.pageSize"
          layout="total, prev, pager, next"
          :total="pages.count"
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
        />
        <el-dialog v-model="visible" title="选择当前成员身份" width="500" align-center>
          <el-radio-group v-model="currentLinkType">
            <el-radio label="RefCode" size="large" border>学生</el-radio>
            <el-radio label="Teacher" size="large" border>助教</el-radio>
          </el-radio-group>
          <template #footer>
            <div class="dialog-footer">
              <el-button @click="cancleLinkType"> 取消 </el-button>
              <el-button type="primary" @click="updateStateNormal"> 确认 </el-button>
            </div>
          </template>
        </el-dialog>
        <el-dialog v-model="visibleReject" title="拒绝原因" width="500" align-center>
          <div class="reasonStr">
            <span style="width: 100px">拒绝原因:</span>
            <el-input type="textarea" :rows="2" maxlength="100" v-model="reasonStr"></el-input>
          </div>
          <template #footer>
            <div class="dialog-footer">
              <el-button @click="cancleReject"> 取消 </el-button>
              <el-button type="primary" @click="updateStateReject"> 确认 </el-button>
            </div>
          </template>
        </el-dialog>
      </div>
    </div>
  </div>
</template>
<script setup lang="ts">
import { reactive, ref, onMounted, inject, watch } from 'vue'
import type { TableInstance } from 'element-plus'
import { useRoute } from 'vue-router'
import { Search, ArrowRight, UserFilled } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getPublicImage } from '@/assets/js/middleGround/tool.js'
import moment from 'moment'
const route: any = useRoute()
const MG: any = inject('MG')
const classInfo = JSON.parse(route.query.classInfo)
const searchKey = ref('')
const reasonStr = ref('')
const dataList = ref([])
const visible = ref(false)
const currentIdentity = ref()
const currentLinkType = ref('RefCode')
const visibleReject = ref(false)
const rejectData: any = ref()
let pages = reactive({
  page: 1,
  pageSize: 10,
  count: 0,
  loading: false
})
const multipleSelection = ref<any[]>([])
const selectable = (row: any) => ![1].includes(row.index)
onMounted(() => {
  getStudentList()
})
const handleSelectionChange = (val: any) => {
  multipleSelection.value = val
}
const handleSizeChange = (val: number) => {
  pages.pageSize = val
  getStudentList()
}
const handleCurrentChange = (val: number) => {
  pages.page = val
  getStudentList()
}
const searchData = () => {
  pages.page = 1
  pages.pageSize = 10
  getStudentList()
}
// 获取学生列表
const getStudentList = () => {
  pages.loading = true
  const data = {
    start: (pages.page - 1) * pages.pageSize,
    size: pages.pageSize,
    groupId: classInfo.id
  }
  MG.identity.getGroupUserList(data).then((res: any) => {
    const { datas } = res
    pages.loading = false
    if (datas.length > 0) {
      dataList.value = datas.map((item: any, index: number) => {
        if (item.linkType == 'Creator') {
          const userInfo = item.appUser?.infoList?.find((citem: any) => citem.type == 'teacherInfo')
          item.appUser.name = userInfo.name
          item.appUser.icon = userInfo.icon
          if (userInfo?.data) {
            const iconData = JSON.parse(userInfo.data)
            item.appUser.icon = getPublicImage(iconData?.relevantCertificates[0]?.md5, 100) ?? ''
          }
        }
        if (item.linkType == 'RefCode' || item.linkType == 'Teacher') {
          let userInfo = null
          const wechatData = item.appUser?.infoList?.find((citem: any) => citem.type == 'WeChat')
          const defaultData = item.appUser?.infoList?.find((citem: any) => citem.type == 'Default')
          userInfo = defaultData
          if (wechatData?.name) {
            userInfo = wechatData
          }
          item.appUser.name = userInfo.name
          item.appUser.icon = userInfo.icon
        }
        return {
          ...item,
          index: index + 1,
          createDate: moment(item.createDate).format('YYYY-MM-DD')
        }
      })
      pages.count = dataList.value.length
    }
  })
}
// 移除学生
const removeStudent = (item: any) => {
  ElMessageBox.confirm('是否移除选中成员?')
    .then(() => {
      const data = {
        groupId: classInfo.id,
        appUserIds: [item.appUser.id]
      }
      MG.identity.removeAppUserFromGroup(data).then((res: any) => {
        if (res) {
          ElMessage({
            message: '已移除',
            type: 'success'
          })
          getStudentList()
        }
      })
    })
    .catch(() => {
      // catch error
    })
}
// 批量移除学生
const removeStudentDatas = () => {
  ElMessageBox.confirm('是否批量移除选中成员?')
    .then(() => {
      const data = {
        groupId: classInfo.id,
        appUserIds: multipleSelection.value.map((item) => item.appUser.id)
      }
      MG.identity.removeAppUserFromGroup(data).then((res: any) => {
        if (res) {
          ElMessage({
            message: '已移除',
            type: 'success'
          })
          getStudentList()
        }
      })
    })
    .catch(() => {
      // catch error
    })
}
const selectIdentity = (item: any) => {
  visible.value = true
  currentIdentity.value = item
}
const cancleLinkType = () => {
  visible.value = false
  currentIdentity.value = null
  currentLinkType.value = 'RefCode'
}
// 更新状态
const updateStateNormal = () => {
  const data = {
    groupId: classInfo.id,
    requests: [
      {
        linkId: currentIdentity.value.linkId,
        linkType: currentLinkType.value,
        state: 'Normal',
        groupState: 'Normal'
      }
    ]
  }
  MG.identity.updateAppUserGroupLink(data).then((res: any) => {
    if (res) {
      visible.value = false
      ElMessage({
        message: '已通过',
        type: 'success'
      })
      getStudentList()
    }
  })
}
// 批量通过
const updateStateNormalDatas = () => {
  const data = {
    groupId: classInfo.id,
    requests: multipleSelection.value.map((item) => {
      return {
        linkId: item.linkId,
        linkType: item.linkType,
        state: 'Normal',
        groupState: 'Normal'
      }
    })
  }
  MG.identity.updateAppUserGroupLink(data).then((res: any) => {
    if (res) {
      ElMessage({
        message: '已通过',
        type: 'success'
      })
      getStudentList()
    }
  })
}
// 批量拒绝
const updateStateRejectDatas = () => {
  const data = {
    groupId: classInfo.id,
    requests:multipleSelection.value.map((item) => {
      return {
        linkId: item.linkId,
        linkType: item.linkType,
        state: 'Reject',
        groupState: 'Reject'
      }
    })
  }
  MG.identity.updateAppUserGroupLink(data).then((res:any) => {
    if (res) {
      ElMessage({
        message: '已拒绝',
        type: 'success'
      })
      getStudentList()
    }
  })
}
const rejectInfo = (item: any) => {
  visibleReject.value = true
  rejectData.value = item
}
const cancleReject = () => {
  visibleReject.value = false
  reasonStr.value = ''
}
// 更新状态 拒绝
const updateStateReject = (item:any) => {
  const data = {
    groupId: classInfo.id,
    requests: [
      {
        linkId: item.linkId,
        linkType: item.linkType,
        state: 'Reject',
        groupState: 'Reject'
      }
    ]
  }
  MG.identity.updateAppUserGroupLink(data).then((res: any) => {
    if (res) {
      ElMessage({
        message: '已拒绝',
        type: 'warning'
      })
      cancleReject()
      getStudentList()
    }
  })
}
</script>
<style lang="less" scoped>
.classManagePage-box {
  padding: 20px;
  .classManagePage-nav {
    padding-bottom: 20px;
    border-bottom: 1px solid #e6e8ed;
  }
  .classManagePage-content {
    .headerBox {
      height: 50px;
      line-height: 50px;
      overflow: hidden;
      display: flex;
      justify-content: space-between;
      align-items: center;
      .btnBox {
        flex: 1;
      }
      .searchBox {
        width: 300px;
        float: right;
        .searchBtn {
          background-color: var(--el-color-primary);
          color: #fff;
          border-top-left-radius: 0;
          border-bottom-left-radius: 0;
        }
      }
    }
    .listBox {
      .el-radio.is-bordered.el-radio--large {
        padding: 0px 100px 0 11px;
      }
      .userBox {
        display: flex;
        align-items: center;
      }
    }
  }
  .reasonStr {
    display: flex;
    justify-content: flex-start;
    align-items: center;
  }
}
</style>
src/views/classManage/talkDetail.vue
New file
@@ -0,0 +1,481 @@
<template>
  <div class="classManagePage-box">
    <div class="classManagePage-nav">
      <el-breadcrumb :separator-icon="ArrowRight">
        <el-breadcrumb-item>我的班级</el-breadcrumb-item>
        <el-breadcrumb-item>{{ classInfo?.name }}</el-breadcrumb-item>
        <el-breadcrumb-item>话题详情</el-breadcrumb-item>
      </el-breadcrumb>
    </div>
    <div class="classManagePage-content">
      <div class="backBtn">
        <el-button @click="goBack()" type="primary" link>
          <el-icon style="margin-right: 5px"><ArrowLeftBold /></el-icon> 返回
        </el-button>
      </div>
      <div class="talkBox">
        <div class="leftBox">
          <div class="MessageCount">{{ dataList.length }} 条回复</div>
          <div class="inputBox">
            <el-input v-model="textarea" :rows="8" type="textarea" placeholder="请输入" />
            <el-button
              class="submit"
              :type="textarea.length > 0 ? 'primary' : 'info'"
              :disabled="textarea.length == 0"
              round
              @click="submitText('1')"
            >
              回 复
            </el-button>
          </div>
          <div v-if="dataList.length > 0 && !childLoading">
            <div class="MessageBox" v-for="(item, index) in dataList" :key="index">
              <div class="MessageHeader">
                <div class="userBox">
                  <el-avatar
                    :src="
                      item.publicText?.icon ??
                      'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png'
                    "
                  />
                  <el-text class="userName mx-1">{{ item.publicText?.publisher }}</el-text>
                  <el-text class="mx-1">{{ item.createDate }}</el-text>
                </div>
                <div class="options">
                  <!-- <el-button type="primary" link @click="openSubmit()"> 回复 </el-button> -->
                  <!-- <el-button
                    type="success"
                    link
                    @click="pubTalk(item)"
                    v-if="userInfo.role == 'Teacher' && item.state == 'WaitAudit'"
                  >
                    发布
                  </el-button> -->
                  <el-button
                    type="danger"
                    v-if="userInfo.role == 'Teacher'"
                    link
                    @click="removeMessageItem(item)"
                  >
                    删除
                  </el-button>
                </div>
                <!-- <el-dialog v-model="dialogVisible" title="回复话题" width="800">
                  <div class="talkWall">
                    <div class="talKBox">
                      <el-input
                        style="flex: 1"
                        v-model="talkContent"
                        :autosize="{ minRows: 10, maxRows: 15 }"
                        type="textarea"
                        placeholder="请输入内容"
                      />
                    </div>
                  </div>
                  <template #footer>
                    <div class="dialog-footer">
                      <el-button @click="dialogVisible = false"> 取消 </el-button>
                      <el-button type="primary" @click="submitText('2')"> 回复 </el-button>
                    </div>
                  </template>
                </el-dialog> -->
              </div>
              <div class="MessageContent" v-html="item.publicText?.content"></div>
            </div>
          </div>
          <div v-if="childLoading" v-loading="childLoading" style="height: 100px"></div>
        </div>
        <div class="rightBox" v-if="ownData && !isLoading">
          <div class="talkHeader">
            <div class="talkUser">
              <el-avatar
                src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
              />
              <el-text class="userName mx-1">{{ ownData?.publicText?.publisher }}</el-text>
            </div>
            <el-text class="mx-1">{{ ownData?.updateDate }}</el-text>
          </div>
          <div class="talkContent">
            <div class="contentTilte">{{ ownData?.name }}</div>
            <div
              v-if="ownData?.publicText"
              class="contentText"
              v-html="ownData?.publicText?.content"
            ></div>
            <div v-else class="contentText" v-html="ownData?.content"></div>
          </div>
        </div>
        <div class="rightBox" v-if="isLoading" v-loading="isLoading"></div>
      </div>
    </div>
  </div>
</template>
<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router'
import { ArrowRight } from '@element-plus/icons-vue'
import { inject, onMounted, ref } from 'vue'
import moment from 'moment'
import { ElMessage } from 'element-plus'
import { getPublicImage } from '@/assets/js/middleGround/tool.js'
const route: any = useRoute()
const router = useRouter()
const classInfo = JSON.parse(route.query.classInfo)
const userInfo = ref()
const textarea = ref('')
const talkContent = ref('')
const dialogVisible = ref(false)
const MG: any = inject('MG')
const config: any = inject('config')
const talkTopicInfo = ref()
const dataList: any = ref([])
const ownData = ref()
const isLoading = ref(false)
const childLoading = ref(false)
onMounted(() => {
  const userCache: any = localStorage.getItem('jesk-userInfo')
  if (userCache) {
    userInfo.value = JSON.parse(userCache)
  }
  getTopicInfo()
})
const openSubmit = () => {
  talkContent.value = ''
  dialogVisible.value = true
}
const submitText = (val: string) => {
  const userCache: any = localStorage.getItem('jesk-userInfo')
  const userInfo = JSON.parse(userCache)
  if (userInfo?.data) {
    const iconData = JSON.parse(userInfo.data)
    userInfo.icon = iconData?.relevantCertificates[0]?.md5 ?? ''
  }
  const textObj = {
    content: val == '1' ? textarea.value : talkContent.value,
    publisher: userInfo?.name ?? '',
    publishRole: userInfo?.role ?? '',
    icon: userInfo.icon ?? '',
    type: userInfo.type ?? ''
  }
  const data = {
    description: '',
    icon: '',
    state: 'Normal',
    parentId: ownData?.value.id,
    topicIdOrRefCode: String(talkTopicInfo.value.id),
    name: userInfo?.name,
    content: JSON.stringify(textObj),
    type: 'Normal',
    cmsTypeRefCode: '',
    newDataListRequest: []
  }
  MG.ugc.newTopicMessage(data).then((res: any) => {
    if (res) {
      ElMessage({
        message: '已回复',
        type: 'success'
      })
      dialogVisible.value = false
      textarea.value = ''
      getMessageChildren()
    }
  })
}
// 获取话题topic
const getTopicInfo = () => {
  const pramas = {
    classId: classInfo.id,
    refCodes: [config.refCodes.talk]
  }
  MG.edu.getClassTopic(pramas).then((res: any) => {
    const list = res
    talkTopicInfo.value = list.find((item: any) => item.refCode == config.refCodes.talk)
    if (talkTopicInfo.value.id) {
      getMessage()
    }
  })
}
// 获取当前话题
const getMessage = () => {
  isLoading.value = true
  const data = {
    start: 0,
    size: 1,
    searchList: [
      {
        keywords: classInfo.MessageName,
        field: 'Name',
        compareType: 'Contains'
      }
    ],
    appRefCode: config.appRefCode,
    topicIdOrRefCode: String(talkTopicInfo.value.id)
  }
  MG.ugc.getTopicMessageList(data).then((res: any) => {
    const list = res.datas
    isLoading.value = false
    list.map((item: any) => {
      const str = item.content.indexOf('publisher')
      if (str > -1) {
        item.publicText = JSON.parse(item.content)
        if (item.publicText && item.publicText.publishRole) {
          item.publicText.publishRole = item.publicText.publishRole == 'Teacher' ? '助教' : '学生'
        }
      }
      item.createDate = moment(item.createDate).format('YYYY-MM-DD HH:mm:ss')
      item.updateDate = moment(item.updateDate).format('YYYY-MM-DD HH:mm:ss')
      return {
        ...item
      }
    })
    ownData.value = list[0]
    getMessageChildren()
  })
}
// 获取当前话题的回复消息
const getMessageChildren = () => {
  childLoading.value = true
  const data = {
    start: 0,
    size: 999,
    parentId: ownData?.value.id,
    topicIdOrRefCode: String(talkTopicInfo.value.id)
  }
  MG.ugc.getTopicMessageSubList(data).then((res: any) => {
    const list = res.datas
    childLoading.value = false
    dataList.value = list.map((item: any) => {
      const str = item.content.indexOf('publisher')
      if (str > -1) {
        item.publicText = JSON.parse(item.content)
        if (item.publicText && item.publicText.publishRole) {
          item.publicText.publishRole = item.publicText.publishRole == 'Teacher' ? '助教' : '学生'
        }
        if (item.publicText?.icon && item.publicText?.type == 'teacherInfo') {
          item.publicText.icon = getPublicImage(item.publicText?.icon, 60)
        }
      }
      item.createDate = moment(item.createDate).format('YYYY-MM-DD HH:mm:ss')
      item.updateDate = moment(item.updateDate).format('YYYY-MM-DD HH:mm:ss')
      return {
        ...item
      }
    })
  })
}
// 删除回复话题
const removeMessageItem = (item: any) => {
  const data = {
    messageIds: [item.id]
  }
  MG.ugc.delTopicMessage(data).then((res: any) => {
    if (res) {
      ElMessage({
        message: '已删除',
        type: 'success'
      })
    }
    getMessage()
  })
}
// 审核话题
const pubTalk = (item: any) => {
  const data = {
    id: item.id,
    name: item.name,
    description: item.description,
    icon: item.icon,
    type: item.type,
    state: 'Normal',
    content: JSON.stringify(item.publicText),
    newDataRequests: [],
    updateDataRequests: []
  }
  MG.ugc
    .updateTopicMessage(data)
    .then((res: any) => {
      if (res) {
        ElMessage({
          type: 'success',
          message: '已发布'
        })
        getMessage()
      }
    })
    .catch((err: any) => {
      console.log(err, '审核话题')
    })
}
const goBack = () => {
  router.go(-1)
}
</script>
<style lang="less" scoped>
.classManagePage-box {
  // padding: 20px 0;
  background: #fff;
  .classManagePage-nav {
    width: 100%;
    padding: 0 20px;
    height: 40px;
    border-bottom: 1px solid #e6e8ed;
    position: sticky;
    top: 0;
    z-index: 999;
    display: flex;
    align-items: center;
    background: #fff;
  }
  .classManagePage-content {
    width: 100%;
    position: relative;
    .backBtn {
      width: 100%;
      height: 45px;
      margin-bottom: 10px;
      padding: 0 20px;
      display: flex;
      align-items: center;
      position: sticky;
      top: 40px;
      z-index: 99;
      background: #fff;
      box-shadow: 0px 0px 20px 1px #eee;
    }
    .talkBox {
      width: 100%;
      display: flex;
      justify-content: space-between;
      align-items: flex-start;
      padding: 0 20px;
      .leftBox {
        flex: 1;
        min-width: 500px;
        min-height: 700px;
        background: #ffffff;
        box-shadow: 0px 0px 10px 1px rgba(0, 0, 0, 0.08);
        border-radius: 5px 5px 5px 5px;
        border: 1px solid #e7eaec;
        margin-right: 20px;
        .MessageCount {
          padding: 0 30px;
          width: 100%;
          height: 60px;
          border-bottom: 1px solid #e7eaec;
          line-height: 60px;
        }
        :deep(.inputBox) {
          padding: 20px 30px;
          position: relative;
          .el-textarea__inner {
            padding: 15px;
            padding-bottom: 45px;
            box-sizing: border-box;
          }
          .submit {
            position: absolute;
            bottom: 30px;
            right: 40px;
          }
        }
        .MessageBox {
          width: 100%;
          padding: 0 30px;
          .MessageHeader {
            width: 100%;
            display: flex;
            justify-content: space-between;
            align-items: center;
            .userBox {
              display: flex;
              align-items: center;
              .userName {
                margin: 0 20px;
              }
            }
          }
          .MessageContent {
            padding: 0 60px;
            padding-bottom: 20px;
            margin-bottom: 10px;
            font-family: PingFang SC;
            font-weight: 400;
            font-size: 13px;
            color: #333333;
            line-height: 25px;
            border-bottom: 1px solid #e7eaec;
          }
        }
      }
      .rightBox {
        width: 650px;
        min-width: 500px;
        height: 650px;
        background: #ffffff;
        box-shadow: 0px 0px 10px 1px rgba(0, 0, 0, 0.08);
        border-radius: 5px 5px 5px 5px;
        border: 1px solid #e7eaec;
        position: sticky;
        top: 95px;
        z-index: 99;
        background: #fff;
        .talkHeader {
          width: 100%;
          padding: 0 30px;
          height: 60px;
          display: flex;
          justify-content: space-between;
          align-items: center;
          border-bottom: 1px solid #e7eaec;
          .talkUser {
            display: flex;
            align-items: center;
            .userName {
              margin-left: 20px;
            }
          }
        }
        .talkContent {
          width: 100%;
          height: calc(100% - 60px);
          overflow: auto;
          padding: 20px 30px;
          .contentTilte {
            font-family: PingFang SC;
            font-weight: bold;
            font-size: 16px;
            color: #333333;
            margin-bottom: 20px;
          }
          .contentText {
            font-family: PingFang SC;
            font-weight: 400;
            font-size: 13px;
            color: #333333;
            line-height: 30px;
            text-align: left;
            word-break: break-all;
            word-wrap: break-word;
          }
        }
      }
    }
  }
}
</style>
src/views/classManage/talkingPoint.vue
New file
@@ -0,0 +1,375 @@
<template>
  <div class="classManagePage-box">
    <div class="classManagePage-nav">
      <el-breadcrumb :separator-icon="ArrowRight">
        <el-breadcrumb-item>我的班级</el-breadcrumb-item>
        <el-breadcrumb-item>{{ classInfo?.name }}</el-breadcrumb-item>
        <el-breadcrumb-item>话题</el-breadcrumb-item>
      </el-breadcrumb>
    </div>
    <div class="classManagePage-content">
      <div class="headerBox">
        <div class="searchBox">
          <el-input
            v-model="searchKey"
            @clear="searchData()"
            @keydown.enter="searchData()"
            clearable
            placeholder="搜索话题名称"
          >
            <template #append>
              <el-button type="primary" class="searchBtn" @click="searchData()" :icon="Search" />
            </template>
          </el-input>
        </div>
        <el-button @click="openTalk()" v-if="userInfo?.role == 'Teacher'" type="primary" round>
          新建 <el-icon><Plus /></el-icon>
        </el-button>
      </div>
      <div class="listBox">
        <el-table
          :header-cell-style="{ background: '#eee' }"
          :data="dataList"
          border
          v-loading="isLoading"
          style="width: 100%"
        >
          <el-table-column prop="index" label="序号" width="70" />
          <el-table-column label="话题名称" width="500">
            <template #default="scope">
              <span style="color: #ff6c00" v-if="scope.row.name">{{ scope.row.name }}</span>
            </template>
          </el-table-column>
          <el-table-column prop="createDate" label="发起日期" />
          <el-table-column label="发起人">
            <template #default="scope">
              <span v-if="scope.row.publicText">{{ scope.row.publicText.publisher }}</span>
            </template>
          </el-table-column>
          <el-table-column label="发起人角色">
            <template #default="scope">
              <span v-if="scope.row.publicText?.publishRole == 'Teacher'">助教</span>
              <span v-else>学生</span>
            </template>
          </el-table-column>
          <el-table-column prop="updateDate" label="发起人最后回复日期" />
          <el-table-column label="操作" width="180px">
            <template #default="scope">
              <el-button
                link
                type="primary"
                v-if="scope.row.state == 'Normal'"
                @click="toDetail(scope.row)"
                >详情</el-button
              >
              <el-button
                link
                type="success"
                v-if="scope.row.state != 'Normal'"
                @click="pubTalk(scope.row)"
                >发布</el-button
              >
              <el-button
                link
                type="danger"
                v-if="userInfo.role == 'Teacher'"
                @click="delMessageItem(scope.row)"
                >删除</el-button
              >
            </template>
          </el-table-column>
        </el-table>
        <el-pagination
          style="float: right"
          v-model:current-page="pages.page"
          :page-size="pages.pageSize"
          layout="total, prev, pager, next"
          :total="pages.count"
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
        />
      </div>
      <el-dialog v-model="dialogVisible" title="新建话题" width="800">
        <div class="talkWall">
          <div class="talKBox">
            <span>话题标题:</span>
            <el-input v-model="talkTitle" style="width: 440px" placeholder="请输入标题" />
          </div>
          <div class="talKBox">
            <span>话题内容:</span>
            <el-input
              style="flex: 1"
              v-model="talkContent"
              :autosize="{ minRows: 10, maxRows: 15 }"
              type="textarea"
              placeholder="请输入内容"
            />
          </div>
        </div>
        <template #footer>
          <div class="dialog-footer">
            <el-button @click="dialogVisible = false"> 取消 </el-button>
            <el-button type="primary" @click="createMessage()"> 确认 </el-button>
          </div>
        </template>
      </el-dialog>
    </div>
  </div>
</template>
<script setup lang="ts">
import { reactive, ref, onMounted, inject, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { Search, ArrowRight } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import moment from 'moment'
const route: any = useRoute()
const router = useRouter()
const MG: any = inject('MG')
const config: any = inject('config')
const classInfo = JSON.parse(route.query.classInfo)
const userInfo = ref()
const searchKey = ref('')
const talkTitle = ref('')
const talkContent = ref('')
const dialogVisible = ref(false)
const talkTopicInfo = ref()
const dataList = ref([])
const isLoading = ref(false)
let pages = reactive({
  page: 1,
  pageSize: 10,
  count: 0,
  loading: false
})
onMounted(() => {
  const userCache: any = localStorage.getItem('jesk-userInfo')
  if (userCache) {
    userInfo.value = JSON.parse(userCache)
  }
  getTopicInfo()
})
const handleSizeChange = (val: number) => {
  pages.pageSize = val
  getMessage()
}
const handleCurrentChange = (val: number) => {
  pages.page = val
  getMessage()
}
const searchData = () => {
  pages.page = 1
  pages.pageSize = 10
  getMessage()
}
// 获取话题topic
const getTopicInfo = () => {
  const pramas = {
    classId: classInfo.id,
    refCodes: [config.refCodes.talk]
  }
  MG.edu.getClassTopic(pramas).then((res: any) => {
    const list = res
    talkTopicInfo.value = list.find((item: any) => item.refCode == config.refCodes.talk)
    if (talkTopicInfo.value.id) {
      getMessage()
    }
  })
}
//  新建话题
const createMessage = () => {
  if (talkTitle.value == '') {
    ElMessage({
      message: '请填写标题',
      type: 'warning'
    })
    return false
  }
  if (talkContent.value == '') {
    ElMessage({
      message: '请填写内容',
      type: 'warning'
    })
    return false
  }
  newTalkMessage()
}
// 话题详情
const toDetail = (item: any) => {
  const obj = classInfo
  obj.MessageName = item.name
  router.push({
    path: '/talkDetail',
    query: {
      classInfo: JSON.stringify(obj)
    }
  })
}
const newTalkMessage = () => {
  const userCache: any = localStorage.getItem('jesk-userInfo')
  const userInfo = JSON.parse(userCache)
  const textObj = {
    content: talkContent.value,
    publisher: userInfo?.name ?? '',
    publishRole: userInfo?.role ?? '',
    icon: userInfo.icon ?? ''
  }
  const data = {
    description: '',
    icon: '',
    state: 'Normal',
    topicIdOrRefCode: String(talkTopicInfo.value.id),
    name: talkTitle.value,
    content: JSON.stringify(textObj),
    type: 'Normal',
    cmsTypeRefCode: '',
    newDataListRequest: []
  }
  MG.ugc.newTopicMessage(data).then((res: any) => {
    if (res) {
      dialogVisible.value = false
      getMessage()
    }
  })
}
// 获取班级话题
const getMessage = () => {
  isLoading.value = true
  const data = {
    start: (pages.page - 1) * pages.pageSize,
    size: pages.pageSize,
    appRefCode: config.appRefCode,
    topicIdOrRefCode: String(talkTopicInfo.value.id),
    searchList: searchKey.value
      ? [
          {
            keywords: searchKey.value,
            field: 'Name',
            compareType: 'Contains'
          }
        ]
      : []
  }
  MG.ugc.getTopicMessageList(data).then((res: any) => {
    const list = res.datas
    pages.count = res.totalSize
    isLoading.value = false
    dataList.value = list.map((item: any, i: number) => {
      const str = item.content.indexOf('publisher')
      if (str > -1) {
        item.publicText = JSON.parse(item.content)
      }
      return {
        ...item,
        index: i + 1,
        createDate: moment(item.createDate).format('YYYY-MM-DD HH:mm:ss'),
        updateDate: moment(item.updateDate).format('YYYY-MM-DD HH:mm:ss')
      }
    })
  })
}
// 删除话题
const delMessageItem = (item: any) => {
  const data = {
    messageIds: [item.id]
  }
  MG.ugc
    .delTopicMessage(data)
    .then((res: any) => {
      if (res) {
        ElMessage.success('已删除')
        getTopicInfo()
      }
    })
    .catch((err: any) => {
      ElMessage.error('删除失败,请稍后再试')
      console.log(err)
    })
}
// 审核话题
const pubTalk = (item: any) => {
  const data = {
    id: item.id,
    name: item.name,
    description: item.description,
    icon: item.icon,
    type: item.type,
    state: 'Normal',
    content: JSON.stringify(item.publicText),
    newDataRequests: [],
    updateDataRequests: []
  }
  MG.ugc
    .updateTopicMessage(data)
    .then((res: any) => {
      if (res) {
        ElMessage({
          type: 'success',
          message: '已发布'
        })
        getMessage()
      }
    })
    .catch((err: any) => {
      console.log(err, '审核话题')
    })
}
const openTalk = () => {
  dialogVisible.value = true
}
</script>
<style lang="less" scoped>
.classManagePage-box {
  padding: 20px;
  .classManagePage-nav {
    padding-bottom: 20px;
    border-bottom: 1px solid #e6e8ed;
  }
  .classManagePage-content {
    .headerBox {
      padding: 20px 0;
      overflow: hidden;
      display: flex;
      align-items: center;
      justify-content: space-between;
      .searchBox {
        width: 300px;
        float: left;
        .searchBtn {
          background-color: var(--el-color-primary);
          color: #fff;
          border-top-left-radius: 0;
          border-bottom-left-radius: 0;
        }
      }
    }
  }
}
.headerCellClass {
  background-color: red !important;
}
.talkWall {
  width: 100%;
  height: auto;
  .talKBox {
    display: flex;
    align-items: baseline;
    margin-bottom: 20px;
  }
}
</style>
src/views/classManage/teachInteraction.vue
New file
@@ -0,0 +1,512 @@
<template>
  <div class="classManagePage-box">
    <div class="classManagePage-nav">
      <el-breadcrumb :separator-icon="ArrowRight">
        <el-breadcrumb-item>我的班级</el-breadcrumb-item>
        <el-breadcrumb-item>{{ classInfo?.name }}</el-breadcrumb-item>
        <el-breadcrumb-item>教学互动</el-breadcrumb-item>
      </el-breadcrumb>
    </div>
    <div class="classManagePage-content">
      <div class="teachPlaneBox">
        <div class="titleBox">
          <div class="titleOptions">
            <span>教学互动</span>
          </div>
          <div class="searchBox">
            <el-input
              v-model="searchKey"
              clearable
              @clear="searchData()"
              placeholder="请输入关键字"
              @keydown.enter="searchData()"
            >
              <template #append>
                <el-button type="primary" @click="searchData()" class="searchBtn" :icon="Search" />
              </template>
            </el-input>
          </div>
        </div>
      </div>
      <div class="listBox">
        <el-table
          :header-cell-style="{ background: '#eee' }"
          :data="cmsDataList"
          max-height="700px"
          style="width: 100%"
          v-loading="cmsLoading"
        >
          <el-table-column prop="id" label="序号" width="100" />
          <el-table-column prop="name" label="标题" width="500" />
          <el-table-column prop="address" label="已互动学生数" #default="scoped">
            <div>
              <span style="color: #ff6d00">{{ scoped.row.subList.length }}</span>
            </div>
          </el-table-column>
          <el-table-column prop="address" label="最后提交时间" #default="scoped">
            <span v-if="scoped.row.updateDate"> {{ scoped.row.updateDate }} </span>
            <span v-else> - </span>
          </el-table-column>
          <el-table-column prop="address" label="操作" #default="scoped">
            <el-button link type="success" @click="toDetail(scoped.row)">查看详情</el-button>
            <el-button link style="color: #409eff" @click="getQuestions(scoped.row)"
              >浏览答题</el-button
            >
          </el-table-column>
        </el-table>
        <!-- 浏览答题 -->
        <el-dialog
          class="customDialog"
          title="浏览答题"
          v-model="visible"
          destroy-on-close
          width="1000"
          align-center
          :before-close="close"
        >
          <div class="pubContent" v-if="dialogList.length > 0 && !dialogLLoading">
            <div v-for="(item, index) in dialogList" :key="index">
              <span class="userName">答题人:{{ item.userName ?? '-' }}</span>
              <question-dom
                v-if="item.questionTypeList.length > 0"
                :questionList="item.questionTypeList"
                :noCheckbox="false"
                :is-preview="true"
                :is-interaction="true"
              />
            </div>
          </div>
          <div class="pubContent" v-if="dialogLLoading" v-loading="dialogLLoading"></div>
          <div class="pubContent noData" v-if="dialogList.length == 0 && !dialogLLoading">
            <el-empty></el-empty>
          </div>
          <template #footer>
            <div class="selectedFooter" style="padding: 0">
              <el-button type="primary" @click="visible = false"> 关闭 </el-button>
            </div>
          </template>
        </el-dialog>
      </div>
    </div>
  </div>
</template>
<script setup lang="ts">
import { Search, ArrowRight } from '@element-plus/icons-vue'
import moment from 'moment'
import { ref, onMounted, inject } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import questionDom from './components/questionDom.vue'
const route: any = useRoute()
const router: any = useRouter()
const MG: any = inject('MG')
const config: any = inject('config')
const classInfo = JSON.parse(route.query.classInfo)
const userInfo = ref()
const teachInteractionInfo: any = ref([])
const searchKey = ref('')
const dataList: any = ref([])
const currentClass = ref()
const cmsDataList: any = ref([])
const cmsLoading = ref(true)
const visible = ref(false)
const dialogList: any = ref([])
const dialogLLoading = ref(true)
const defaultCmsPath = ref('')
onMounted(() => {
  const userCache: any = localStorage.getItem('jesk-userInfo')
  if (userCache) {
    userInfo.value = JSON.parse(userCache)
  }
  defaultCmsPath.value = classInfo.bookRefCode ? 'jsek_digitalTextbooks' : config.goodsStore
  getData()
})
const searchData = () => {
  const data = [...cmsDataList.value]
  if (!searchKey.value) {
    getCmsList()
  } else {
    const list = data.filter((item) => item.name.indexOf(searchKey.value) > -1)
    cmsDataList.value = list
  }
}
const close = () => {
  visible.value = false
}
// 获取教学互动
const getCmsList = () => {
  cmsLoading.value = true
  cmsDataList.value = []
  try {
    MG.store
      .getProductDetail({
        path: defaultCmsPath.value,
        queryType: '*',
        productId: classInfo.bookId,
        storeInfo: defaultCmsPath.value,
        cmsPath: classInfo.rootCmsItemId
      })
      .then((res: any) => {
        try {
          const data = res.datas?.cmsDatas[0]?.datas.find(
            (item: any) => item.refCode == 'questionBank'
          )
          MG.store
            .getProductDetail({
              path: defaultCmsPath.value,
              queryType: '*',
              storeInfo: defaultCmsPath.value,
              productId: classInfo.bookId,
              cmsPath: data.productLinkPath
            })
            .then((res: any) => {
              try {
                const dataTeach = res.datas.cmsDatas[0]?.datas.find(
                  (item: any) => item.refCode == 'jsek_interaction'
                )
                MG.store
                  .getProductDetail({
                    path: defaultCmsPath.value,
                    queryType: '*',
                    productId: classInfo.bookId,
                    storeInfo: defaultCmsPath.value,
                    cmsPath: dataTeach.productLinkPath
                  })
                  .then((res: any) => {
                    let datas = res.datas.cmsDatas[0] ? res.datas.cmsDatas[0].datas : []
                    if (datas?.length > 0) {
                      datas.forEach((item: any) => {
                        MG.store
                          .getProductDetail({
                            path: defaultCmsPath.value,
                            queryType: '*',
                            productId: classInfo.bookId,
                            storeInfo: defaultCmsPath.value,
                            cmsPath: item.productLinkPath,
                            cmsSort: {
                              ProductLinkOrder: 'Desc'
                            }
                          })
                          .then(async (cmsRes: any) => {
                            cmsLoading.value = false
                            if (
                              cmsRes.datas.cmsDatas[0].datas &&
                              cmsRes.datas.cmsDatas[0].datas.length > 0
                            ) {
                              cmsRes.datas.cmsDatas[0].datas.forEach((item: any) => {
                                item.subList = []
                                if (dataList.value.length > 0) {
                                  dataList.value.forEach((mitem: any) => {
                                    if (mitem.name == item.name) {
                                      item.updateDate = moment(dataList.value[0].updateDate).format(
                                        'YYYY-MM-DD HH:mm:ss'
                                      )
                                      item.subList.push(mitem)
                                    }
                                  })
                                }
                                cmsDataList.value.push(item)
                              })
                            }
                          })
                      })
                    }
                  })
              } catch (error) {
                cmsLoading.value = false
                cmsDataList.value = []
              }
            })
        } catch (error) {
          cmsLoading.value = false
          cmsDataList.value = []
        }
      })
  } catch (error) {
    cmsLoading.value = false
    cmsDataList.value = []
  }
}
// 获取Messsagetopic
const getTopicInfo = () => {
  const pramas = {
    classId: classInfo.id,
    refCodes: [config.refCodes.teachInteraction]
  }
  MG.edu.getClassTopic(pramas).then((res: any) => {
    const list = res
    teachInteractionInfo.value = list.find(
      (item: any) => item.refCode == config.refCodes.teachInteraction
    )
    if (teachInteractionInfo.value.id) {
      getMessage()
    }
  })
}
// 获取当前MessageList
const getMessage = () => {
  const data = {
    start: 0,
    size: 999,
    appRefCode: config.appRefCode,
    topicIdOrRefCode: String(teachInteractionInfo.value.id),
    sort: {
      type: 'Desc',
      field: 'CreateDate'
    },
    searchList: searchKey.value
      ? [
          {
            keywords: searchKey.value,
            field: 'Name',
            compareType: 'Contains'
          }
        ]
      : []
  }
  MG.ugc.getTopicMessageList(data).then((res: any) => {
    dataList.value = res.datas.map((item: any) => {
      item.question = []
      item.bookId = null
      item.path = ''
      try {
        const obj = JSON.parse(item.content)
        if (obj.bookId) {
          item.question = obj.content.map((citem: any) => {
            return {
              ...citem,
              updateDate: moment(item.updateDate).format('YYYY-MM-DD HH:mm:ss'),
              userId: item.appUserCreator.userId
            }
          })
          item.bookId = obj.bookId
          item.path = obj.path
          item.userName = obj.userName
        }
      } catch (error) {
        console.log(item)
      }
      return {
        ...item
      }
    })
    console.log(dataList.value, 'datal')
    getCmsList()
  })
}
// 获取班级
const getData = () => {
  MG.edu
    .getCourseClass({
      ClassIdOrRefCode: String(classInfo.id)
    })
    .then((res: any) => {
      if (res) {
        currentClass.value = res
      }
      getTopicInfo()
    })
}
// 获取题目列表
const getQuestions = (pitem: any) => {
  visible.value = true
  dialogLLoading.value = true
  MG.store
    .getProductDetail({
      path: '*',
      queryType: '*',
      productId: classInfo.bookId,
      cmsPath: pitem.productLinkPath,
      itemFields: {
        Embedded_QuestionBank_AnalysisCon: [],
        Embedded_QuestionBank_Answer: [],
        Embedded_QuestionBank_Difficulty: [],
        Embedded_QuestionBank_KnowledgePoint: [],
        Embedded_QuestionBank_Option: [],
        Embedded_QuestionBank_OptionStyle: [],
        Embedded_QuestionBank_QuestionType: [],
        Embedded_QuestionBank_Score: [],
        Embedded_QuestionBank_Stem: [],
        Embedded_QuestionBank_StemStyle: []
      }
    })
    .then((res: any) => {
      try {
        let list = []
        list = res.datas.cmsDatas[0]?.datas.map((item: any) => {
          try {
            if (item.Embedded_QuestionBank_Stem) {
              item.questionStem = JSON.parse(item.Embedded_QuestionBank_Stem)
            }
            if (
              item.Embedded_QuestionBank_Option &&
              item.Embedded_QuestionBank_Option.indexOf('[') > -1
            ) {
              item.questionOption = JSON.parse(item.Embedded_QuestionBank_Option)
            }
            if (
              item.Embedded_QuestionBank_Answer &&
              item.Embedded_QuestionBank_Answer.indexOf('[') > -1
            ) {
              item.Embedded_QuestionBank_Answer = JSON.parse(item.Embedded_QuestionBank_Answer)
            }
            return {
              ...item,
              questionType: item.Embedded_QuestionBank_QuestionType,
              questionAnalysisCon: item.Embedded_QuestionBank_AnalysisCon,
              questionAnswer: item.Embedded_QuestionBank_Answer,
              customAnswer: null,
              updateDate: pitem.updateDate
            }
          } catch (error) {
            console.log(item)
          }
        })
        dialogList.value = chageData(pitem.subList, list)
        dialogLLoading.value = false
      } catch (error) {
        dialogList.value = []
        dialogLLoading.value = false
      }
    })
}
// 处理数据结构
const chageData = (arr: any, zrr: any) => {
  // 题库题目类型
  const questionTypeList = [
    { name: '单选题', value: 'singleChoice', data: [] },
    { name: '多选题', value: 'multipleChoice', data: [] },
    { name: '判断题', value: 'judge', data: [] },
    { name: '简答题', value: 'shortAnswer', data: [] },
    { name: '论述题', value: 'discuss', data: [] },
    { name: '填空题', value: 'completion', data: [] },
    { name: '连线题', value: 'matching', data: [] },
    { name: '分类题', value: 'classification', data: [] }
  ]
  for (let i = 0; i < arr.length; i++) {
    const item = arr[i]
    item.questionTypeList = JSON.parse(JSON.stringify(questionTypeList))
    item.other = []
    for (let j = 0; j < item.question.length; j++) {
      const ele = item.question[j]
      const data = zrr.find((sitem: any) => sitem.id == ele.cmsItemId)
      const index = findIndexById(zrr, ele.cmsItemId)
      if (index > -1) {
        item.other[index] = { ...data, userAnswer: ele.answer }
      }
    }
    if (item.other.length > 0) {
      item.other.forEach((aitem: any) => {
        const index = findIndexByValue(item.questionTypeList, aitem.questionType)
        if (index > -1) {
          item.questionTypeList[index].data.push(aitem)
        }
      })
    }
    item.questionTypeList = item.questionTypeList.filter((xitem: any) => xitem.data.length > 0)
  }
  return arr
}
const findIndexByValue = (res: any, type: string) => {
  for (let i = 0; i < res.length; i++) {
    if (res[i].value == type) {
      return i
    }
  }
  return -1 // 如果未找到,则返回 -1
}
const findIndexById = (res: any, id: string) => {
  for (let i = 0; i < res.length; i++) {
    if (res[i].id == id) {
      return i
    }
  }
  return -1 // 如果未找到,则返回 -1
}
// 去详情
const toDetail = (item: any) => {
  const obj = classInfo
  obj.questionName = item.name
  obj.teachInteractionId = teachInteractionInfo.value.id
  router.push({
    path: '/interactionDetail',
    query: {
      classInfo: JSON.stringify(obj)
    }
  })
}
</script>
<style lang="less" scoped>
.classManagePage-box {
  padding: 20px;
  .classManagePage-nav {
    padding-bottom: 20px;
    border-bottom: 1px solid #e6e8ed;
    margin-bottom: 20px;
  }
  .classManagePage-content {
    width: 100%;
    .teachPlaneBox {
      width: 100%;
      .titleBox {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 20px;
        .titleOptions {
          width: 160px;
          display: flex;
          justify-content: space-between;
          align-items: center;
          span {
            height: 30px;
            font-family: PingFang SC;
            font-weight: bold;
            font-size: 16px;
            color: #333;
            line-height: 30px;
            text-align: left;
            border-left: 6px solid #ff6c00;
            padding-left: 10px;
          }
        }
        .searchBox {
          width: 300px;
          float: left;
          .searchBtn {
            background-color: var(--el-color-primary);
            color: #fff;
            border-top-left-radius: 0;
            border-bottom-left-radius: 0;
          }
        }
      }
    }
    .pubContent {
      height: 80vh;
      padding: 10px;
      box-sizing: border-box;
      overflow-y: auto;
      .userName {
        color: #ff6d00;
      }
    }
  }
}
</style>
src/views/classManage/teachingPlan.vue
New file
@@ -0,0 +1,855 @@
<template>
  <div class="classManagePage-box">
    <div class="classManagePage-nav">
      <el-breadcrumb :separator-icon="ArrowRight">
        <el-breadcrumb-item>我的班级</el-breadcrumb-item>
        <el-breadcrumb-item>{{ classInfo?.name }}</el-breadcrumb-item>
        <el-breadcrumb-item>教学计划</el-breadcrumb-item>
      </el-breadcrumb>
    </div>
    <div class="classManagePage-content">
      <div class="teachPlaneBox">
        <div class="titleBox">
          <div class="titleOptions">
            <span>教学计划</span>
            <el-button v-if="userInfo?.role == 'Teacher'" @click="openPlan()" type="primary" round
              >新建 <el-icon style="margin-left: 3px"><Plus /></el-icon
            ></el-button>
          </div>
          <div class="searchBox">
            <el-input
              v-model="searchKey"
              clearable
              @clear="searchData()"
              placeholder="请输入关键字"
              @keydown.enter="searchData()"
            >
              <template #append>
                <el-button type="primary" @click="searchData()" class="searchBtn" :icon="Search" />
              </template>
            </el-input>
          </div>
        </div>
        <div class="listBox">
          <el-table
            :header-cell-style="{ background: '#eee' }"
            :data="tableData"
            max-height="600px"
            style="width: 100%"
            v-loading="pages.loading"
          >
            <el-table-column label="序号" width="70">
              <template #default="scope">
                <span v-if="scope.row.datas.index">{{ scope.row.datas.index }}</span>
              </template>
            </el-table-column>
            <el-table-column label="名称" prop="name">
              <template #default="scope">
                <span v-if="scope.row.datas.Name">{{ scope.row.datas.Name }}</span>
              </template>
            </el-table-column>
            <el-table-column label="学习章节" width="300">
              <template #default="scope">
                <div v-if="scope.row.datas.selectChapter?.length > 0">
                  <div
                    @click="toRead(item)"
                    class="linkTitle"
                    v-for="(item, index) in scope.row.datas.selectChapter"
                    :key="index"
                  >
                    {{ item.parentName }}
                  </div>
                </div>
              </template>
            </el-table-column>
            <el-table-column label="上传资源">
              <template #default="scope">
                <div v-if="scope.row.datas.uploadResources?.length > 0">
                  <span
                    @click="downloadRes(item)"
                    class="linkTitle"
                    v-for="(item, index) in scope.row.datas.uploadResources"
                    :key="index"
                  >
                    {{ item.FileName + '.' + item.Extension }}
                  </span>
                </div>
              </template>
            </el-table-column>
            <el-table-column label="参考资料" width="300">
              <template #default="scope">
                <span v-if="scope.row.datas.referenceMaterial">{{
                  scope.row.datas.referenceMaterial
                }}</span>
              </template>
            </el-table-column>
            <el-table-column label="说明" width="300">
              <template #default="scope">
                <span v-if="scope.row.datas.explain">{{ scope.row.datas.explain }}</span>
              </template>
            </el-table-column>
            <el-table-column v-if="userInfo?.role == 'Teacher'" label="操作" width="150">
              <template #default="scope">
                <!-- <el-button
                  v-if="scope.row"
                  @click="editOpen(scope.row)"
                  link
                  type="primary"
                  size="small"
                >
                  编辑
                </el-button> -->
                <el-button
                  v-if="scope.row"
                  @click="removeTaskItem(scope.row)"
                  link
                  type="danger"
                  size="small"
                >
                  移除
                </el-button>
              </template>
            </el-table-column>
          </el-table>
          <el-pagination
            style="float: right"
            v-model:current-page="pages.page"
            :page-size="pages.pageSize"
            layout="total, prev, pager, next"
            :total="pages.count"
            @size-change="handleSizeChange"
            @current-change="handleCurrentChange"
          />
        </div>
      </div>
      <el-dialog v-model="visible" destroy-on-close title="新建教学计划" width="1000">
        <div class="formBox">
          <el-row>
            <el-col :span="2" class="labelItem">
              <div class="grid-content ep-bg-purple" />
              名称<span style="color: red">*</span>
            </el-col>
            <el-col :span="20">
              <div class="grid-content ep-bg-purple-light" />
              <el-input v-model="taskItem.title" placeholder="请填写名称" size="large" clearable />
            </el-col>
          </el-row>
          <el-row>
            <el-col :span="2" class="labelItem">
              <div class="grid-content ep-bg-purple" />
              选择章节<span style="color: red">*</span>
            </el-col>
            <el-col :span="20" style="position: relative" v-if="classInfo?.bookRefCode">
              <div class="grid-content ep-bg-purple-light" />
              <div
                :class="dynamicList.length > 1 ? 'selectBox selectMarBot' : 'selectBox'"
                v-for="(item, index) in dynamicList"
                :key="index"
              >
                <el-cascader
                  size="large"
                  style="width: 620px; margin-right: 10px"
                  :options="chapterList"
                  clearable
                  @change="parentSelect($event, item)"
                >
                  <template #default="{ node, data }">
                    <span>{{ data.label }}</span>
                    <span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
                  </template>
                </el-cascader>
              </div>
              <div class="btngroup">
                <el-button type="primary" @click="addGroup" style="height: 40px" :icon="Plus" />
                <el-button
                  type="warning"
                  :disabled="dynamicList.length == 1"
                  @click="reduceGroup"
                  style="height: 40px"
                  :icon="Minus"
                />
              </div>
            </el-col>
            <el-col :span="20" style="position: relative" v-else>
              <div class="grid-content ep-bg-purple-light" />
              <div
                :class="dynamicList.length > 1 ? 'selectBox selectMarBot' : 'selectBox'"
                v-for="(item, index) in dynamicList"
                :key="index"
              >
                <div class="inputBox">
                  <el-input
                    type="text"
                    placeholder="请输入章"
                    style="width: 300px; margin-right: 15px"
                    v-model="item.parentVal"
                  ></el-input>
                  <el-input
                    type="text"
                    placeholder="请输入节"
                    style="width: 300px"
                    v-model="item.childVal"
                  ></el-input>
                </div>
                <div class="btngroup">
                  <el-button type="primary" @click="addGroup" style="height: 40px" :icon="Plus" />
                  <el-button
                    type="warning"
                    :disabled="dynamicList.length == 1"
                    @click="reduceGroup"
                    style="height: 40px"
                    :icon="Minus"
                  />
                </div>
              </div>
            </el-col>
          </el-row>
          <el-row>
            <el-col :span="2" class="labelItem">
              <div class="grid-content ep-bg-purple" />
              上传资源<span style="color: red">*</span>
            </el-col>
            <el-col :span="20">
              <div class="grid-content ep-bg-purple-light" />
              <!-- <el-upload
                class="upload"
                :http-request="fileUpload"
                :show-file-list="false"
                :action="'#'"
              >
                <el-button type="primary" size="large"
                  ><el-icon size="large" style="margin-right: 5px"><DocumentAdd /></el-icon
                  >上传</el-button
                >
              </el-upload> -->
              <el-upload class="upload" drag action="#" multiple :http-request="fileUpload">
                <el-icon class="el-icon--upload"><upload-filled /></el-icon>
                <div class="el-upload__text">拖拽或点击文件上传</div>
              </el-upload>
            </el-col>
          </el-row>
          <el-row>
            <el-col :span="2" class="labelItem">
              <div class="grid-content ep-bg-purple" />
              参考资料
            </el-col>
            <el-col :span="20">
              <div class="grid-content ep-bg-purple-light" />
              <el-input
                placeholder="请填写参考资料"
                type="textarea"
                :rows="8"
                size="large"
                clearable
                v-model="taskItem.referenceMaterial"
              />
            </el-col>
          </el-row>
          <el-row>
            <el-col :span="2" class="labelItem">
              <div class="grid-content ep-bg-purple" />
              说明
            </el-col>
            <el-col :span="20">
              <div class="grid-content ep-bg-purple-light" />
              <el-input
                v-model="taskItem.explain"
                placeholder="请填写说明"
                type="textarea"
                :rows="8"
                size="large"
                clearable
              />
            </el-col>
          </el-row>
        </div>
        <template #footer>
          <div class="dialog-footer">
            <el-button type="primary" :loading="newLoading" @click="newTaskCmsItems">
              确认
            </el-button>
          </div>
        </template>
      </el-dialog>
    </div>
  </div>
</template>
<script setup lang="ts">
import { Search, ArrowRight, Plus, Minus } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { reactive, ref, onMounted, inject, watch } from 'vue'
import { useRoute } from 'vue-router'
import axios from 'axios'
const route: any = useRoute()
const MG: any = inject('MG')
const config: any = inject('config')
const tool: any = inject('toolClass')
const classInfo = JSON.parse(route.query.classInfo)
const userInfo = ref()
const searchKey = ref('')
const visible = ref(false)
const dynamicList: any = ref([])
const tableData: any = ref([])
const classItem = ref([])
const fileList: any = ref([])
const chapterList: any = ref([])
const childrenList: any = ref([])
const taskData: any = ref()
// 新建教学计划防抖
const newLoading = ref(false)
const teachPlanvalue = ref('')
const taskItem = reactive({
  title: '',
  selectChapter: '',
  referenceMaterial: '',
  explain: '',
  uploadResources: ''
})
let pages = reactive({
  page: 1,
  pageSize: 13,
  count: 0,
  loading: false
})
onMounted(() => {
  const list = [{ key: 1, parentVal: '', childVal: '' }]
  dynamicList.value = list
  const userCache: any = localStorage.getItem('jesk-userInfo')
  if (userCache) {
    userInfo.value = JSON.parse(userCache)
  }
  pages.loading = true
  getTaskList()
})
const searchData = () => {
  pages.page = 1
  getTaskCmsList()
}
const openPlan = () => {
  visible.value = true
  taskItem.title = ''
  taskItem.selectChapter = ''
  taskItem.referenceMaterial = ''
  taskItem.explain = ''
  taskItem.uploadResources = ''
  dynamicList.value = [{ key: 1, parentVal: '', childVal: '' }]
  fileList.value = []
}
const reduceGroup = () => {
  dynamicList.value.pop()
}
const addGroup = () => {
  const source = dynamicList.value[dynamicList.value.length - 1]
  const obj = { key: source.key++, parentVal: '', childVal: '' }
  dynamicList.value.push(obj)
}
const handleSizeChange = (val: number) => {
  pages.pageSize = val
  getTaskCmsList()
}
const handleCurrentChange = (val: number) => {
  pages.page = val
  getTaskCmsList()
}
// 新建任务
const newTask = () => {
  const data = {
    name: classInfo?.name + '教学计划',
    description: '',
    icon: '',
    type: config.taskType.teachingPlan,
    state: 'Normal',
    groupId: classInfo?.id,
    order: 0,
    beginDate: '2024-09-09T03:38:07.167Z',
    endDate: '2024-09-09T03:38:07.167Z',
    duration: 0
  }
  MG.edu
    .newTask(data)
    .then((res: any) => {})
    .catch((e: any) => {
      console.log(e)
    })
}
// 获取任务列表
const getTaskList = () => {
  const data = {
    start: 0,
    size: 10,
    filterList: [
      {
        value: config.taskType.teachingPlan,
        field: 'Type',
        subFilters: []
      }
    ],
    searchList: [],
    groupId: classInfo?.id
  }
  MG.edu
    .getTaskList(data)
    .then((res: any) => {
      if (res.datas.length == 0 && userInfo.value.role == 'Teacher') {
        newTask()
      }
      if (res.datas.length > 0) {
        taskData.value = res.datas[0]
        classInfo.taskId = taskData.value?.id
        classInfo.rootTaskCmsId = taskData.value?.rootCmsItemId
      }
      getTypeByCode()
      if (classInfo.bookRefCode) {
        getCatalogueList()
      }
    })
    .catch((e: any) => {
      console.log(e)
    })
}
// 前往阅读器
const toRead = (item: any) => {
  // config.textReaderUrl
  // 'http://192.168.3.132:8005/#/home'
  if (classInfo.bookRefCode) {
    const url =
      config.textReaderUrl +
      '?bookId=' +
      classInfo?.bookRefCode +
      '&token=' +
      localStorage.getItem('jsek-token') +
      '&chapter=' +
      item.parentVal +
      '&startPage=' +
      item.childVal
    window.open(url, '_blank')
  } else {
    ElMessage.warning('当前章节无法跳转')
  }
}
// 下载上传资源
const downloadRes = (item: any) => {
  const url = config.requestCtx + '/file/api/ApiDownload?md5=' + item.Md5
  window.open(url, '_blank')
}
// 选择器
const parentSelect = (val: any, item: any) => {
  if (val?.length) {
    const str = val[2]
    const obj = findObj(chapterList.value, str)
    item.parentVal = obj.chapter
    item.childVal = obj.start
  }
}
// 文件上传
const fileUpload = (file: any) => {
  return new Promise((resolve, reject) => {
    // const isJPG = file.file.type === 'image/jpeg' || file.file.type === 'image/png'
    // const isLt2M = (0.3 * file.file.size) / 1024 / 1024 < 0.3
    // if (!isJPG) {
    //   ElMessage.error('上传文件只能是 jpg/png 格式!')
    //   return reject()
    // }
    // if (!isLt2M) {
    //   ElMessage.error('上传文件大小不能超过 300KB!')
    //   return reject()
    // }
    const FileName = file.file.name.split('.')[0]
    const Extension = file.file.name.split('.')[1]
    const FileType = file.file.type
    let size = 1024
    tool
      .getFileMd5(file.file, size * 1024)
      .then((e: string) => {
        if (!fileList.value.find((item: any) => item.md5 == e)) {
          const imgData = new FormData()
          imgData.append('Md5', e)
          imgData.append('FileName', FileName)
          imgData.append('Extension', Extension)
          imgData.append('FileType', FileType)
          imgData.append('MetaData', null)
          imgData.append('file', file.file)
          MG.file.upload(imgData).then(() => {
            fileList.value.push({
              md5: e,
              linkType: 'LinkFile',
              linkProtectType: 'Public',
              fileName: FileName,
              extension: Extension,
              url: config.requestCtx + `​/file​/api​/ApiDownload?md5=` + e
            })
          })
        } else {
          ElMessage.warning('当前文件已上传,请勿重复操作!')
        }
      })
      .catch((e: any) => {
        console.error(e)
      })
  })
}
// 获取章节目录
const getCatalogueList = () => {
  const url = config.requestCtx + '/books/resource/' + classInfo?.bookRefCode + '/information.json'
  axios
    .get(url)
    .then((res) => {
      if (res.data?.data.length > 0) {
        const datas = res.data.data
        const list = datas?.filter((item: any) => item.children?.length > 0)
        chapterList.value = changeCascaderData(list)
      }
    })
    .catch((e) => {
      console.log(e)
    })
}
// 递归更数组
const changeCascaderData = (data: any) => {
  for (const item of data) {
    item.value = item.start
    if (item.children?.length > 0) {
      changeCascaderData(item.children)
    }
  }
  return data
}
// 递归查找
const findObj = (arr: any, val: any) => {
  for (let i = 0; i < arr.length; i++) {
    const obj = arr[i]
    if (obj.start === val) {
      return obj // 找到目标对象,直接返回
    }
    if (obj.children && obj.children.length > 0) {
      // 如果当前对象有子对象,递归查找子对象
      const found: any = findObj(obj.children, val)
      if (found) {
        return found // 在子对象中找到目标对象,返回
      }
    }
  }
  return null
}
// 获取类型字段
const getTypeByCode = () => {
  MG.resource
    .getCmsTypeByRefCode({
      refCodes: [config.refCodes.teachingPlan]
    })
    .then((res: any) => {
      const data = res[0]?.cmsTypeLinks[0]?.children
      if (data?.length) {
        classItem.value = data
      }
      getTaskCmsList()
    })
    .catch((err: any) => {
      console.log(err)
    })
}
// 获取任务下的资源列表
const getTaskCmsList = () => {
  const data = {
    start: (pages.page - 1) * pages.pageSize,
    size: pages.pageSize,
    searchList: searchKey.value
      ? [
          {
            keywords: searchKey.value,
            field: 'Name',
            compareType: 'Contains'
          }
        ]
      : [],
    taskId: classInfo?.taskId,
    path: String(classInfo?.rootTaskCmsId),
    type: '*',
    keys: ['referenceMaterial', 'Name', 'selectChapter', 'uploadResources', 'explain']
  }
  MG.edu
    .getTaskCmsItem(data)
    .then((res: any) => {
      pages.loading = false
      pages.count = res.totalSize
      for (let i = 0; i < res.datas.length; i++) {
        const item = res.datas[i]
        // 处理字段
        if (taskItem != null) {
          for (let fieldKey in taskItem) {
            if (item.datas[fieldKey]) {
              const values = JSON.parse(item.datas[fieldKey])
              if (values.length > 0) {
                // 用字段名处理返回的字段值
                if (values[0].Value) {
                  item.datas[fieldKey] = values[0].Value
                } else if (values[0].Data) {
                  item.datas[fieldKey] = values[0].Data.Value
                } else if (!values[0].Value && values[0].FileList?.length > 0) {
                  item.datas[fieldKey] = values[0].FileList
                } else {
                  item.datas[fieldKey] = '-'
                }
                if (fieldKey == 'selectChapter' && values[0].Value) {
                  const data = JSON.parse(values[0].Value)
                  item.datas['selectChapter'] = data.map((citem: any) => {
                    if (classInfo.bookRefCode) {
                      const dataS = findObj(chapterList.value, citem.childVal)
                      return {
                        ...citem,
                        parentName:
                          chapterList.value.find((sitem: any) => sitem.chapter == citem.parentVal)
                            ?.label +
                          '---' +
                          dataS?.label
                      }
                    } else {
                      return {
                        ...citem,
                        parentName: citem.parentVal + '---' + citem.childVal
                      }
                    }
                  })
                }
                if (fieldKey == 'selectChapter' && !values[0].Value) {
                  item.datas['selectChapter'] = []
                }
              }
              const index = i
              item.datas['index'] = index + 1
            }
          }
        }
      }
      tableData.value = res.datas
    })
    .catch((e: any) => {
      ElMessage({
        message: '列表获取失败',
        type: 'error'
      })
      console.log(e)
    })
}
// 删除资源
const removeTaskItem = (item: any) => {
  const data = {
    taskId: classInfo?.taskId,
    requests: [
      {
        cmsItemId: item.id,
        path: String(classInfo?.rootTaskCmsId)
      }
    ]
  }
  MG.edu
    .removeTaskCmsItemList(data)
    .then((res: any) => {
      if (res) {
        ElMessage({
          message: '删除成功',
          type: 'success'
        })
        getTaskCmsList()
      }
    })
    .catch((e: any) => {
      ElMessage({
        message: '删除失败',
        type: 'error'
      })
    })
}
// 为任务新建资源
const newTaskCmsItems = () => {
  newLoading.value = true
  taskItem.selectChapter = JSON.stringify(dynamicList.value)
  if (!taskItem.title) {
    ElMessage({
      message: '请填写教学名称',
      type: 'warning'
    })
    newLoading.value = false
    return false
  }
  if (dynamicList.value[0]?.parentVal == '') {
    ElMessage({
      message: '请选择教学章节',
      type: 'warning'
    })
    newLoading.value = false
    return false
  }
  if (fileList.value.length == 0) {
    ElMessage({
      message: '请上传教学文件',
      type: 'warning'
    })
    newLoading.value = false
    return false
  }
  const data = {
    groupId: classInfo?.id,
    taskId: classInfo?.taskId,
    accessPath: String(classInfo?.rootTaskCmsId),
    newGroupCmsItemRequests: [
      {
        name: taskItem.title,
        description: '',
        icon: '',
        type: config.refCodes.teachingPlan,
        state: 'Normal',
        order: 0,
        newDataListRequest: tool.worksDataBytool(classItem.value, taskItem, fileList.value),
        newCmsItemAndFileLinkListRequest: [],
        newChildrenListRequest: []
      }
    ]
  }
  MG.edu.newTaskCmsItem(data).then((res: any) => {
    newLoading.value = false
    if (res) {
      ElMessage({
        message: '新建成功',
        type: 'success'
      })
      visible.value = false
      getTaskCmsList()
    }
  })
}
// // 编辑资源
// const editOpen = (item: any) => {
//   visible.value = true
//   taskItem.title = item?.datas?.Name
//   taskItem.explain = item?.datas?.explain
//   taskItem.referenceMaterial = item?.datas?.referenceMaterial
//   if (item?.datas?.selectChapter > 0) {
//     dynamicList.value = item?.datas?.selectChapter
//     chapterList.value?.forEach((item: any) => {
//       if (dynamicList.value.find((citem: any) => citem.parentVal == item.chapter)) {
//         childrenList.value.push(...item.children)
//       }
//     })
//   }
// }
</script>
<style lang="less" scoped>
.classManagePage-box {
  padding: 20px;
  .classManagePage-nav {
    padding-bottom: 20px;
    border-bottom: 1px solid #e6e8ed;
    margin-bottom: 30px;
  }
  .classManagePage-content {
    width: 100%;
    .teachPlaneBox {
      width: 100%;
      .titleBox {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 10px;
        .titleOptions {
          width: 160px;
          display: flex;
          justify-content: space-between;
          align-items: center;
          span {
            font-family: PingFang SC;
            font-weight: bold;
            font-size: 16px;
            color: #333;
            line-height: 0px;
            text-align: left;
          }
        }
        .searchBox {
          width: 300px;
          float: left;
          .searchBtn {
            background-color: var(--el-color-primary);
            color: #fff;
            border-top-left-radius: 0;
            border-bottom-left-radius: 0;
          }
        }
      }
    }
    .linkTitle {
      color: #ff6c00;
      cursor: pointer;
    }
    .linkTitle:hover {
      text-decoration: underline;
    }
  }
  .formBox {
    width: 100%;
    font-family: PingFang SC;
    font-weight: 400;
    font-size: 14px;
    color: #333333;
    .el-row {
      margin-bottom: 20px;
      display: flex;
      align-items: flex-start;
      .labelItem {
        text-align: right;
        margin-right: 15px;
        padding-top: 2px;
      }
      .selectBox {
        border: 1px solid #ddd;
        border-radius: 5px;
        padding: 20px 30px;
        ::v-deep(.inputBox) {
          display: flex;
          align-items: center;
          justify-content: flex-start;
          .el-input__wrapper {
            padding: 5px 11px;
          }
        }
      }
      .selectMarBot {
        margin-bottom: 10px;
      }
      .btngroup {
        position: absolute;
        top: 20px;
        right: 27px;
      }
      .el-upload-list__item-name {
        line-height: 22px;
      }
    }
  }
}
</style>
src/views/classManage/testManage.vue
New file
@@ -0,0 +1,22 @@
<template>
  <div class="classManagePage-box">
    <div class="classManagePage-nav">我的班级>{{ classInfo.name }}>作业管理</div>
    <div class="classManagePage-content"></div>
  </div>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router'
const route = useRoute()
const classInfo = JSON.parse(route.query.classInfo)
</script>
<style lang="less" scoped>
.classManagePage-box {
  padding: 20px;
  .classManagePage-nav {
    padding-bottom: 20px;
    border-bottom: 1px solid #e6e8ed;
  }
}
</style>
src/views/courseManage/components/class.vue
New file
@@ -0,0 +1,502 @@
<template>
  <div class="classPage">
    <div class="headerBox">
      <div class="searchBox">
        <el-input v-model="searchKey" @clear="searchData()" clearable placeholder="请输入关键字">
          <template #append>
            <el-button type="primary" class="searchBtn" :icon="Search" @click="searchData()" />
          </template>
        </el-input>
      </div>
      <el-button type="primary" class="applyStartClasses" @click="applyClass">申请开班</el-button>
    </div>
    <div class="classListBox" v-loading="pages.loading">
      <div class="classItem" v-for="(item, index) in classList" :key="index">
        <div class="itemHeader">
          <div class="title">{{ item.name }}</div>
          <div class="classId">(ID:{{ item.id }})</div>
          <div class="copyIdBtn" v-if="item.applyState == 'Normal'" @click="copy(item.refCode)">
            复制邀请码
          </div>
          <div
            class="copyIdBtn"
            style="background: transparent; padding: 0"
            v-if="item.applyState == 'Reject'"
          >
            <el-icon @click="delClass(item)" style="color: red; font-size: 14px"
              ><Delete
            /></el-icon>
          </div>
        </div>
        <div class="itemInfo" @click="groupDetail(item)">
          <div class="infoBox">
            <p>
              状态:<span v-if="item.applyState == 'WaitAudit'" style="color: #ef9f29">
                审核中 </span
              ><span v-if="item.applyState == 'Normal'" style="color: #1dbd11"> 进行中 </span
              ><span v-if="item.applyState == 'Reject'" style="color: red"> 未通过 </span>
            </p>
            <p v-if="item.applyState == 'Reject'">
              拒绝原因:<span style="color: red">{{ item.reason != '' ? item.reason : '-' }}</span>
            </p>
            <p>班级人数:{{ item.memberCount }} / {{ item.maxUserCount }}</p>
            <p>
              有效期:{{ moment(item.beginDate).format('YYYY-MM-DD') }} -
              {{ moment(item.endDate).format('YYYY-MM-DD') }}
            </p>
          </div>
        </div>
      </div>
    </div>
    <div class="pagination-box" v-if="classList.length > 0">
      <el-pagination
        v-model:current-page="pages.page"
        v-model:page-size="pages.pageSize"
        :background="false"
        layout="total, prev, pager, next"
        :total="pages.count"
        @current-change="pageChange"
        @size-change="handleSizeChange"
      />
    </div>
    <div class="nullBox" v-if="!pages.loading && classList.length == 0">
      <el-empty />
    </div>
    <!-- 申请开班弹框 -->
    <el-dialog v-model="applyClassDialog" width="450" align-center>
      <template #title>申请开班</template>
      <el-form :model="formData" label-position="left" ref="dialogFormRef" label-width="100px">
        <el-form-item
          label="班级名称"
          prop="name"
          :rules="[{ required: true, message: '请输入班级名称' }]"
        >
          <el-input v-model="formData.name" placeholder="请输入班级名称" />
        </el-form-item>
        <el-form-item
          label="班级人数"
          prop="num"
          :rules="[{ required: true, message: '请输入班级人数' }]"
        >
          <el-input-number v-model="formData.num" :min="1" :step="1" :precision="0" />
        </el-form-item>
        <el-form-item
          label="班级有效期"
          prop="date"
          :rules="[{ required: true, message: '请选择班级有效期' }]"
        >
          <el-date-picker
            v-model="formData.date"
            type="daterange"
            range-separator="-"
            start-placeholder="开始时间"
            end-placeholder="结束时间"
          />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="applyClassDialog = false">取消</el-button>
          <el-button type="primary" @click="submit" :loading="submitLoading">提交</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup lang="ts">
import { defineProps, reactive, ref, onMounted, inject, watch } from 'vue'
import { Search } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { getPublicImage } from '@/assets/js/middleGround/tool.js'
import moment from 'moment'
import { useRouter } from 'vue-router'
import useClipboard from 'vue-clipboard3'
const { toClipboard } = useClipboard()
const uRouter = useRouter()
const MG: any = inject('MG')
const props = defineProps<{
  courseId: number
  bookInfo: any
}>()
const emit = defineEmits(['refreshParent'])
// 申请开班防抖loading
const submitLoading = ref(false)
const classList: any = ref([])
onMounted(() => {
  getData()
})
const searchKey = ref('')
const pages = reactive({
  page: 1,
  pageSize: 6,
  count: 0,
  loading: true
})
const searchData = () => {
  pages.page = 1
  pages.pageSize = 10
  getData()
}
// 获取班级
const getData = () => {
  pages.loading = true
  MG.edu
    .getCourseClassList({
      courseId: props.courseId,
      start: (pages.page - 1) * pages.pageSize,
      size: pages.pageSize,
      sort: {
        type: 'Desc',
        field: 'CreateDate'
      },
      filterList: [],
      searchList: searchKey.value
        ? [
            {
              keywords: searchKey.value,
              field: 'Name',
              compareType: 'Contains'
            }
          ]
        : []
    })
    .then((res: any) => {
      pages.loading = false
      pages.count = res.totalSize;
      classList.value = res.datas.map((item: any) => {
        return {
          ...item,
          name: item.name,
          id: item.id,
          icon: item.icon ? getPublicImage(item.icon, 80) : '',
          introduction: item.description,
          reason: item.applyReturnMsg ? JSON.parse(item.applyReturnMsg).reason : ''
        }
      })
      refreshParent()
    })
}
// 加入班级
const applyClass = () => {
  formData.value = {
    name: '',
    num: '',
    date: ''
  }
  applyClassDialog.value = true
}
// 刷新父组件数据
const refreshParent = () => {
  emit('refreshParent', 'del')
}
const applyClassDialog = ref(false)
const formData = ref({
  name: '',
  num: '',
  date: ''
})
const submit = () => {
  submitLoading.value = true
  MG.edu
    .newCourseClass({
      courseId: props.courseId,
      name: formData.value.name,
      description: '',
      icon: '',
      type: 'class',
      beginDate: moment(formData.value.date[0]).format('YYYY-MM-DD'),
      endDate: moment(formData.value.date[1]).format('YYYY-MM-DD'),
      config: '',
      price: 0,
      maxUserCount: formData.value.num
    })
    .then((res: any) => {
      if (res) {
        setTimeout(() => {
          submitLoading.value = false
          ElMessage.success('开班已申请,等待管理员审核。')
          applyClassDialog.value = false
          getData()
        }, 1000)
      }
    })
    .catch((err: any) => {
      ElMessage.error('开班申请出错,请稍后再试')
      setTimeout(() => {
        submitLoading.value = false
      }, 1000)
    })
}
// 复制
const copy = async (text: string) => {
  try {
    await toClipboard(text)
    ElMessage({
      message: '复制成功',
      type: 'success'
    })
  } catch (e) {
    console.error(e)
  }
}
// 删除班级
const delClass = (item: any) => {
  const data = {
    ids: [item.id]
  }
  MG.edu
    .delCourseClass(data)
    .then((res: any) => {
      if (res) {
        ElMessage({
          message: '已删除',
          type: 'success'
        })
        getData()
      }
    })
    .catch((err: any) => {
      ElMessage({
        message: '删除失败',
        type: 'error'
      })
      console.log(err)
    })
}
// 获取审核通过的课程
const coursePages = reactive({
  page: 1,
  pageSize: 10,
  count: 0,
  loading: false
})
const courseListData = ref([])
// const getCourse = () => {
//   coursePages.loading = true
//   const searchData = [
//     {
//       keywords: 'jsek_digitalTextbooks',
//       field: 'ProductType'
//     }
//   ]
//   const data = {
//     Size: coursePages.pageSize,
//     Start: coursePages.pageSize * coursePages.page - coursePages.pageSize,
//     sort: {
//       type: 'Desc',
//       field: 'CreateDate'
//     },
//     searchList: searchData
//   }
//   MG.store
//     .getPurchasedProductList(data)
//     .then((res: any) => {
//       coursePages.count = res.totalSize
//       courseListData.value = res.datas.map((item: any) => {
//         return {
//           ...item,
//           img: item.product.icon ? getPublicImage(item.product.icon, 80) : ''
//         }
//       })
//       coursePages.loading = false
//     })
//     .catch(() => {
//       coursePages.loading = false
//     })
// }
const pageChange = (val: number) => {
  pages.page = val
  getData()
}
const handleSizeChange = (val: number) => {
  pages.pageSize = val
  getData()
}
const groupDetail = (item: any) => {
  if (item.applyState == 'WaitAudit') {
    ElMessage({
      message: '正在审核中....',
      type: 'warning'
    })
    return false
  }
  if (item.applyState == 'Reject') {
    ElMessage({
      message: '审核未通过',
      type: 'warning'
    })
    return false
  }
  const bookData = item.linkProductDto.product
  let classinfo: any = {
    id: item.id,
    name: item.name,
    courseId: props.courseId,
    icon: bookData.icon,
    rootCmsItemId: bookData.rootCmsItemId,
    bookId: bookData.id,
    author: bookData.author,
    isbn: bookData.isbn,
    bookRefCode: bookData.refCode
  }
  let page = uRouter.resolve({
    path: '/classManage',
    query: {
      classInfo: JSON.stringify(classinfo)
    }
  })
  window.open(page.href, '_blank')
}
</script>
<style lang="less" scoped>
.classPage {
  .headerBox {
    margin-bottom: 10px;
    overflow: hidden;
    .searchBox {
      width: 300px;
      float: left;
      .searchBtn {
        background-color: var(--el-color-primary);
        color: #fff;
        border-top-left-radius: 0;
        border-bottom-left-radius: 0;
      }
    }
    .applyStartClasses {
      float: right;
    }
  }
  .classListBox {
    overflow: hidden;
    min-height: 200px;
    .classItem {
      float: left;
      width: 49%;
      margin-bottom: 16px;
      margin-right: 1%;
      border-radius: 8px;
      border: 1px solid #efefef;
      overflow: hidden;
      &:nth-child(2n) {
        margin-left: 1%;
        margin-right: 0;
      }
      .itemHeader {
        height: 40px;
        line-height: 40px;
        padding: 0 20px;
        background-color: #f8f8f8;
        div {
          display: inline-block;
        }
        .classId {
          margin-left: 6px;
          font-size: 12px;
          color: #999;
        }
        .copyIdBtn {
          float: right;
          height: 20px;
          line-height: 20px;
          margin-top: 10px;
          font-size: 12px;
          background-color: #fff;
          color: #3b93fe;
          padding: 0 6px;
          border-radius: 50px;
          overflow: hidden;
          cursor: pointer;
        }
      }
      .itemInfo {
        height: 128px;
        padding: 20px;
        flex: 1;
        display: flex;
        cursor: pointer;
        .infoBox {
          flex: 1;
          font-size: 12px;
          p {
            margin-bottom: 10px;
          }
        }
      }
    }
  }
}
.courseList {
  overflow: hidden;
  margin-bottom: 20px;
  .courseItem {
    float: left;
    width: 100px;
    margin: 10px;
    position: relative;
    .checkBox {
      position: absolute;
      right: 0;
      top: 0;
      height: 14px;
      ::v-deep {
        .el-checkbox__inner {
          border-color: #888;
        }
      }
    }
    .imgBox {
      width: 100px;
      height: 110px;
      margin-bottom: 10px;
    }
    p {
      line-height: 1.2;
      display: -webkit-box;
      -webkit-line-clamp: 2;
      -webkit-box-orient: vertical;
      overflow: hidden;
    }
  }
}
.pagination-box {
  padding: 10px 0;
  display: flex;
  justify-content: center;
}
.nullBox {
  text-align: center;
  margin-top: 30px;
  font-size: 20px;
  color: #ccc;
}
</style>
src/views/courseManage/index.vue
New file
@@ -0,0 +1,228 @@
<template>
  <div class="courseManage" v-loading="loading">
    <div class="backBox">
      <span @click="router.back()">&lt; 返回</span>
    </div>
    <div class="courseName">
      <div class="title" :title="detailData.name">{{ detailData.name }}</div>
      <span class="courseId">ID:{{ detailData.id }}</span>
    </div>
    <div class="courseInfoBox">
      <div class="desc" :title="detailData.description">
        {{ detailData.description }}
      </div>
      <div class="textBookImg autoImgBox">
        <img
          v-if="detailData?.linkProduct?.icon != 'default'"
          :src="
            detailData.linkProduct?.icon ? getPublicImage(detailData.linkProduct.icon, 80) : defaultImg
          "
        />
      </div>
      <div class="textBookInfo">
        <p class="title">{{ bookDetail?.name }}</p>
        <p v-if="bookDetail?.author">作者: {{ bookDetail?.author }}</p>
        <p>ISBN:{{ bookDetail?.isbn }}</p>
      </div>
      <div class="statisticsInfoBox">
        <p class="title">班级数量<span>(进行中/全部)</span></p>
        <!-- <p class="num">0 / 0</p> -->
        <p class="num">{{ numClass }}</p>
      </div>
      <!-- <div class="statisticsInfoBox">
        <p class="title">学习人数<span>(学习中/全部)</span></p>
        <p class="num">0 / 0</p>
      </div> -->
      <!-- <div class="statisticsInfoBox">
        <p class="title" style="margin-bottom: 40px">教学大纲</p>
        <p class="num">25</p>
      </div> -->
    </div>
    <el-tabs style="margin-top: 20px">
      <el-tab-pane label="我的班级">
        <classManage
          v-if="detailData.id"
          :courseId="detailData.id"
          :bookInfo="bookDetail"
          @refreshParent="refreshParent"
        />
      </el-tab-pane>
      <!-- <el-tab-pane label="教学计划">教学计划</el-tab-pane> -->
    </el-tabs>
  </div>
</template>
<script setup lang="ts">
import { reactive, ref, onMounted, inject, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { Search } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { getPublicImage } from '@/assets/js/middleGround/tool.js'
import classManage from './components/class.vue'
import defaultImg from '@/assets/images/default-book-img.png'
const MG: any = inject('MG')
const router = useRouter()
const route = useRoute()
const query = route.query
const bookDetail = ref()
const numClass = ref(0)
onMounted(() => {
  getData()
})
const loading = ref(true)
const detailData: any = ref({})
// 获取班级
const getDataClass = () => {
  MG.edu
    .getCourseClassList({
      courseId: detailData.value.id,
      start: 0,
      size: 999,
      sort: {
        type: 'Desc',
        field: 'CreateDate'
      },
      filterList: [],
      searchList: []
    })
    .then((res: any) => {
      numClass.value = res.datas.length
    })
}
// 获取课程信息
const getData = () => {
  MG.edu
    .getCourseById({
      courseId: query.courseId
    })
    .then((res: any) => {
      detailData.value = res
      loading.value = false
      const shopId = res.linkProduct?.id
      getBookDetail(shopId)
      getDataClass()
    })
}
// 获取教材详情
const getBookDetail = (shopId: number) => {
  let query = {
    path: '*',
    queryType: '*',
    productId: String(shopId),
    coverSize: {
      height: 300,
      width: 210
    },
    fields: {
      seriesName: [],
      author: [],
      isbn: [],
      publicationDate: []
    }
  }
  MG.store.getProductDetail(query).then(async (res: any) => {
    bookDetail.value = res.datas
  })
}
const refreshParent = (str: string) => {
  if (str == 'del') getData()
}
</script>
<style lang="less" scoped>
.courseManage {
  padding: 0 20px;
  .backBox {
    border-bottom: 1px solid #efefef;
    padding: 15px 0;
    margin-bottom: 20px;
    color: var(--el-color-primary);
    span {
      cursor: pointer;
    }
  }
  .courseName {
    margin-bottom: 20px;
    font-size: 16px;
    font-weight: bold;
    display: flex;
    justify-content: flex-start;
    align-items: center;
    .title {
      max-width: 300px;
      white-space: nowrap;
      text-overflow: ellipsis;
      overflow: hidden;
      word-wrap: break-word;
    }
    .courseId {
      margin-left: 40px;
      font-size: 12px;
      font-weight: normal;
    }
  }
  .courseInfoBox {
    font-size: 14px;
    border: 1px solid #7ac1ec;
    border-radius: 10px;
    padding: 20px 10px;
    display: flex;
    background: #ebf7ff;
    .desc {
      width: 18%;
      border-right: 1px solid #ccc;
      padding-right: 20px;
      margin-right: 20px;
      font-size: 12px;
      line-height: 30px;
      display: -webkit-box;
      -webkit-box-orient: vertical;
      -webkit-line-clamp: 4;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    .textBookImg {
      width: 100px;
      height: 120px;
      margin-right: 10px;
    }
    .textBookInfo {
      flex: 1.5;
      overflow: hidden;
      line-height: 18px;
      p {
        margin-bottom: 10px;
      }
    }
    .statisticsInfoBox {
      flex: 1;
      overflow: hidden;
      text-align: center;
      p {
        margin-bottom: 20px;
      }
      .num {
        font-size: 16px;
      }
    }
    .title {
      font-weight: bold;
      font-size: 14px;
      span {
        display: inline-block;
        font-weight: normal;
        font-size: 12px;
        margin-top: 6px;
      }
    }
  }
}
</style>
src/views/personalCenter/activeCode.vue
@@ -1,3 +1,397 @@
<template>
    <div>激活码</div>
  <!-- <page> -->
  <div class="personalPage-box">
    <div class="personalPage-title">激活商品</div>
    <div class="personalPage-content">
      <div class="activation flex">
        <el-input class="inputBox" v-model="activateCode" placeholder="请输入激活码">
          <template #append>
            <el-button
              style="background-color: #019e58; color: #fff"
              @click="userActiveCodeGet"
              :loading="loading"
              >激活</el-button
            >
</template>
        </el-input>
        <!-- <el-button class="buttonBox" type="primary" @click="userActiveCodeGet" :loading="loading"
          >激活商品</el-button
        > -->
      </div>
      <div class="tipTitle">已激活商品</div>
      <div class="myCarTopPage">
        <div class="cartContent">
          <div class="list-box" v-loading="pages.loading">
            <ul class="listTable" v-if="dataList.length > 0 && !pages.loading">
              <li v-for="item in dataList" :key="item.index">
                <div class="stateBox flex">
                  <span class="flex1"
                    >使用激活码:<span>{{ item.code }}</span></span
                  >
                  <span class="createDate flex1"
                    >激活日期:{{ item.createDate ? item.createDate : "-" }}</span
                  >
                </div>
                <div class="listItemBox flex">
                  <div style="width: 100%" v-if="item.typeList.length > 0">
                    <div
                      v-for="pItem in item.typeList"
                      :key="pItem.id"
                      class="listItem"
                      @click="
                        goBookDetails(pItem.id, pItem.name, pItem.defaultSaleMethodId)
                      "
                    >
                      <div class="cover">
                        <img
                          :src="
                            pItem.icon ? getPublicImage(pItem.icon, '', '') : bookCover
                          "
                          alt=""
                        />
                        <!-- <div class="type" v-if="pItem.type">{{ pItem.type }}</div> -->
                      </div>
                      <div class="info">
                        <div style="margin-bottom: 10px" v-if="pItem.type">
                          {{ pItem.type }}
                        </div>
                        <span :title="pItem.name">{{ pItem.name }}</span>
                      </div>
                    </div>
                  </div>
                  <div v-else class="noProduct">
                    <el-empty :image-size="80" description="无激活商品" />
                  </div>
                </div>
              </li>
            </ul>
            <div v-if="dataList.length == 0 && !pages.loading">
              <el-empty :image-size="200" />
            </div>
          </div>
          <div class="pagination-box" v-if="dataList.length > 0 && !pages.loading">
            <el-pagination
              v-model:current-page="pages.page"
              v-model:page-size="pages.pageSize"
              layout="total, prev, pager, next"
              :total="pages.count"
              @current-change="handleCurrentChange"
            />
          </div>
        </div>
      </div>
    </div>
  </div>
  <!-- </page> -->
</template>
<script setup lang="ts">
import { reactive, ref, onMounted, inject, watch } from "vue";
import moment from "moment";
import { getPublicImage } from "@/assets/js/middleGround/tool.js";
import { ElMessage } from "element-plus";
import { useBreadcrumbStore, useUserStore } from "@/store";
import { useRouter } from "vue-router";
import bookCover from "@/assets/images/personalCenter/book-cover.png";
const router = useRouter();
const crumbStore = useBreadcrumbStore();
const userStore = useUserStore();
const MG = inject("MG");
const activateCode = ref("");
const loading = ref(false);
let dataList = ref([]);
let pages = reactive({
  page: 1,
  pageSize: 5,
  count: 0,
  loading: false,
});
// 使用激活码
const userActiveCodeGet = () => {
  loading.value = true;
  let lock = true;
  if (activateCode.value == "") {
    ElMessage({
      type: "error",
      message: "请输入激活码!",
    });
    loading.value = false;
  } else {
    if (lock) {
      lock = false;
      MG.store
        .userActiveCode({
          code: activateCode.value,
        })
        .then((res) => {
          ElMessage({
            type: res == "激活成功" ? "success" : "error",
            message: res,
          });
          activateCode.value = "";
          loading.value = false;
          getDataList();
          lock = true;
        });
    }
  }
};
function getDataList() {
  pages.loading = true;
  MG.store
    .userActiveCodeList({
      start: (pages.page - 1) * pages.pageSize,
      size: pages.pageSize,
      sort: {
        type: "Desc",
        field: "CreateDate",
      },
    })
    .then((res) => {
      let list: any[] = [];
      res.datas.forEach((item) => {
        item.createDate = moment(item.createDate).format("YYYY-MM-DD HH:mm:ss");
        item.typeList = [];
        item.saleMethodList.forEach(async (i) => {
          const obj = {
            icon: item.productList[0]?.icon,
            id: item.productList[0]?.id,
            name: item.productList[0]?.name,
            type:
              i.type == "defaultSaleMethod"
                ? "电子书"
                : i.name.includes("-")
                ? i.name.split("-")[0]
                : i.name,
            defaultSaleMethodId: item.productList[0]?.defaultSaleMethodId,
          };
          let parentData = await MG.store.getProductBySaleMethod({ saleMethodId: i.id });
          if (parentData.storeLinks[0].storeRefCode == "jsek_digitalCourses") {
            obj.type = "数字课程";
          }
          if (parentData.storeLinks[0].storeRefCode == "jsek_digitalTextbooks") {
            obj.type = "数字教材";
          }
          item.typeList.push(obj);
        });
        list.push(item);
      });
      setTimeout(() => {
        dataList.value = list;
        pages.count = res.totalSize;
        pages.loading = false;
      }, 500);
    })
    .catch(() => {
      pages.loading = false;
    });
}
onMounted(() => {
  getDataList();
});
// watch(
//   () => userStore.token,
//   () => {
//     getDataList()
//   }
// )
const handleCurrentChange = (val: number) => {
  pages.page = val;
  getDataList();
};
// 跳转书本详情
const goBookDetails = async (id: number, name: string, defaultSaleMethodId: number) => {
  let parentData = await MG.store.getProductBySaleMethod({
    saleMethodId: defaultSaleMethodId,
  });
  console.log(parentData, 123);
  if (parentData.storeLinks[0].storeRefCode == "jsek_digitalCourses") {
    let crumbs = [
      {
        name,
        isCrumbs: true,
        type: "digitalCourses",
        path: "digitalCoursesDetails",
      },
    ];
    // 在全局数据中设置面包屑
    crumbStore.setCrumbs({
      type: "digitalCourses",
      data: crumbs,
      callback: (key: any) => {
        router.push({
          name: "digitalCoursesDetails",
          query: {
            crumbsKey: key,
            bookId: parentData.id,
            bookName: parentData.name,
            type: "digitalCourses",
          },
        });
      },
    });
  } else if (parentData.storeLinks[0].storeRefCode == "jsek_digitalTextbooks") {
    let crumbs = [
      {
        name,
        isCrumbs: true,
        type: "digitalTextbooks",
        path: "digitalTextbooksDetails",
      },
    ];
    // 在全局数据中设置面包屑
    crumbStore.setCrumbs({
      type: "digitalTextbooks",
      data: crumbs,
      callback: (key: any) => {
        router.push({
          name: "digitalTextbooksDetails",
          query: {
            crumbsKey: key,
            bookId: parentData.id,
            bookName: parentData.name,
            type: "digitalTextbooks",
          },
        });
      },
    });
  } else {
    let crumbs = [
      {
        name,
        isCrumbs: true,
        type: "bookService",
        path: "/bookService/details",
      },
    ];
    // 在全局数据中设置面包屑
    crumbStore.setCrumbs({
      type: "bookService",
      data: crumbs,
      callback: (key: any) => {
        router.push({
          name: "bookDetails",
          query: {
            crumbsKey: key,
            bookId: id,
            bookName: name,
            type: "bookService",
          },
        });
      },
    });
  }
};
</script>
<style lang="less" scoped>
::v-deep(.activation) {
  margin-bottom: 30px;
  .el-input__wrapper.is-focus {
    border-color: none !important;
  }
}
.inputBox {
  width: 300px;
  margin-right: 10px;
}
.tipTitle {
  font-family: Microsoft YaHei UI, Microsoft YaHei UI;
  font-weight: 400;
  font-size: 16px;
  color: #000000;
  padding: 5px 20px;
  box-sizing: border-box;
  border-left: 3px solid #019e58;
  margin-bottom: 20px;
}
.stateBox {
  height: 47px;
  line-height: 47px;
  padding: 0 20px;
  border: 1px solid #edecec;
  background: #f3f3f3;
}
.listItemBox {
  margin-top: 20px;
}
.listItem {
  width: 130px;
  cursor: pointer;
  box-sizing: border-box;
  float: left;
  position: relative;
  margin-right: 5%;
  .cover {
    width: 100%;
    height: 180px;
    box-shadow: 0px 0px 20px 1px #ccc;
    position: relative;
    img {
      width: 100%;
      height: 100%;
      object-fit: contain;
    }
    .type {
      width: 50px;
      height: 26px;
      text-align: center;
      font-size: 12px;
      line-height: 24px;
      position: absolute;
      top: 0;
      right: 0;
      background-color: #019e58;
      color: #fff;
      border-radius: 0px 0px 0px 5px;
    }
  }
  .info {
    height: 90px;
    padding: 15px 0;
    width: 100%;
    span {
      font-weight: bold;
      height: 45px;
      line-height: 22px;
      display: -webkit-box;
      margin-bottom: 5px;
      -webkit-box-orient: vertical;
      -webkit-line-clamp: 2;
      overflow: hidden;
      text-overflow: ellipsis;
    }
  }
}
.list-box {
  min-height: 450px;
}
.createDate {
  text-align: right;
}
.noProduct {
  padding: 30px;
  text-align: center;
}
.pagination-box {
  display: flex;
  justify-content: center;
}
</style>
src/views/personalCenter/class.vue
@@ -1,7 +1,462 @@
<template>
  <div class="myBook">
    <div class="myBook_header">
      <div class="myBook_header_title">我的班级</div>
  <div class="coursePage">
    <div class="personalPage-title">我的班级</div>
    <div class="tabs">
      <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
        <el-tab-pane
          :label="'当前班级(' + calssList.length + ')'"
          name="1"
        ></el-tab-pane>
        <!-- <el-tab-pane label="历史班级(5)" name="2"></el-tab-pane> -->
      </el-tabs>
    </div>
    <div class="headerBox">
      <div class="searchBox">
        <el-input
          v-model="searchKey"
          clearable
          @clear="searchList()"
          placeholder="请输入关键字"
        >
          <template #append>
            <el-button
              type="primary"
              class="searchBtn"
              @click="searchList()"
              :icon="Search"
            />
          </template>
        </el-input>
      </div>
      <el-button type="primary" class="applyStartClasses" @click="openJoin()"
        >加入班级</el-button
      >
      <el-dialog v-model="dialogVisible" title="加入班级" width="500">
        <div class="codeContent">
          <span>邀请码:</span>
          <el-input style="width: 330px" v-model="codeText" placeholder="请输入邀请码" />
          <el-button type="primary" @click="getClassDetail">确认</el-button>
        </div>
        <div class="classInfo" v-if="classDetail?.name">
          <div class="itemCon">
            <span>班级名称:</span>
            <span>{{ classDetail.name }}</span>
          </div>
          <div class="itemCon">
            <span>班级人数:</span>
            <span>{{ classDetail.memberCount }} / {{ classDetail.maxUserCount }}</span>
          </div>
          <div class="itemCon">
            <span>开课时间:</span>
            <span
              >{{ moment(classDetail.beginDate).format("YYYY-MM-DD") }} -
              {{ moment(classDetail.endDate).format("YYYY-MM-DD") }}</span
            >
          </div>
        </div>
        <template #footer>
          <div class="dialog-footer">
            <el-button @click="dialogVisible = false"> 取消 </el-button>
            <el-button type="primary" @click="joinClass()"> 确认 </el-button>
          </div>
        </template>
      </el-dialog>
    </div>
    <div class="courseListBox" v-if="calssList.length > 0 && !isLoading">
      <div class="courseItem" v-for="(item, index) in calssList" :key="index">
        <div class="itemHeader">
          <div class="title">{{ item.name }}</div>
          <div class="courseId">(ID:{{ item.id }})</div>
          <!-- <div class="copyIdBtn" @click="copy(item.refCode)">复制邀请码</div> -->
        </div>
        <div class="itemInfo" @click="goClassManage(item)">
          <div class="infoBox">
            <p>
              状态:<span v-if="item.userState == 'WaitValid'" style="color: #ef9f29">
                审核中 </span
              ><span v-if="item.userState == 'Normal'" style="color: #1dbd11">
                进行中 </span
              ><span v-if="item.userState == 'Reject'" style="color: red"> 未通过 </span>
            </p>
            <p>班级人数:{{ item.memberCount }} / {{ item.maxUserCount }}</p>
            <p>有效期:{{ item.classTime }}</p>
          </div>
        </div>
      </div>
    </div>
    <el-empty description="暂无数据" v-if="calssList.length == 0 && !isLoading" />
    <div style="min-height: 200px" v-if="isLoading" v-loading="isLoading"></div>
    <div class="pageBox">
      <el-pagination
        v-model:current-page="pages.currentPage"
        :page-size="pages.pageSize"
        :size="'small'"
        :disabled="pages.count <= 1"
        layout="total, prev, pager, next"
        :total="pages.count"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      />
    </div>
  </div>
</template>
<script setup lang="ts">
import { reactive, ref, onMounted, inject, watch } from "vue";
import { Search } from "@element-plus/icons-vue";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import { getPublicImage } from "@/assets/js/middleGround/tool.js";
import moment from "moment";
import useClipboard from "vue-clipboard3";
const { toClipboard } = useClipboard();
const MG: any = inject("MG");
const config: any = inject("config");
const router = useRouter();
const activeName = ref("1");
interface ClassItem {
  id: string;
  name: string;
  memberCount: number;
  maxUserCount: number;
  userState: string;
  beginDate: string;
  endDate: string;
  classTime: string;
  linkProductDto: {
    product: {
      name: string;
      icon: string;
    };
  };
}
const calssList = ref<ClassItem[]>([]);
const searchKey = ref("");
const codeText = ref("");
const dialogVisible = ref(false);
const classDetail = ref();
const isLoading = ref(true);
let pages = reactive({
  currentPage: 1,
  page: 1,
  pageSize: 6,
  count: 0,
  loading: false,
});
onMounted(() => {
  getCurrentClassList();
});
//  分页
const handleSizeChange = (val: number) => {
  pages.pageSize = val;
  getCurrentClassList();
};
const handleCurrentChange = (val: number) => {
  pages.page = val;
  pages.currentPage = val;
  getCurrentClassList();
};
const handleClick = (val: any) => {
  activeName.value = val;
};
const openJoin = () => {
  dialogVisible.value = true;
  classDetail.value = null;
  codeText.value = "";
};
// 搜索
const searchList = () => {
  pages.page = 1;
  pages.currentPage = 1;
  getCurrentClassList();
};
// 复制
const copy = async (text: string) => {
  try {
    await toClipboard(text);
    ElMessage({
      message: "复制成功",
      type: "success",
    });
  } catch (e) {
    console.error(e);
  }
};
// 加入班级
const joinClass = () => {
  if (!codeText.value) {
    ElMessage({
      message: "无效的邀请码",
      type: "error",
    });
    return false;
  }
  const data = { refCode: codeText.value };
  MG.identity.joinGroupByRefCode(data).then((res: any) => {
    if (res == "组不存在") {
      ElMessage({
        message: "无效的班级",
        type: "error",
      });
    }
    if (res == "组成员数量已最大,不能加入") {
      ElMessage({
        message: "班级成员数量已最大,不能加入",
        type: "error",
      });
    }
    if (res == "已经申请过加入此组") {
      ElMessage({
        message: "已经申请过加入此班级",
        type: "error",
      });
    }
    dialogVisible.value = false;
    getCurrentClassList();
  });
};
// 获取当前班级列表
const getCurrentClassList = () => {
  isLoading.value = true;
  const data = {
    start: (pages.page - 1) * pages.pageSize,
    size: pages.pageSize,
    sort: {
      type: "Desc",
      field: "CreateDate",
      subSorts: [],
    },
    filterList: [],
    searchList: searchKey.value
      ? [
          {
            keywords: searchKey.value,
            field: "Name",
            compareType: "Contains",
          },
        ]
      : [],
  };
  MG.identity.joinedGroupByList(data).then((res: any) => {
    isLoading.value = false;
    pages.count = res.totalSize;
    if (res.datas) {
      calssList.value = res.datas.map((item: any) => {
        return {
          ...item,
          classTime:
            moment(item.beginDate).format("YYYY.MM.DD") +
            "--" +
            moment(item.endDate).format("YYYY.MM.DD"),
          bookName: item.linkProductDto.product.name,
          bookIcon: getPublicImage(item.linkProductDto.product.icon, 100),
        };
      });
    }
  });
};
// 跳转班级管理
const goClassManage = async (item: any) => {
  if (item.userState == "WaitValid") {
    ElMessage({
      message: "正在审核中....",
      type: "warning",
    });
    return false;
  }
  if (item.userState == "Reject") {
    ElMessage({
      message: "审核未通过",
      type: "warning",
    });
    return false;
  }
  const bookInfo = await getBookDetail(item.linkProductDto?.product);
  let info = {
    id: item.id,
    name: item.name,
    icon: bookInfo.icon,
    rootCmsItemId: bookInfo.rootCmsItemId,
    bookId: bookInfo.id,
    author: bookInfo.author,
    isbn: bookInfo.isbn,
    bookRefCode: bookInfo.refCode,
  };
  let page = router.resolve({
    path: "/classManage",
    query: {
      classInfo: JSON.stringify(info),
    },
  });
  window.open(page.href, "_blank");
  // router.push({
  //   path: '/classManage',
  //   query: {
  //     classInfo: JSON.stringify(info)
  //   }
  // })
};
// 获取教材详情
const getBookDetail = async (item: any) => {
  const path = item.refCode ? "jsek_digitalTextbooks" : config.goodsStore;
  let query = {
    path,
    queryType: "*",
    productId: String(item.id),
    storeInfo: path,
    coverSize: {
      height: 300,
      width: 210,
    },
    fields: {
      author: [],
      isbn: [],
    },
  };
  const res = await MG.store.getProductDetail(query);
  return res.datas ?? null;
};
// 通过code查询班级
const getClassDetail = () => {
  if (codeText.value == "") {
    ElMessage({
      message: "请输入邀请码",
      type: "warning",
    });
    return false;
  }
  const data = {
    classIdOrRefCode: codeText.value,
  };
  MG.edu
    .getCourseClass(data)
    .then((res: any) => {
      classDetail.value = res;
    })
    .catch((err: any) => {
      console.log(err);
    });
};
</script>
<style lang="less" scoped>
.coursePage {
  .tabs {
    padding: 0 20px;
  }
  .headerBox {
    padding: 5px 20px;
    margin-bottom: 10px;
    overflow: hidden;
    .searchBox {
      width: 300px;
      float: left;
      .searchBtn {
        background-color: var(--el-color-primary);
        color: #fff;
        border-top-left-radius: 0;
        border-bottom-left-radius: 0;
      }
    }
    .applyStartClasses {
      float: right;
    }
  }
  .courseListBox {
    overflow: hidden;
    .courseItem {
      float: left;
      width: 46%;
      margin: 0 2% 20px;
      border-radius: 8px;
      border: 1px solid #efefef;
      overflow: hidden;
      .itemHeader {
        height: 40px;
        line-height: 40px;
        padding: 0 20px;
        // color: #fff;
        background-color: #f8f8f8;
        .title {
          font-weight: 600;
        }
        div {
          display: inline-block;
        }
        .courseId {
          margin-left: 6px;
          font-size: 12px;
          color: #999;
        }
        .copyIdBtn {
          float: right;
          height: 20px;
          line-height: 20px;
          margin-top: 10px;
          font-size: 12px;
          background-color: #fff;
          color: #3b93fe;
          padding: 0 6px;
          border-radius: 50px;
          overflow: hidden;
          cursor: pointer;
        }
      }
      .itemInfo {
        padding: 20px;
        flex: 1;
        display: flex;
        cursor: pointer;
        .imgBox {
          width: 90px;
          height: 120px;
          margin-right: 20px;
        }
        .infoBox {
          flex: 1;
          font-size: 12px;
          p {
            margin-bottom: 10px;
          }
        }
      }
    }
  }
  .codeContent {
    width: 100%;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding-bottom: 30px;
  }
  .classInfo {
    padding: 30px 0;
    border-top: 2px dashed #ccc;
    .itemCon {
      margin-bottom: 20px;
    }
  }
  .pageBox {
    padding: 10px 0;
    display: flex;
    justify-content: center;
  }
}
</style>
src/views/personalCenter/config.ts
@@ -20,7 +20,7 @@
</svg>`,
  },
  {
    label: '我的图书',
    label: '我的书架',
    key: '3',
    path: 'myBook',
    icon: `<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
src/views/personalCenter/course.vue
@@ -1,7 +1,655 @@
<template>
  <div class="myBook">
    <div class="myBook_header">
      <div class="myBook_header_title">我的课程</div>
  <div class="coursePage">
    <div class="personalPage-title">我的课程</div>
    <div class="headerBox">
      <div class="searchBox">
        <el-input
          v-model="searchKey"
          clearable
          @clear="getData"
          placeholder="请输入关键字"
        >
          <template #append>
            <el-button type="primary" class="searchBtn" :icon="Search" @click="getData" />
          </template>
        </el-input>
    </div>
      <el-button type="primary" class="applyStartClasses" @click="applyCourse"
        >申请开课</el-button
      >
    </div>
    <div class="courseListBox" v-loading="pages.loading">
      <div
        class="courseItem"
        v-for="(item, index) in courseList"
        :key="index"
        @click="gotoDetail(item)"
      >
        <div class="itemHeader">
          <div class="title" :title="item.name">{{ item.name }}</div>
        </div>
        <div class="reasonBox">
          <el-tooltip placement="right-end" effect="light" class="box-item">
            <template #content>
              <div style="width: 300px" v-html="item.reason"></div>
            </template>
            <span v-if="item.applyState == 'Reject'" style="color: red">
              拒绝原因:{{ item.reason != "" ? item.reason : "-" }}
            </span>
          </el-tooltip>
          <el-button
            v-if="item.applyState == 'Reject'"
            type="primary"
            size="small"
            style="margin-left: 20px"
            @click="reapplyCourse(item)"
            >重新申请</el-button
          >
        </div>
        <div class="itemInfo">
          <div class="imgBox autoImgBox">
            <div
              class="stateIcon"
              v-if="item.applyState == 'Normal'"
              style="background-color: #1dbd11"
            >
              使用中
            </div>
            <div
              class="stateIcon"
              v-if="item.applyState == 'WaitAudit'"
              style="background-color: #ef9f29"
            >
              审核中
            </div>
            <div
              class="stateIcon"
              v-if="item.applyState == 'Reject'"
              style="background-color: red"
            >
              未通过
            </div>
            <img :src="item.icon" />
          </div>
          <div class="infoBox">
            <p class="id">ID:{{ item.id }}</p>
            <div class="introduction" :title="item.introduction">
              {{ item.introduction }}
            </div>
          </div>
        </div>
      </div>
    </div>
    <div class="pagination-box" v-if="courseList.length > 0">
      <el-pagination
        v-model:current-page="pages.page"
        v-model:page-size="pages.pageSize"
        :background="false"
        layout="total, prev, pager, next"
        :total="pages.count"
        @current-change="pageChange"
      />
    </div>
    <div class="nullBox" v-if="!pages.loading && courseList.length == 0">
      <el-empty />
    </div>
    <!-- 申请开课弹框 -->
    <el-dialog v-model="applyCourseDialog" width="750" align-center>
      <template #title>{{ editData ? "重新申请" : "申请开课" }}</template>
      <el-form
        :model="formData"
        label-position="left"
        ref="dialogFormRef"
        label-width="80px"
      >
        <el-form-item
          label="课程名称"
          prop="name"
          :rules="[{ required: true, message: '请输入课程名称' }]"
        >
          <el-input v-model="formData.name" placeholder="请输入课程名称" />
        </el-form-item>
        <el-form-item
          v-if="!editData"
          label="关联教材"
          prop="bookName"
          :rules="[{ required: true, message: '请选择教材' }]"
        >
          <div style="display: flex">
            <el-input v-model="formData.bookName" disabled="true" style="width: 300px" />
            <el-button style="margin-left: 10px" @click="selectBook">选择教材</el-button>
          </div>
        </el-form-item>
        <el-form-item label="课程介绍">
          <el-input
            v-model="formData.desc"
            type="textarea"
            autocomplete="off"
            maxlength="300"
            show-word-limit
            rows="7"
            placeholder="请输入课程介绍"
          />
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="applyCourseDialog = false">取消</el-button>
          <el-button type="primary" @click="submit" :loading="submitLoading"
            >提交</el-button
          >
  </div>
</template>
    </el-dialog>
    <!-- 选择教材弹框 -->
    <el-dialog v-model="selectTextBookDialog" width="1000" align-center>
      <template #title>选择教材</template>
      <div class="textBookList" v-loading="textBookPages.loading">
        <div class="headerBox" style="padding: 10px">
          <div class="searchBox" style="float: right">
            <el-input
              v-model="selectName"
              @clear="getTextBook"
              clearable
              placeholder="请输入关键字"
            >
              <template #append>
                <el-button
                  type="primary"
                  class="searchBtn"
                  :icon="Search"
                  @click="getTextBook"
                />
              </template>
            </el-input>
          </div>
        </div>
        <div style="min-height: 370px" v-if="textBookListData.length > 0">
          <div
            v-for="(item, index) in textBookListData"
            :key="index"
            class="textBookItem"
          >
            <el-checkbox
              class="checkBox"
              v-model="item.check"
              @change="selectChange(item)"
            />
            <div class="imgBox autoImgBox">
              <img :src="item.img" />
            </div>
            <p>{{ item.product.name }}</p>
          </div>
        </div>
        <div
          class="nullBox"
          v-if="!textBookPages.loading && textBookListData.length == 0"
        >
          <el-empty />
        </div>
      </div>
      <div class="pagination-box" v-if="textBookListData.length > 0">
        <el-pagination
          v-model:current-page="textBookPages.page"
          v-model:page-size="textBookPages.pageSize"
          :background="false"
          layout="total, prev, pager, next"
          :total="textBookPages.count"
          @current-change="handleCurrentChange"
        />
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="selectTextBookDialog = false">取消</el-button>
          <el-button type="primary" @click="selectTextBookSubmit">确定</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup lang="ts">
import { reactive, ref, onMounted, inject, watch } from "vue";
import { Search } from "@element-plus/icons-vue";
import { useRouter, useRoute } from "vue-router";
import { ElMessage } from "element-plus";
// eslint-disable-next-line
import { getPublicImage } from "@/assets/js/middleGround/tool.js";
// eslint-disable-next-line
import defaultImg from "@/assets/images/default-book-img.png";
const router: any = useRouter();
const route: any = useRoute();
const MG: any = inject("MG");
const selectName = ref("");
// 申请开课防抖loading
const submitLoading = ref(false);
const courseList: any = ref([]);
onMounted(() => {
  getData();
});
const searchKey = ref("");
const pages = reactive({
  page: 1,
  pageSize: 6,
  count: 0,
  loading: true,
});
// 获取课程
const getData = () => {
  pages.loading = true;
  MG.edu
    .getAppCourseList({
      size: pages.pageSize,
      start: pages.pageSize * pages.page - pages.pageSize,
      sort: {
        type: "Desc",
        field: "CreateDate",
      },
      filterList: [],
      searchList: searchKey.value
        ? [
            {
              keywords: searchKey.value,
              field: "Name",
              compareType: "Contains",
            },
          ]
        : [],
    })
    .then((res: any) => {
      pages.loading = false;
      pages.count = res.totalSize;
      courseList.value = res.datas.map((item: any) => {
        return {
          ...item,
          name: item.name,
          id: item.id,
          icon: item.icon != "default" ? getPublicImage(item.icon, 80) : defaultImg,
          introduction: item.description,
          reason: item.applyReturnMsg ? JSON.parse(item.applyReturnMsg).reason : "",
        };
      });
    });
};
// 申请开课
const applyCourse = () => {
  editData.value = null;
  formData.value = {
    name: "",
    bookName: "",
    bookId: "",
    selectData: "",
    desc: "",
  };
  applyCourseDialog.value = true;
};
const editData: any = ref(null);
const reapplyCourse = (data: any) => {
  editData.value = data;
  formData.value = {
    name: data.name,
    bookName: data.linkProduct.name,
    bookId: data.linkProduct.id,
    selectData: "",
    desc: data.description,
  };
  applyCourseDialog.value = true;
};
// 申请教材弹框
const applyCourseDialog = ref(false);
const selectTextBookDialog = ref(false);
const formData = ref({
  name: "",
  bookName: "",
  bookId: "",
  selectData: "",
  desc: "",
});
const selectChange = (select: any) => {
  for (let i = 0; i < textBookListData.value.length; i++) {
    const item: any = textBookListData.value[i];
    if (item.id == select.id) {
      item.check = true;
    } else {
      item.check = false;
    }
  }
};
const selectTextBookSubmit = () => {
  const selectData: any = textBookListData.value.filter((item: any) => item.check)[0];
  if (!selectData?.product?.id) {
    ElMessage.warning("请选择开课教材!");
    return false;
  }
  formData.value.bookId = selectData.product.id;
  formData.value.bookName = selectData.product.name;
  formData.value.selectData = selectData;
  selectTextBookDialog.value = false;
};
const submit = () => {
  submitLoading.value = true;
  if (editData.value) {
    MG.edu
      .updateCourse({
        courseId: editData.value.id,
        name: formData.value.name,
        description: formData.value.desc,
      })
      .then((res: any) => {
        MG.edu
          .updateCourseApply({
            courseId: editData.value.id,
            applyData: JSON.stringify({
              textBookId: formData.value.bookId,
              textBookName: formData.value.bookName,
            }),
          })
          .then((ares: any) => {
            ElMessage.success("课程已重新申请,等待管理员审核。");
            applyCourseDialog.value = false;
            getData();
          });
      });
  } else {
    if (formData.value.name == "") {
      ElMessage({
        type: "warning",
        message: "请填写课程名称",
      });
      submitLoading.value = false;
      return false;
    }
    if (!formData.value.bookId) {
      ElMessage({
        type: "warning",
        message: "请选择关联教材",
      });
      submitLoading.value = false;
      return false;
    }
    MG.edu
      .applyNewCourse({
        name: formData.value.name,
        description: formData.value.desc,
        content: "",
        icon: formData.value.selectData.product.icon ?? "default",
        type: "course",
        config: "",
        applyData: JSON.stringify({
          textBookId: formData.value.bookId,
          textBookName: formData.value.bookName,
        }),
        linkProductId: formData.value.bookId,
        maxClassCount: 999,
        payPrice: 0,
      })
      .then((res: any) => {
        if (res) {
          ElMessage.success("课程已申请,等待管理员审核。");
          applyCourseDialog.value = false;
          getData();
        }
      });
  }
  formData.value.selectData = "";
  setTimeout(() => {
    submitLoading.value = false;
  }, 1000);
};
// 获取已购买的教材列表
const textBookPages = reactive({
  page: 1,
  pageSize: 12,
  count: 0,
  loading: false,
});
const textBookListData = ref([]);
const getTextBook = () => {
  textBookPages.loading = true;
  const searchData = [
    {
      keywords: "jsek_digitalTextbooks",
      field: "ProductType",
    },
    {
      keywords: "jsek_mediaBook",
      field: "ProductType",
    },
    {
      keywords: selectName.value,
      field: "ProductName",
    },
  ];
  const data = {
    Size: textBookPages.pageSize,
    Start: textBookPages.pageSize * textBookPages.page - textBookPages.pageSize,
    sort: {
      type: "Desc",
      field: "CreateDate",
    },
    searchList: searchData,
  };
  MG.store
    .getPurchasedProductList(data)
    .then((res: any) => {
      textBookPages.count = res.totalSize;
      textBookListData.value = res.datas.map((item: any) => {
        return {
          ...item,
          img: item.product.icon ? getPublicImage(item.product.icon, 80) : defaultImg,
        };
      });
      textBookPages.loading = false;
    })
    .catch(() => {
      textBookPages.loading = false;
    });
};
// 选择教材
const selectBook = () => {
  selectTextBookDialog.value = true;
  textBookListData.value = [];
  getTextBook();
};
const pageChange = (val: any) => {
  pages.page = val;
  getData();
};
const handleCurrentChange = (val: any) => {
  textBookPages.page = val;
  getTextBook();
};
const gotoDetail = (data: any) => {
  if (data.applyState == "Normal") {
    router.push({
      name: "courseDetail",
      query: {
        courseId: data.id,
      },
    });
  }
};
</script>
<style lang="less" scoped>
.coursePage {
  .headerBox {
    padding: 20px;
    margin-bottom: 10px;
    overflow: hidden;
    .searchBox {
      width: 300px;
      float: left;
      .searchBtn {
        background-color: var(--el-color-primary);
        color: #fff;
        border-top-left-radius: 0;
        border-bottom-left-radius: 0;
      }
    }
    .applyStartClasses {
      float: right;
    }
  }
  .courseListBox {
    overflow: hidden;
    min-height: 200px;
    .courseItem {
      float: left;
      width: 46%;
      height: 210px;
      margin: 0 2% 20px;
      border-radius: 8px;
      border: 1px solid #efefef;
      overflow: hidden;
      cursor: pointer;
      .itemHeader {
        height: 40px;
        line-height: 40px;
        padding: 0 20px;
        background-color: #f8f8f8;
        .title {
          font-weight: 600;
        }
        div {
          width: 100%;
          white-space: nowrap;
          overflow: hidden;
          text-overflow: ellipsis;
          word-break: break-all;
        }
      }
      .reasonBox {
        display: flex;
        padding: 5px 20px;
        cursor: default;
        span {
          font-size: 12px;
          line-height: 24px;
          white-space: nowrap;
          overflow: hidden;
          text-overflow: ellipsis;
        }
      }
      .itemInfo {
        padding: 10px 20px;
        flex: 1;
        display: flex;
        .imgBox {
          width: 90px;
          height: 120px;
          margin-right: 20px;
        }
        .infoBox {
          flex: 1;
          font-size: 14px;
          p {
            margin-bottom: 10px;
          }
          .introduction {
            height: 88px;
            line-height: 22px;
            display: -webkit-box;
            -webkit-box-orient: vertical;
            -webkit-line-clamp: 4;
            overflow: hidden;
            text-overflow: ellipsis;
            word-break: break-all;
          }
        }
        .stateIcon {
          position: absolute;
          right: -6px;
          top: -4px;
          padding: 5px 8px;
          color: #fff;
          border-top-left-radius: 30px;
          border-bottom-right-radius: 30px;
          border-top-right-radius: 30px;
          z-index: 99;
        }
      }
    }
  }
}
.textBookList {
  overflow: hidden;
  margin-bottom: 20px;
  .textBookItem {
    float: left;
    width: 140px;
    height: 200px;
    margin: 10px;
    position: relative;
    border: 1px solid #eee;
    padding: 20px;
    box-sizing: border-box;
    .checkBox {
      position: absolute;
      right: 0;
      top: 0;
      height: 14px;
      ::v-deep {
        .el-checkbox__inner {
          border-color: #888;
        }
      }
    }
    .imgBox {
      width: 100px;
      height: 110px;
      margin-bottom: 10px;
    }
    p {
      line-height: 1.2;
      display: -webkit-box;
      -webkit-line-clamp: 2;
      -webkit-box-orient: vertical;
      overflow: hidden;
    }
  }
}
.pagination-box {
  display: flex;
  justify-content: center;
  padding: 10px 0;
}
.nullBox {
  text-align: center;
  margin-top: 30px;
  font-size: 20px;
  color: #ccc;
}
</style>
src/views/personalCenter/myApply.vue
@@ -1,3 +1,318 @@
<!-- 基本信息 -->
<template>
  <div>我的申请</div>
  <div class="personalPage-box">
    <div class="personalPage-title">我的申请</div>
    <div class="personalPage-content">
      <div class="tipsText">
        <div>
          如您在教材试用申请过程中遇到问题,请于工作时间联系我们。<span class="phone">
            QQ号:3565269931 / 咨询电话010-65778403(工作时间:9:00~17:00)
          </span>
        </div>
      </div>
      <div class="stageBtm" v-for="(item, index) in listData" :key="index">
        <div class="infor">
          <div class="infoBox">
            <div>
              审核状态:
              <span
                :class="{
                  reviewstatus: true,
                  reviewstatusRed: item.state == 'Reject',
                  reviewstatusWait: item.state == 'WaitAudit',
                }"
                >{{
                  item.state == "WaitAudit"
                    ? "审核中"
                    : item.state == "Normal"
                    ? "通过"
                    : "拒绝"
                }}</span
              >
            </div>
            <div
              style="color: orangered"
              v-if="item.state == 'Normal' && item.feedBack && !item.isExpiry"
            >
              试用期限:<span>{{ item.updateDate }}</span> --
              <span>{{ item.feedBack.endDate }}</span>
            </div>
            <div style="color: orangered" v-if="item.isExpiry">
              阅读期限:<span>已过期</span>
            </div>
            <div class="time">申请时间:{{ item.updateDate }}</div>
          </div>
          <div
            class="reasonForFailure"
            style="margin: 10px 0"
            v-if="item.state == 'Reject'"
          >
            <div class="centerVertically">未通过原因:</div>
            <div
              style="flex: 1"
              class="ellipsis-3"
              :title="item.feedBack.reason ? item.feedBack.reason : ' - '"
            >
              {{ item.feedBack.reason ? item.feedBack.reason : " - " }}
            </div>
          </div>
        </div>
        <div class="contentInfoBox">
          <div class="listImg">
            <img @click.stop="toDetail(item.content)" :src="item.content.icon" alt="" />
            <div class="name" :title="item.content.title">
              {{ item.content.title }}
            </div>
            <el-button
              size="mini"
              v-if="item.state == 'Normal' && !item.isExpiry"
              @click="read(item.content)"
              >开始阅读</el-button
            >
          </div>
        </div>
      </div>
      <div
        style="min-height: 100px"
        v-if="listData && listData.length > 0"
        v-loading="loading"
      ></div>
    </div>
    <div v-if="listData && listData.length == 0 && !loading">
      <el-empty :image-size="200" description="暂无内容"></el-empty>
    </div>
    <div class="pageBox" v-if="listData && listData.length > 0">
      <!-- 分页 -->
      <el-pagination
        background
        :current-page="paginationData.page - 0"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
        :page-size="paginationData.limit"
        layout="total, prev, pager, next, slot"
        :total="paginationData.totalCount"
      >
        <div style="display: inline-block">
          <span class="el-pagination__jump"
            >前往
            <div class="el-input el-pagination__editor is-in-pagination">
              <input
                type="number"
                autocomplete="off"
                min="1"
                :max="paginationData.totalPage"
                v-model="inputPage"
                class="el-input__inner"
                @keyup.enter="jumpFun($event)"
              />
            </div>
            页
          </span>
          <el-button type="primary" class="toBtn" @click="jumpFun">确定</el-button>
        </div>
      </el-pagination>
    </div>
  </div>
</template>
<script setup lang="ts">
import { reactive, ref, onMounted, inject } from "vue";
import { getPublicImage } from "@/assets/js/middleGround/tool";
const MG: any = inject("MG");
const config: any = inject("config");
const crumbStore = inject("crumbStore");
const router = inject("router");
let listData = ref([]);
let loading = ref(false);
let paginationData = reactive({
  page: 1,
  limit: 10,
  totalCount: 0,
  totalPage: 0,
});
let inputPage = ref(1);
const getTextBookList = () => {
  loading.value = true;
  let { page, limit } = paginationData;
  const data = {
    start: limit * page - limit,
    size: limit,
    topicIdOrRefCode: "applyDigitalBook",
    appRefCode: config.appRefCode,
    sort: {
      type: "Desc",
      field: "CreateDate",
    },
  };
  MG.ugc.getTopicMessageList(data).then((res) => {
    loading.value = false;
    res.datas.forEach((item) => {
      item.icon = item.icon ? item.icon : getPublicImage(null);
      item.updateDate = item.updateDate.split("T")[0];
      if (item.feedBack) {
        item.feedBack = JSON.parse(item.feedBack);
        if (item.feedBack.endDate) {
          let times = new Date(item.feedBack.endDate + " 23:59:59").getTime();
          if (times < sessionStorage.currentDate) {
            item.isExpiry = true;
          }
        }
      }
      if (item.content) {
        item.content = JSON.parse(item.content)[0] ?? {};
        if (!item.content?.icon) {
          item.content.icon = require("@/assets/images/default-book-img.png");
        }
      }
    });
    paginationData.totalCount = res.totalSize;
    paginationData.totalPage = Math.ceil(res.totalSize / limit);
    listData.value = res.datas;
  });
};
onMounted(() => {
  getTextBookList();
});
const toDetail = (item: any) => {
  const thisCrumbs = [{ name: "书籍详情", pathName: "digitalTextbooks-textbooksDetail" }];
  MG.store.dispatch("setCrumbs", {
    type: "textbooks",
    data: thisCrumbs,
    callback: (key: string) => {
      MG.router.push({
        name: "digitalTextbooks-textbooksDetail",
        query: {
          id: item.id,
          rootCmsItemId: item.rootCmsItemId,
          crumbsKey: key,
        },
      });
    },
  });
};
const read = (pItem: any) => {
  let token = MG.tool.getCookie(config.tokenKey);
  window.open(config.textReaderUrl + "?bookId=" + pItem.refCode + "&token=" + token);
};
const handleSizeChange = (val: number) => {
  paginationData.limit = val;
  getTextBookList();
};
const handleCurrentChange = (val: number) => {
  paginationData.page = val;
  getTextBookList();
};
const jumpFun = (event: any) => {
  event.target.blur();
  if (inputPage.value <= 0) {
    inputPage.value = 1;
  }
  if (inputPage.value > paginationData.totalPage) {
    inputPage.value = paginationData.totalPage;
  }
  paginationData.page = inputPage.value;
  getTextBookList();
};
</script>
<style scoped lang="less">
.myCarTopPage {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 15px;
  box-sizing: border-box;
  background-color: #fff;
  border-bottom: 1px solid #dcdcdc;
}
.tipsText {
  padding: 10px 15px;
  line-height: 20px;
  box-sizing: border-box;
  border: 1px solid #ccc;
  .phone {
    color: #019e58;
  }
  margin-bottom: 10px;
}
.pageBox {
  background-color: #fff;
  margin-top: 50px;
}
.stageBtm {
  border: 1px solid #dcdcdc;
  .infor {
    border-bottom: 1px solid #dcdcdc;
    padding: 0 15px;
    .infoBox {
      line-height: 45px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      .time {
        color: #999;
      }
    }
    .reasonForFailure {
      display: flex;
      margin: 10px 0;
      line-height: 1.5;
      .centerVertically {
        width: 80px;
      }
      /* 双行省略 */
      .ellipsis-3 {
        text-overflow: -o-ellipsis-lastline;
        overflow: hidden;
        text-overflow: ellipsis;
        display: -webkit-box;
        -webkit-line-clamp: 3;
        line-clamp: 3;
        -webkit-box-orient: vertical;
      }
    }
  }
  .contentInfoBox {
    padding: 15px;
    box-sizing: border-box;
    .listImg {
      display: inline-block;
      margin-right: 60px;
      width: 120px;
      img {
        width: 120px;
        cursor: pointer;
      }
      .name {
        line-height: 27px;
        font-size: 15px;
        color: #333;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }
    }
  }
}
.reviewstatus {
  color: #0bc266;
}
.reviewstatusWait {
  color: #ffbe00;
}
.reviewstatusRed {
  color: red;
}
.timer {
  color: rgb(240, 67, 67);
}
</style>
<style>
.el-pagination {
  text-align: center;
  margin-top: 15px;
}
</style>
src/views/personalCenter/myBook.vue
@@ -1,7 +1,329 @@
<template>
  <div class="myBook">
    <div class="myBook_header">
      <div class="myBook_header_title">我的订单</div>
  <div class="personalPage-box">
    <div class="personalPage-title">我的书架</div>
    <div class="personalPage-content">
      <div class="pageBox cartPage" v-loading="isLoading">
        <div class="myCarTopPage" v-if="collectList.length > 0">
          <div
            class="bookone"
            v-for="(item, index) in collectList"
            :key="index"
            @click="goDetail(item)"
          >
            <div class="imgBox">
              <img :src="item.product.icon" alt />
            </div>
            <div class="details">
              <div class="text-flow" v-if="item.product.name">
                {{ item.product.name || "-" }}
              </div>
              <div class="text-flow" :title="item.tourism_ISBN">
                ISBN:{{ item.tourism_ISBN.length != 0 ? item.tourism_ISBN : "-" }}
              </div>
              <div class="text-flow">
                作者:{{ item.tourism_author.length != 0 ? item.tourism_author : "-" }}
              </div>
              <!-- <div class="text-flow" v-if="item.ExpiryDate">
            截止日期:<span style="color: #dd0000">{{ item.ExpiryDate }}</span>
          </div> -->
            </div>
          </div>
          <div class="pageCon">
            <!-- 分页 -->
            <el-pagination
              background
              :current-page="paginationData.page - 0"
              @size-change="handleSizeChange"
              @current-change="handleCurrentChange"
              :page-size="paginationData.limit"
              layout="total, prev, pager, next, slot"
              :total="paginationData.totalCount"
            >
              <div style="display: inline-block">
                <span class="el-pagination__jump"
                  >前往
                  <div class="el-input el-pagination__editor is-in-pagination">
                    <input
                      type="number"
                      autocomplete="off"
                      min="1"
                      :max="paginationData.totalPage"
                      v-model="inputPage"
                      class="el-input__inner"
                      @keyup.enter="jumpFun($event)"
                    />
                  </div>
                  页
                </span>
                <el-button type="primary" class="toBtn" @click="jumpFun">确定</el-button>
              </div>
            </el-pagination>
          </div>
        </div>
        <div class="myCarTopPage" v-else>
          <el-empty description="您还未购买任何图书" :image-size="200" />
        </div>
      </div>
    </div>
  </div>
</template>
<script lang="ts" setup>
import { reactive, ref, onMounted, inject, watch } from "vue";
import { useRouter } from "vue-router";
import { useUserStore, useBreadcrumbStore } from "@/store";
import tool from "@/assets/js/toolClass";
const MG: any = inject("MG");
const config: any = inject("config");
const router = useRouter();
const crumbStore = useBreadcrumbStore();
let collectList = ref([]);
let currentCollect = ref("book");
let isLoading = ref(false);
let pages = reactive({
  page: 1,
  pageSize: 10,
  count: 0,
  loading: false,
});
let linkType = ref("PurchasedProduct");
let paginationData = reactive({
  page: 1,
  limit: 10,
  totalCount: 0,
  totalPage: 0,
});
let inputPage = ref(1);
const loading = ref(false);
const listData = ref([]);
const keyQueryRequests = [
  {
    key: "author",
  },
  {
    key: "isbn",
  },
];
const getData = () => {
  loading.value = true;
  const searchData = [
    {
      keywords: "digitalTextbooks",
      field: "ProductType",
    },
  ];
  const data = {
    Size: paginationData.limit,
    Start: (paginationData.page - 1) * paginationData.limit,
    sort: {
      type: "Desc",
      field: "CreateDate",
    },
    searchList: searchData,
    keyQueryRequests: keyQueryRequests,
  };
  MG.store.getPurchasedProductList(data).then(async (response) => {
    listData.value = handResultsChange(response.datas);
    listData.value.forEach((item) => {
      item.product.icon = tool.getPublicImage(item.product.icon);
    });
    // console.log(that.collectList);
    // //当前页面
    paginationData.totalCount = response.totalSize;
    paginationData.totalPage =
      response.totalSize % paginationData.limit === 0
        ? response.totalSize / paginationData.limit
        : Math.floor(response.totalSize / paginationData.limit) + 1;
    loading.value = false;
  });
};
onMounted(() => {
  getData();
});
// 处理查询结果
const handResultsChange = (data) => {
  let fieldsData = [];
  for (let i = 0; i < data.length; i++) {
    const item = data[i];
    for (const val in keyQueryRequests) {
      fieldsData.push(keyQueryRequests[val].key);
    }
    for (let i = 0; i < fieldsData.length; i++) {
      const field = fieldsData[i];
      item[field] = JSON.parse(item.datas[field]);
      const datas = item[field];
      if (datas.length > 0) {
        if (datas[0].Value) {
          item[field] = datas[0].Value;
        } else if (datas[0].Data) {
          item[field] = datas[0].Data.Value;
        }
      }
    }
  }
  return data;
};
//到图书详情
const goDetail = (item) => {
  let crumbs = [
    {
      name: "教材详情",
    },
  ];
  crumbStore.dispatch("setCrumbs", {
    type: "textbooks",
    data: crumbs,
    callback: (key) => {
      router.push({
        name: "digitalTextbooks-textbooksDetail",
        query: {
          id: item.product.id,
          rootCmsItemId: item.product.rootCmsItemId,
          crumbsKey: key,
        },
      });
    },
  });
};
//分页
const handleSizeChange = (val) => {
  paginationData.limit = val;
  getData();
};
const handleCurrentChange = (val) => {
  paginationData.page = val;
  inputPage.value = val;
  getData();
};
const jumpFun = (event) => {
  event.target.blur();
  var that = this;
  if (inputPage.value <= 0) {
    inputPage.value = 1;
  }
  if (inputPage.value > paginationData.totalPage) {
    inputPage.value = paginationData.totalPage;
  }
  paginationData.page = inputPage.value;
  getData();
};
</script>
<style scoped>
.pageCon {
  width: 100%;
  float: left;
  margin-top: 20px;
  display: flex;
  justify-content: center;
}
.myCarTopPage {
  box-sizing: border-box;
  overflow: hidden;
  padding-bottom: 20px;
}
.details div:first-child {
  font-size: 16px;
  color: #2b68cd;
  margin-bottom: 11px;
}
.details div:nth-child(2) {
  font-size: 14px;
  color: #666;
  margin: 15px 0;
}
.details div:nth-child(3) {
  margin-bottom: 11px;
  display: flex;
  align-items: center;
}
.details div:last-child {
  font-size: 14px;
  color: #666666;
}
.resonBox {
  display: flex;
  line-height: 24px;
}
.resonTxt {
  flex: 1;
  overflow: hidden;
  line-height: 24px;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  flex-direction: column;
}
.bookoneTitle {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-bottom: 10px;
  border-bottom: 1px solid #ddd;
}
.bookone {
  display: flex;
  width: 478px;
  min-height: 173px;
  float: left;
  cursor: pointer;
  box-sizing: border-box;
  margin: 20px 0px 0 20px;
  padding: 10px 30px;
  border: 1px solid #ddd;
}
.bookone:hover {
  -moz-box-shadow: 4px 3px 6px rgba(0, 0, 0, 0.3);
  -webkit-box-shadow: 4px 3px 6px rgba(0, 0, 0, 0.3);
  box-shadow: 4px 3px 6px rgba(0, 0, 0, 0.3);
}
.bookone .imgBox {
  position: relative;
  width: 120px;
  height: 160px;
  background: #fff;
}
.newBookli .imgBox {
  position: relative;
  width: 105px;
  height: 140px;
}
.imgBox img {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  margin: auto;
}
.details {
  flex: 1;
  margin-left: 10px;
  margin-top: 10px;
  overflow: hidden;
}
.pageBox {
  width: 100%;
}
.noDataTxt {
  margin: 50px auto;
  text-align: center;
  font-size: 22px;
  color: #999;
}
.el-pagination button {
  margin-left: 10px;
}
</style>
src/views/personalCenter/myCart.vue
@@ -1,3 +1,599 @@
<template>
  <div>购物车</div>
  <div class="personalPage-box">
    <div class="personalPage-title">我的购物车</div>
    <div class="personalPage-content">
      <div class="deleteBox">
        <el-dialog
          v-model="dialogVisible"
          width="30%"
          draggable
          align-center
          :modal="false"
        >
          <span>
            <el-icon style="color: orange"> <Warning /> </el-icon
            >确认要删除选中的商品吗?</span
          >
          <template #footer>
            <span class="dialog-footer">
              <el-button @click="dialogVisible = false">取消</el-button>
              <el-button type="primary" @click="handleDelete"> 确定 </el-button>
            </span>
</template>
        </el-dialog>
        <el-dialog v-model="showHide" width="30%" draggable align-center :modal="false">
          <span
            ><el-icon style="color: orange"> <Warning /> </el-icon
            >确认要删除选中的商品吗?</span
          >
          <template #footer>
            <span class="dialog-footer">
              <el-button @click="showHide = false">取消</el-button>
              <el-button type="primary" @click="confirmEvent"> 确定 </el-button>
            </span>
          </template>
        </el-dialog>
      </div>
      <div class="breadcrumbsBox">
        <p>
          位置: <span>购物车({{ total }})</span>
        </p>
      </div>
      <div class="selectproduct">
        <el-table
          ref="multipleTableRef"
          :data="shoppingCartData"
          style="width: 100%"
          @selection-change="handleSelectionChange"
          :cell-style="cellStyle"
          v-loading="loading"
        >
          <template v-slot:empty>
            <el-empty class="noData" image-size="100" description="暂无数据" />
          </template>
          <el-table-column type="selection" width="30" />
          <el-table-column label="全选" width="200">
            <template #default="scope">
              <div style="position: relative; width: 110px">
                <el-image
                  :src="scope.row.imgUrl ? scope.row.imgUrl : defaultImg"
                  class="bookImg"
                >
                </el-image>
                <div
                  class="labelBox"
                  :style="{
                    background: scope.row.type == 'product' ? '#019e58 ' : '#5f92fd',
                  }"
                >
                  <p>{{ scope.row.typeTxt }}</p>
                </div>
              </div>
            </template>
          </el-table-column>
          <el-table-column
            property="name"
            label="商品信息"
            width="300"
            :cell-style="{ margin: '30px' }"
          />
          <el-table-column property="productType" label="商品类型" width="300" />
          <el-table-column label="价格">
            <template #default="scope">¥{{ scope.row.unitprice.toFixed(2) }}</template>
          </el-table-column>
          <el-table-column label="操作">
            <template #default="scope">
              <span @click="dialog(scope.$index, scope.row)" class="deleteBtn">删除</span>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </div>
    <div class="settlement">
      <div class="box">
        <div class="gaugeOutfit">
          <div class="leftBox">
            <el-checkbox
              v-if="shoppingCartData.length"
              class="checkbox"
              v-model="selectAll"
              label="全选"
              name="type"
              @change="toggleAllSelection"
            />
            <el-checkbox
              v-else
              class="checkbox"
              v-model="select"
              disabled
              label="全选"
              name="type"
              @change="toggleAllSelection"
            />
            <el-button class="buttonBox" @click="batchDelete">批量删除</el-button>
          </div>
          <div class="rightBox">
            <p>
              已选择 <span>{{ selectedItemCount }}</span> 件商品
            </p>
            <p>
              总价:<span v-if="sumUnitprice">¥{{ sumUnitprice.toFixed(2) }}</span>
              <span v-else>¥0.00</span>
            </p>
            <el-button class="button" type="warning" @click="goPaymentPage"
              >结算</el-button
            >
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script setup lang="ts">
import { onMounted, ref, inject, watch } from "vue";
import { ElTable, ElMessage } from "element-plus";
import { reactive } from "vue";
import { useRouter } from "vue-router";
import { InfoFilled } from "@element-plus/icons-vue";
import { useUserStore, useBreadcrumbStore } from "@/store";
import { getPublicImage } from "@/assets/js/middleGround/tool.js";
import defaultImg from "@/assets/images/default-book-img.png";
const dialogVisible = ref(false);
const crumbStore = useBreadcrumbStore();
const router = useRouter();
const userStore = useUserStore();
const MG = inject("MG");
const total = ref();
const multipleTableRef = ref();
const loading = ref(true);
const selectAll = ref(false);
const multipleSelection = ref([]);
const orderNumber = ref();
const select = ref(false);
const showHide = ref(false);
const selectedItemCount = ref(0); // 新增一个变量用于存储已选商品数量
const sumUnitprice = ref();
watch(multipleSelection, (newSelection) => {
  // 当 multipleSelection.value 发生变化时触发的回调函数
  let sum = 0;
  newSelection.forEach((item) => {
    sum += item.unitprice;
  });
  sumUnitprice.value = sum;
  selectedItemCount.value = newSelection.length;
});
onMounted(() => {
  shoppingCartGet();
  // totalPrice()
});
const batchDelete = (evt) => {
  let target = evt.target;
  if (target.nodeName == "SPAN") {
    target = evt.target.parentNode;
  }
  target.blur();
  if (multipleSelection.value.length === 0) {
    // 如果没有选择任何商品,可以在此处给出提示或者直接返回
    ElMessage({
      message: "未选择商品",
      type: "warning",
    });
  } else {
    showHide.value = true;
    // showHide.value = false
  }
};
const handleSelectionChange = (val) => {
  // console.log(val);
  multipleSelection.value = val;
  // 判断是否全部选择
  if (!delShoppingSelec.value) {
    if (val.length === shoppingCartData.length) {
      selectAll.value = true;
    } else {
      selectAll.value = false;
    }
  }
};
// 切换所有行选中状态的方法
const toggleAllSelection = () => {
  if (shoppingCartData.length === 0) {
    selectAll.value = false;
    ElMessage({
      message: "没有可选择的商品",
      type: "warning",
    });
  } else {
    multipleTableRef.value.toggleAllSelection();
  }
};
const selectedRow = ref();
const dialog = (index, row) => {
  dialogVisible.value = true;
  // 将当前行数据存储起来,以备删除时使用
  selectedRow.value = row;
};
const handleDelete = () => {
  const row = selectedRow.value;
  dialogVisible.value = false;
  MG.store
    .delShoppingCart({
      ids: [row.id],
    })
    .then((res) => {
      shoppingCartGet();
      ElMessage({
        message: "删除成功",
        type: "success",
      });
      //更新购物车数量
      userStore.updateRightPop();
    })
    .catch((error) => {
      ElMessage.error("删除失败");
    });
};
//表单的样式
const cellStyle = ({ row, column, rowIndex, columnIndex }) => {
  if (columnIndex === 4) {
    return { color: "#FF6C00" };
  }
};
const shoppingCartData = reactive([]);
const shoppingCartGet = () => {
  let query = {
    start: 0,
    size: 999,
    filterList: [],
    searchList: [],
  };
  MG.store.getShoppingCartProductList(query).then((res) => {
    const newData = res.datas.map((item) => {
      console.log(item.saleMethod.type, "item.saleMethod.type");
      if (item.productMonWithLinkDto.links[0].storeRefCode == "jsek_digitalTextbooks") {
        item.typeTxt = "数字教材";
        item.productType = "数字教材";
      } else if (
        item.productMonWithLinkDto.links[0].storeRefCode == "jsek_digitalCourses"
      ) {
        item.typeTxt = "数字课程";
        item.productType = "数字课程";
      } else {
        item.typeTxt = "电子书";
        item.productType = "图书服务-电子书";
      }
      // console.log(item.saleMethod.id);
      console.log(item.saleMethod.type, "item.saleMethod.type");
      return {
        saleMethodId: item.saleMethod.id,
        id: item.id,
        name:
          item.linkCmsItems.length && item.linkCmsItems[0].name
            ? item.productMonWithLinkDto.product.name + ":" + item.linkCmsItems[0].name
            : item.productMonWithLinkDto.product.name,
        type: item.saleMethod.type == "createProductItemSaleMethod" ? "item" : "product",
        typeTxt: item.typeTxt,
        productType: item.productType,
        imgUrl: getPublicImage(item.productMonWithLinkDto.product.icon, "", "160"),
        unitprice: item.saleMethod.price,
        expire:
          new Date(item.saleMethod.endDate).getTime() < new Date().getTime() ||
          new Date().getTime() < new Date(item.saleMethod.beginDate).getTime()
            ? true
            : false,
      };
    });
    // 重新赋值 shoppingCartData.value
    shoppingCartData.splice(0, shoppingCartData.length, ...newData);
    loading.value = false;
  });
};
//跳转面包屑
const goPaymentPage = async () => {
  try {
    // console.log(multipleSelection.value, 346588998)
    let expire = multipleSelection.value.filter((item) => item.expire == true);
    if (expire.length > 0) {
      ElMessage({
        message: "您选择的商品有不在有效期内的,请重新选择可购买商品!",
        type: "warning",
      });
    } else {
      const selectedIds = multipleSelection.value.map((item) => item.id);
      const saleMethodId = multipleSelection.value.map((item) => item.saleMethodId);
      console.log(saleMethodId, 789);
      let queryCreateOrder = { linkIds: selectedIds };
      const createOrderResult = await MG.store.shoppingCartCreateOrder(queryCreateOrder);
      orderNumber.value = createOrderResult.orderNumber;
      if (selectedIds.length) {
        let crumbs = {
          name: "订单支付", // 面包屑名称
          pathName: "paymentPage", // 面包屑跳转路由,可传递 pathName 或 path
          isCrumbs: true, // 面包屑点击跳转时是否创建新的面包屑记录
          type: "shoppingCart", // 如果需要创建新的面包屑记录,创建的type
        };
        // 在全局数据中设置面包屑
        crumbStore.setCrumbs({
          type: "shoppingCart",
          data: [crumbs],
          callback: (key) => {
            router.push({
              name: "paymentPage",
              query: {
                crumbsKey: key,
                orderNumber: orderNumber.value,
                // type: route.query.type,
                type: "shoppingCart",
                onNorderSaleMethod: saleMethodId,
              },
            });
          },
        });
      } else {
        ElMessage({
          message: "请选择商品",
          type: "warning",
        });
      }
    }
  } catch (error) {
    console.error(error);
    // 错误处理逻辑
  }
};
const delShoppingSelec = ref();
const confirmEvent = () => {
  showHide.value = false;
  if (multipleSelection.value.length === 0) {
    // 如果没有选择任何商品,可以在此处给出提示或者直接返回
    return;
  } else {
    const selectedIds = multipleSelection.value.map((item) => item.id);
    MG.store
      .delShoppingCart({ ids: selectedIds })
      .then((res) => {
        delShoppingSelec.value = res;
        ElMessage({
          message: "批量删除成功",
          type: "success",
        });
        selectAll.value = false;
        // 删除成功后,刷新购物车列表
        shoppingCartGet();
        //更新购物车数量
        userStore.updateRightPop();
      })
      .catch((error) => {
        console.error("批量删除失败", error);
        ElMessage.error("批量删除失败");
      });
  }
};
</script>
<style lang="less" scoped>
.deleteBtn {
  cursor: pointer;
  color: #019e58;
}
page {
  position: relative;
  min-height: calc(100vh - 430px);
}
.bookImg {
  box-shadow: 0px 3px 6px 1px rgba(0, 0, 0, 0.16);
  /deep/ .el-image__inner {
    object-fit: contain !important;
  }
}
.settlement {
  width: 100%;
  box-shadow: 0px -2px 10px 1px rgba(0, 0, 0, 0.08);
  position: sticky;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #fff;
  z-index: 2;
  .box {
    position: relative;
    .gaugeOutfit {
      padding-left: 12px;
      box-shadow: 0px -2px 5px 1px rgba(0, 0, 0, 0.02);
      display: flex;
      justify-content: space-between;
      align-items: center;
      position: sticky;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      z-index: 2;
      background-color: #fff;
      height: 60px;
      .leftBox {
        display: flex;
        flex-direction: row;
        .checkbox {
          margin-right: 20px;
        }
        /deep/ .el-checkbox__input.is-checked + .el-checkbox__label {
          color: #000 !important;
        }
      }
      .rightBox {
        display: flex;
        flex-direction: row;
        align-items: center;
        p {
          margin: 0 10px;
          font-size: 14px;
          span {
            color: #019e58;
            font-size: 18px;
          }
        }
        .button {
          margin-left: 50px;
          background-color: #019e58;
          width: 150px;
          height: 50px;
        }
      }
    }
    .selected {
      display: flex;
      justify-content: center;
      height: 200px;
      min-width: 1370px;
      width: 1370px;
      margin: 0 auto;
    }
  }
}
.selectproduct {
  margin-top: 30px;
}
::v-deep {
  .el-checkbox__label {
    margin-left: 6px;
    color: #000;
    font-weight: bold;
  }
  .el-table td.el-table__cell div {
    margin-right: 20px;
  }
  .el-table th {
    background-color: #f3f3f3;
    color: #000;
  }
  .el-table td {
    color: #333333;
  }
  .el-table--enable-row-transition .el-table__body td.el-table__cell {
    height: 180px;
  }
  .el-checkbox__inner {
    width: 16px;
    height: 16px;
  }
  .el-image__inner {
    box-shadow: 0px 0px 20px 1px #ccc;
    object-fit: contain !important;
    width: 110px;
    height: 140px;
  }
  .el-icon {
    margin-right: 10px;
    font-size: 20px;
    position: relative;
    top: 3px;
    left: 0px;
  }
  .deleteBox {
    .el-dialog.is-align-center {
      width: 350px;
    }
    .el-dialog__body {
      display: flex;
      justify-content: center;
      font-size: 16px;
    }
  }
  .el-table__inner-wrapper::before {
    height: 0px;
  }
}
.labelBox {
  // width: 50px;
  padding: 0 4px;
  height: 25px;
  color: #fff;
  position: absolute;
  top: 0;
  right: 0;
  margin: 0 !important;
  border-radius: 0px 0px 0px 5px;
  p {
    display: flex;
    justify-content: center;
  }
}
.breadcrumbsBox {
  height: 60px;
  border-bottom: 1px solid #f5f5f5;
  p {
    line-height: 60px;
    font-size: 14px;
    color: #545c63;
  }
}
.noData {
  font-size: 26px;
  font-weight: bold;
  display: flex;
  justify-content: center;
  margin: 0 auto;
}
</style>
src/views/personalCenter/myCollection.vue
@@ -1,3 +1,349 @@
<template>
  <div>我的收藏</div>
  <!-- <page> -->
  <div class="personalPage-box">
    <div class="personalPage-title">我的收藏</div>
    <div class="personalPage-content">
      <div class="cartClass">
        <el-tabs v-model="currentCollect" type="capsule" @tab-click="tabCart">
          <el-tab-pane label="数字教材" name="textBooks"></el-tab-pane>
        </el-tabs>
      </div>
      <div class="myCarTopPage">
        <div class="list-box" v-loading="pages.loading">
          <div
            :class="
              currentCollect == 'book' || currentCollect == 'textBooks'
                ? 'bookCartContent cartContent'
                : currentCollect == 'course'
                ? 'courseCartContent cartContent'
                : 'cartContent'
            "
          >
            <div
              class="collectList flex jc-sb clear"
              v-if="collectList.length > 0 && !pages.loading"
            >
              <div
                v-for="(item, index) in collectList"
                :key="index"
                class="collectList-item fl"
              >
                <div class="cover" @click="goBookDetails(item.id, item.name)">
                  <img :src="item.icon" alt="" />
                </div>
                <div class="info" @click="goBookDetails(item.id, item.name)">
                  <span>{{ item.name }}</span>
                </div>
                <div class="currentBtn hover" @click="setCoolect(item)">
                  <img
                    src="@/assets/images/personalCenter/collect-click.png"
                    alt="star"
                  />
                </div>
              </div>
            </div>
            <div v-if="collectList.length == 0 && !pages.loading">
              <el-empty :image-size="200" description="暂无数据" />
            </div>
          </div>
          <div class="pagination-box">
            <el-pagination
              v-model:current-page="pages.page"
              v-model:page-size="pages.pageSize"
              :disabled="disabled"
              :background="background"
              layout="total, prev, pager, next"
              :total="pages.count"
              @current-change="handleCurrentChange"
              v-if="collectList.length > 0 && !pages.loading"
            />
          </div>
        </div>
      </div>
    </div>
  </div>
  <!-- </page> -->
</template>
<script setup lang="ts">
import { reactive, ref, onMounted, inject, watch } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { useBreadcrumbStore, useUserStore } from '@/store'
import { useRouter } from "vue-router";
const crumbStore = useBreadcrumbStore()
const userStore = useUserStore()
const router = useRouter();
const MG: any = inject("MG");
const config: any = inject("config");
let currentCollect = ref("book");
let collectList = ref([]);
const background = ref(false);
const disabled = ref(false);
let pages = reactive({
  page: 1,
  pageSize: 10,
  count: 0,
  loading: false,
});
let linkType = ref("FavoriteTextBooks");
const tabCart = (event: Event) => {
  pages.page = 1;
  pages.loading = true;
  collectList.value = [];
  currentCollect.value = event.props.name;
  if (currentCollect.value == "textBooks") {
    linkType.value = "FavoriteTextBooks";
  }
  getDataList();
};
function getDataList() {
  pages.loading = true;
  MG.store
    .getProductList({
      handelEBooK: true,
      queryType: "AppUserProductLink",
      linkType: linkType.value,
      paging: {
        start: pages.pageSize * pages.page - pages.pageSize,
        size: pages.pageSize,
      },
    })
    .then((res) => {
      collectList.value = res.datas;
      pages.count = res.total;
      pages.loading = false;
    })
    .catch(() => {
      pages.loading = false;
    });
}
onMounted(() => {
  getDataList();
});
// watch(
//   () => userStore.token,
//   () => {
//     getDataList()
//   }
// )
const handleCurrentChange = (val: number) => {
  pages.page = val;
  getDataList();
};
const setCoolect = (item) => {
  ElMessageBox.confirm("确定要取消收藏吗?", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    autofocus: false,
    type: "warning",
  })
    .then(() => {
      MG.store
        .delProductLink({
          productIds: [item.id],
          linkType: linkType.value,
        })
        .then(() => {
          ElMessage({
            message: "收藏已取消!",
            type: "success",
          });
          pages.page = 1;
          getDataList();
        });
    })
    .catch(() => {});
};
// 跳转书本详情
const goBookDetails = (id: number, name: string) => {
  let crumbs = [
    {
      name,
      isCrumbs: true,
      type:
        currentCollect.value == "book"
          ? "bookService"
          : currentCollect.value == "textBooks"
          ? "digitalTextbooks"
          : "digitalCourses",
      path:
        currentCollect.value == "book"
          ? "/bookService/details"
          : "/digitalCoursesDetails",
    },
  ];
  // 在全局数据中设置面包屑
  crumbStore.setCrumbs({
    type:
      currentCollect.value == "book"
        ? "bookService"
        : currentCollect.value == "textBooks"
        ? "digitalTextbooks"
        : "digitalCourses",
    data: crumbs,
    callback: (key: any) => {
      router.push({
        name:
          currentCollect.value == "book"
            ? "bookDetails"
            : currentCollect.value == "textBooks"
            ? "digitalTextbooksDetails"
            : "digitalCoursesDetails",
        query: {
          crumbsKey: key,
          bookId: id,
          bookName: name,
          type:
            currentCollect.value == "book"
              ? "bookService"
              : currentCollect.value == "textBooks"
              ? "digitalTextbooks"
              : "digitalCourses",
        },
      });
    },
  });
};
</script>
<style lang="less" scoped>
.cartClass {
  ::v-deep .el-tabs__nav-wrap::after {
    background-color: #019e58;
    height: 1px;
  }
  ::v-deep .el-tabs__item {
    width: 100px;
    padding: 0;
    color: #545c63;
  }
  ::v-deep .is-active {
    background-color: #019e58;
    color: #fff;
    border-radius: 3px 3px 0 0;
  }
}
.collectList {
  // overflow: hidden;
  justify-content: flex-start;
  flex-wrap: wrap;
}
.bookCartContent {
  .collectList-item {
    width: 130px;
    cursor: pointer;
    box-sizing: border-box;
    float: left;
    position: relative;
    margin-right: 38px;
    .cover {
      width: 100%;
      height: 180px;
      box-shadow: 0px 0px 20px 1px #ccc;
      img {
        width: 100%;
        height: 100%;
        object-fit: contain;
      }
    }
    .info {
      height: 90px;
      padding: 15px 0;
      width: 100%;
      span {
        font-weight: bold;
        height: 45px;
        line-height: 22px;
        display: -webkit-box;
        margin-bottom: 5px;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 2;
        overflow: hidden;
        text-overflow: ellipsis;
      }
    }
  }
  .collectList-item:nth-child(5),
  :nth-child(10) {
    margin-right: 0;
  }
}
.courseCartContent {
  .collectList-item {
    width: 28%;
    cursor: pointer;
    box-sizing: border-box;
    float: left;
    margin-right: 5%;
    box-shadow: 0px 0px 20px 1px #ccc;
    position: relative;
    .cover {
      width: 100%;
      height: 130px;
      img {
        width: 100%;
        height: 100%;
        object-fit: contain;
      }
    }
    .info {
      height: 70px;
      padding: 15px;
      width: 100%;
      span {
        font-weight: bold;
        height: 45px;
        line-height: 22px;
        display: -webkit-box;
        margin-bottom: 5px;
        -webkit-box-orient: vertical;
        -webkit-line-clamp: 2;
        overflow: hidden;
        text-overflow: ellipsis;
      }
    }
  }
}
.currentBtn {
  width: 20px;
  height: 20px;
  padding: 2px;
  background-color: #fff;
  position: absolute;
  top: 10px;
  right: 10px;
  img {
    width: 16px;
    height: 16px;
  }
}
.cartContent {
  min-height: 495px;
}
.pagination-box {
  display: flex;
  justify-content: center;
}
</style>
src/views/personalCenter/myMessage.vue
@@ -1,3 +1,206 @@
<template>
  <div>我的消息</div>
  <!-- <page> -->
  <div class="personalPage-box">
    <div class="personalPage-title">我的消息</div>
    <div class="personalPage-content">
      <div class="list-box" v-loading="pages.loading">
        <ul class="listTable" v-if="dataList.length > 0 && !pages.loading">
          <li v-for="(item, index) in dataList" :key="index" class="body flex">
            <div class="icon">
              <img src="@/assets/images/personalCenter/notification.svg" alt="star" />
            </div>
            <div class="flex1 ai-c">
              <p class="title hover" :title="item.name" @click="viewDetail(item)">
                {{ item.name }}
              </p>
              <p class="content" :title="item.description">{{ item.description }}</p>
            </div>
            <span class="createDate">{{ item.createDate }}</span>
          </li>
        </ul>
        <div v-if="dataList.length == 0 && !pages.loading">
          <el-empty :image-size="200" description="暂无数据" />
        </div>
      </div>
      <div class="pagination-box" v-if="dataList.length > 0 && !pages.loading">
        <el-pagination
          v-model:current-page="pages.page"
          v-model:page-size="pages.pageSize"
          layout="total, prev, pager, next"
          :total="pages.count"
          @current-change="handleCurrentChange"
        />
      </div>
    </div>
    <el-dialog align-center v-model="detailDialog" title="消息" class="messageDialog">
      <div>
        <div class="title">{{ dataInfo.name }}</div>
        <div class="content" v-html="dataInfo.content"></div>
      </div>
    </el-dialog>
  </div>
  <!-- </page> -->
</template>
<script setup lang="ts">
import { reactive, ref, onMounted, inject, watch } from "vue";
import moment from "moment";
import { useUserStore } from "@/store";
const userStore = useUserStore();
const MG: any = inject("MG");
const config: any = inject("config");
let dataList = ref([]);
let pages = reactive({
  page: 1,
  pageSize: 10,
  count: 0,
  loading: false,
});
const detailDialog = ref(false);
let dataInfo = reactive({
  name: "",
  content: "",
});
function getDataList() {
  pages.loading = true;
  MG.app
    .getAppMessageList({
      appRefCode: config.appRefCode,
      start: (pages.page - 1) * pages.pageSize,
      size: pages.pageSize,
      sort: {
        type: "Desc",
        field: "CreateDate",
      },
    })
    .then((res) => {
      pages.count = res.totalSize;
      res.datas.forEach((item) => {
        item.createDate = moment(item.createDate).format("YYYY-MM-DD HH:mm:ss");
      });
      dataList.value = res.datas;
      pages.loading = false;
    })
    .catch(() => {
      pages.loading = false;
    });
}
onMounted(() => {
  getDataList();
});
watch(
  () => userStore?.token,
  () => {
    getDataList();
  }
);
const handleCurrentChange = (val: number) => {
  pages.page = val;
  getDataList();
};
function viewDetail(data) {
  MG.app
    .getMessage({
      messageId: data.id,
    })
    .then((res) => {
      if (res) {
        dataInfo.name = res.name;
        dataInfo.content = res.content;
        detailDialog.value = true;
      }
    });
}
</script>
<style lang="less" scoped>
.listTable {
  .body {
    padding: 15px 0;
    border-bottom: 1px solid #ededed;
    .icon {
      width: 36px;
      img {
        margin-top: 2px;
        width: 22px;
        height: 22px;
      }
    }
    .title {
      font-weight: bold;
      line-height: 24px;
      height: 24px;
      display: -webkit-box;
      -webkit-box-orient: vertical;
      -webkit-line-clamp: 1;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    .content {
      height: 45px;
      line-height: 24px;
      display: -webkit-box;
      margin: 5px 0;
      -webkit-box-orient: vertical;
      -webkit-line-clamp: 2;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    .createDate {
      line-height: 24px;
      padding-left: 20px;
      color: #949494;
    }
  }
}
.list-box {
  min-height: 570px;
}
.pagination-box {
  display: flex;
  justify-content: center;
}
.messageDialog {
  width: 600px;
  .title {
    line-height: 22px;
    font-weight: bold;
  }
  .content {
    margin-top: 10px;
    line-height: 22px;
  }
}
</style>
<style lang="less">
.messageDialog {
  .el-dialog__header {
    padding: 15px;
    margin-right: 0;
    border-bottom: 1px solid #f4f4f4;
  }
  .el-dialog__title {
    font-weight: bold;
    font-size: 16px;
  }
  .el-dialog__headerbtn {
    top: 6px;
    right: 6px;
  }
}
</style>
src/views/personalCenter/myOrder.vue
@@ -1,3 +1,495 @@
<template>
  <div>我的订单</div>
  <!-- <page> -->
  <div class="personalPage-box">
    <div class="personalPage-title">我的订单</div>
    <div class="personalPage-content">
      <div class="cartClass">
        <el-tabs v-model="order" type="capsule" @tab-click="tabCart">
          <el-tab-pane label="全部" name="all"></el-tab-pane>
          <el-tab-pane label="待支付" name="payment"></el-tab-pane>
          <el-tab-pane label="已完成" name="complete"></el-tab-pane>
          <el-tab-pane label="已取消" name="cancellation"></el-tab-pane>
        </el-tabs>
      </div>
      <div class="myCarTopPage">
        <div class="cartContent" v-loading="pages.loading">
          <div class="list-box">
            <ul class="listTable" v-if="dataList.length > 0 && !pages.loading">
              <li class="head flex">
                <span class="index">序号</span>
                <span class="clear flex1">商品信息</span>
                <span class="state">商品类型</span>
                <span class="price">价格</span>
              </li>
              <li v-for="item in dataList" :key="item.index">
                <div class="body flex ai-c">
                  <div class="index">{{ item.index }}</div>
                  <div class="list">
                    <div
                      v-for="pItem in item.productList"
                      :key="pItem.id"
                      class="listItem flex ai-c"
                    >
                      <div
                        class="clear hover"
                        @click="
                          goBookDetails(
                            pItem.orderSaleMethod.product.id,
                            pItem.orderSaleMethod.product.name,
                            pItem.orderSaleMethod.product.cmsTypeRefCode,
                            item.remarks,
                            pItem.orderSaleMethod.id
                          )
                        "
                      >
                        <div class="cover fl">
                          <img
                            :src="
                              pItem.orderSaleMethod.product.icon
                                ? getPublicImage(
                                    pItem.orderSaleMethod.product.icon,
                                    '',
                                    ''
                                  )
                                : bookCover
                            "
                            alt=""
                          />
                        </div>
                        <div class="title flex ai-c">
                          {{
                            pItem.orderSaleMethod.type === "defaultSaleMethod" ||
                            pItem.orderSaleMethod.cmsItemList.length == 0
                              ? pItem.orderSaleMethod.product.name
                              : pItem.orderSaleMethod.product.name +
                                ":" +
                                pItem.orderSaleMethod.cmsItemList[0].name
                          }}
                        </div>
                      </div>
                      <span class="state">{{
                        pItem.orderSaleMethod.product.cmsTypeRefCode ==
                        "jsek_digitalTextbooks"
                          ? "数字教材"
                          : pItem.orderSaleMethod.product.cmsTypeRefCode ==
                            "jsek_digitalCourses"
                          ? "数字课程"
                          : pItem.orderSaleMethod.type == "defaultSaleMethod"
                          ? "图书服务-电子书"
                          : pItem.orderSaleMethod.type == "createProductSaleMethod" &&
                            pItem.orderSaleMethod.cmsItemList == 0
                          ? "图书服务-组卷"
                          : pItem.orderSaleMethod.cmsItemList[0].type ==
                            "questionBankFolder"
                          ? "图书服务-云测试"
                          : "图书服务-云学习"
                      }}</span>
                      <div class="price">
                        <span>¥{{ pItem.payPrice.toFixed(2) }}</span>
                      </div>
                    </div>
                  </div>
                </div>
                <div class="count">
                  <span>订单号: {{ item.orderNumber }}</span>
                  <span v-if="item.createDate">
                    创建时间:
                    <span>{{ item.createDate.slice(0, 10) }}</span>
                    <span style="margin-left: 5px">{{
                      item.createDate.slice(11, 19)
                    }}</span>
                  </span>
                  <span class="right">
                    <span
                      >总计:<span class="main"
                        >¥{{ item.totalPrice.toFixed(2) }}</span
                      ></span
                    >
                    <span class="status yes" v-if="item.state == 'Success'">已完成</span>
                    <span class="status cancel" v-if="item.state == 'Cancel'"
                      >已取消</span
                    >
                    <span class="status cancel" v-if="item.state == 'ReFoundFinished'"
                      >已退款</span
                    >
                    <span class="status" v-if="item.state == 'WaitPay'">
                      <span class="main hover" @click="toPay(item.orderNumber)"
                        >立即支付</span
                      >
                      <span class="grey hover" @click="cancleOrder(item.orderNumber)"
                        >取消订单</span
                      >
                    </span>
                  </span>
                </div>
              </li>
            </ul>
            <div v-if="dataList.length == 0 && !pages.loading">
              <el-empty :image-size="200" description="暂无数据" />
            </div>
          </div>
          <div class="pagination-box" v-if="dataList.length > 0 && !pages.loading">
            <el-pagination
              v-model:current-page="pages.page"
              v-model:page-size="pages.pageSize"
              layout="total, prev, pager, next"
              :total="pages.count"
              @current-change="handleCurrentChange"
            />
          </div>
        </div>
      </div>
    </div>
  </div>
  <!-- </page> -->
</template>
<script setup lang="ts">
import { reactive, ref, onMounted, inject, watch } from "vue";
import { ElMessage } from "element-plus";
import { getPublicImage } from "@/assets/js/middleGround/tool.js";
import { useRouter } from "vue-router";
import { useBreadcrumbStore, useUserStore } from "@/store";
import bookCover from "@/assets/images/personalCenter/book-cover.png";
const router = useRouter();
const crumbStore = useBreadcrumbStore();
const userStore = useUserStore();
const MG: any = inject("MG");
let order = ref("all");
let dataList = ref([]);
let queryFilter = reactive([]);
let pages = reactive({
  page: 1,
  pageSize: 5,
  count: 0,
  loading: false,
});
const tabCart = (event: Event) => {
  order.value = event.props.name;
  pages.page = 1;
  dataList.value = [];
  if (order.value == "all") {
    queryFilter.value = [];
  }
  if (order.value == "payment") {
    queryFilter.value = [{ field: "State", value: "WaitPay" }];
  }
  if (order.value == "complete") {
    queryFilter.value = [{ field: "State", value: "Success" }];
  }
  if (order.value == "cancellation") {
    queryFilter.value = [{ field: "State", value: "Cancel" }];
  }
  getDataList();
};
function getDataList() {
  pages.loading = true;
  const data = {
    start: pages.pageSize * pages.page - pages.pageSize,
    size: pages.pageSize,
    filterList: queryFilter.value,
    sort: {
      type: "Desc",
      field: "CreateDate",
    },
  };
  MG.store
    .getUserOrderList(data)
    .then((res) => {
      res.datas.forEach((item, index) => {
        item.index =
          pages.page == 1 ? index + 1 : pages.pageSize * (pages.page - 1) + (index + 1);
        item.productList = item.saleMethodLinks;
        item.time = item.createDate.slice(0, 10) + +item.createDate.slice(11, 20);
      });
      pages.count = res.totalSize;
      dataList.value = [...res.datas];
      console.log("订单列表", res.datas);
      pages.loading = false;
    })
    .catch(() => {
      pages.loading = false;
    });
}
onMounted(() => {
  getDataList();
});
// watch(
//   () => userStore.token,
//   () => {
//     getDataList()
//   }
// )
const handleCurrentChange = (val: number) => {
  pages.page = val;
  getDataList();
};
// 跳转书本详情
const goBookDetails = async (
  id: number,
  name: string,
  refCode: string,
  remarks: string,
  orderSaleMethodId: string
) => {
  let parentData = null;
  let bookId = id;
  if (refCode == "digitalCourses") {
    let crumbs = [
      {
        name,
        isCrumbs: true,
        type: "digitalCourses",
        path: "digitalCoursesDetails",
      },
    ];
    // 在全局数据中设置面包屑
    crumbStore.setCrumbs({
      type: "digitalCourses",
      data: crumbs,
      callback: (key: any) => {
        router.push({
          name: "digitalCoursesDetails",
          query: {
            crumbsKey: key,
            bookId: bookId,
            bookName: name,
            type: "digitalCourses",
          },
        });
      },
    });
  } else if (refCode == "digitalTextbooks") {
    let crumbs = [
      {
        name,
        isCrumbs: true,
        type: "digitalTextbooks",
        path: "digitalTextbooksDetails",
      },
    ];
    // 在全局数据中设置面包屑
    crumbStore.setCrumbs({
      type: "digitalTextbooks",
      data: crumbs,
      callback: (key: any) => {
        router.push({
          name: "digitalTextbooksDetails",
          query: {
            crumbsKey: key,
            bookId: bookId,
            bookName: name,
            type: "digitalTextbooks",
          },
        });
      },
    });
  } else {
    parentData = await MG.store.getProductBySaleMethod({
      saleMethodId: orderSaleMethodId,
    });
    if (parentData.parentProduct.length > 0) {
      bookId = parentData.parentProduct[parentData.parentProduct.length - 1].id;
    }
    console.log(bookId, "bookId");
    let crumbs = [
      {
        name,
        isCrumbs: true,
        type: "bookService",
        path: "/bookService/details",
      },
    ];
    // 在全局数据中设置面包屑
    crumbStore.setCrumbs({
      type: "bookService",
      data: crumbs,
      callback: (key: any) => {
        router.push({
          name: "bookDetails",
          query: {
            crumbsKey: key,
            bookId: bookId,
            bookName: name,
            type: "bookService",
          },
        });
      },
    });
  }
};
//立即支付
const toPay = (orderNo) => {
  let crumbs: any[] = [
    {
      name: "我的订单", // 面包屑名称
      pathName: "myOrder", // 面包屑跳转路由,可传递 pathName 或 path
      isCrumbs: true, // 面包屑点击跳转时是否创建新的面包屑记录
      type: "personalCenter", // 如果需要创建新的面包屑记录,创建的type
      query: {
        type: "personalCenter",
      },
    },
  ];
  crumbs.push({
    name: "订单详情",
  });
  // 在全局数据中设置面包屑
  crumbStore.setCrumbs({
    type: "personalCenter",
    data: crumbs,
    callback: (key: any) => {
      router.push({
        path: "/paymentPage", //要跳转的页面
        query: {
          crumbsKey: key,
          orderNum: orderNo,
          type: "personalCenter",
        },
      });
    },
  });
};
//取消订单
const cancleOrder = (orderNum) => {
  MG.store.cancelOrder({ orderNum: orderNum }).then(() => {
    ElMessage({
      message: "订单已取消",
      type: "success",
    });
    getDataList();
  });
};
</script>
<style lang="less" scoped>
.cartClass {
  ::v-deep .el-tabs__nav-wrap::after {
    background-color: #019e58;
    height: 1px;
  }
  ::v-deep .el-tabs__item {
    width: 100px;
    padding: 0;
    color: #545c63;
  }
  ::v-deep .is-active {
    background-color: #019e58;
    color: #fff;
    border-radius: 3px 3px 0 0;
  }
}
.listTable {
  .head {
    font-weight: bold;
    padding: 12px 10px;
    background-color: #f3f3f3;
    .flex1 {
      padding: 0 50px;
    }
  }
  .body {
    padding: 15px 10px;
  }
  li {
    line-height: 23px;
    border-bottom: 1px solid #f4f4f4;
    .index {
      // padding: 0 20px;
      text-align: center;
      width: 80px;
      flex-shrink: 0;
    }
    .list {
      .listItem {
        padding-bottom: 10px;
        .clear {
          width: 400px;
          display: flex;
          align-items: center;
        }
      }
      :last-child {
        padding-bottom: 0px;
      }
    }
    .cover {
      box-shadow: 0px 0px 20px 1px #ccc;
      margin-right: 20px;
      width: 130px;
      height: 180px;
      img {
        width: 100%;
        height: 100%;
        object-fit: contain;
      }
    }
    .title {
      height: 180px;
      line-height: 180px;
    }
    .state {
      padding: 0 20px;
      width: 230px;
    }
    .price {
      text-align: right;
      padding: 0 20px;
      // width: 100px;
      color: #019e58;
    }
    .count {
      // text-align: right;
      display: flex;
      justify-content: space-between;
      padding: 10px;
      background: rgba(205, 207, 219, 0.14);
      .right {
        float: right;
      }
      .cancel {
        color: #949494;
      }
      .status {
        margin-left: 40px;
        span {
          padding: 0 10px;
        }
      }
    }
  }
}
.list-box {
  min-height: 495px;
}
.pagination-box {
  display: flex;
  justify-content: center;
}
</style>
src/views/personalCenter/teacherCertification.vue
New file
@@ -0,0 +1,858 @@
<template>
  <div>
    <el-dialog
      align-center
      destroy-on-close
      :close-on-click-modal="false"
      v-model="teacherDialog"
      title="教师认证申请"
      class="myDialog"
      @close="closeDialog(teacherFormRef)"
    >
      <div v-loading="loading" class="box">
        <div class="body-box">
          <div class="tipsBox">
            <p class="main">温馨提示</p>
            <p>仅限学校本课程任课教师申请;请上传有效在职教师工作证将有助于审核。</p>
          </div>
          <el-form
            ref="teacherFormRef"
            :model="teacherInfo"
            :rules="teacherRules"
            label-width="140px"
            class="teacherInfo"
            status-icon
          >
            <el-form-item label="当前状态:">
              <span class="wait" v-if="teacherInfo.state == 'WaitAudit'">等待审核</span>
              <span class="yes" v-else-if="teacherInfo.state == 'Normal'">已认证</span>
              <span class="no" v-else-if="teacherInfo.state == 'Reject'"
                ><span>已驳回</span
                ><span @click="lookReason()" class="wait hover" style="margin-left: 20px"
                  >查看原因</span
                ></span
              >
              <span class="wait" v-else>待认证</span>
            </el-form-item>
            <el-form-item label="学校:" prop="schoolName">
              <span v-if="!editState">{{ teacherInfo.schoolName || "-" }}</span>
              <el-input
                v-else
                v-model="teacherInfo.schoolName"
                autocomplete="off"
                placeholder="请输入学校"
              />
            </el-form-item>
            <el-form-item label="真实姓名:" prop="fullName">
              <span v-if="!editState">{{ teacherInfo.fullName || "-" }}</span>
              <el-input
                v-else
                v-model="teacherInfo.fullName"
                autocomplete="off"
                placeholder="请输入真实姓名"
              />
            </el-form-item>
            <el-form-item label="职称:">
              <span v-if="!editState">{{ teacherInfo.positionalTitle || "-" }}</span>
              <el-select
                v-else
                v-model="teacherInfo.positionalTitle"
                placeholder="请选择职称"
              >
                <el-option
                  v-for="item in teachPosts"
                  :key="item.value"
                  :label="item.name"
                  :value="item.name"
                ></el-option>
              </el-select>
            </el-form-item>
            <el-form-item label="任教课程:" prop="courseName">
              <span v-if="!editState">{{ teacherInfo.courseName || "-" }}</span>
              <el-input
                v-else
                v-model="teacherInfo.courseName"
                autocomplete="off"
                placeholder="请输入任教课程"
              />
            </el-form-item>
            <el-form-item label="手机号:" prop="phone">
              <span v-if="!editState">{{ teacherInfo.phone || "-" }}</span>
              <el-input
                v-else
                v-model="teacherInfo.phone"
                autocomplete="off"
                placeholder="请输入手机号"
              />
            </el-form-item>
            <el-form-item label="座机:" prop="telphone">
              <span v-if="!editState">{{ teacherInfo.telphone || "-" }}</span>
              <el-input
                v-else
                v-model="teacherInfo.telphone"
                autocomplete="off"
                placeholder="请输入座机"
              />
            </el-form-item>
            <el-form-item label="邮箱:" prop="email">
              <span v-if="!editState">{{ teacherInfo.email || "-" }}</span>
              <el-input
                v-else
                v-model="teacherInfo.email"
                autocomplete="off"
                placeholder="请输入邮箱"
              />
            </el-form-item>
            <el-form-item label="详细地址:" prop="detailedAddress">
              <span v-if="!editState">{{ teacherInfo.detailedAddress || "-" }}</span>
              <el-input
                v-else
                v-model="teacherInfo.detailedAddress"
                autocomplete="off"
                placeholder="请输入详细地址"
              />
            </el-form-item>
            <el-form-item label="在职教师工作证:" prop="relevantCertificates">
              <div class="uploadBox">
                <div class="fileList">
                  <div class="fileImgBox" v-for="(file, index) in fileList" :key="file">
                    <el-image
                      v-if="file.md5"
                      style="width: 100%; height: 100%"
                      :src="config?.requestCtx + `/file/GetPreViewImage?md5=` + file.md5"
                      :zoom-rate="1.2"
                      :max-scale="7"
                      :min-scale="0.2"
                      :hide-on-click-modal="true"
                      :preview-src-list="showFileList"
                      :initial-index="index"
                      fit="contain"
                    />
                    <el-icon
                      v-if="editState"
                      @click="handleRemove(file)"
                      color="#F56C6C"
                      style="
                        position: absolute;
                        top: -10px;
                        right: -10px;
                        font-size: 20px;
                        background: #fff;
                        border-radius: 50%;
                        cursor: pointer;
                      "
                      ><CircleCloseFilled
                    /></el-icon>
                  </div>
                  <el-upload
                    v-if="editState"
                    class="upload"
                    :http-request="fileUpload"
                    :show-file-list="false"
                    list-type="picture-card"
                    :action="'#'"
                  >
                    <el-icon>
                      <Plus />
                    </el-icon>
                  </el-upload>
                </div>
              </div>
              <div class="grey" style="font-size: 12px">教务处盖章文件、校工卡皆可</div>
            </el-form-item>
          </el-form>
          <div class="agree-msg">
            <!-- <input
              :disabled="!editState ? true : false"
              type="checkbox"
              class="checkbox"
              v-model="teacherInfo.agree"
            /> -->
            <el-checkbox
              v-model="teacherInfo.agree"
              :disabled="!editState ? true : false"
            ></el-checkbox>
            <span class="agree"
              >同意<span class="main hover" @click="dialogVisibleTecher = true"
                >《教师认证服务条款》</span
              ></span
            >
          </div>
        </div>
        <div class="footer-box">
          <span class="myDialog-footer" v-if="!loading && editState">
            <el-button @click="closeDialog(teacherFormRef)"> 取消</el-button>
            <el-button
              type="primary"
              @click="submitBtn(teacherFormRef)"
              :loading="subLoading"
            >
              提交</el-button
            >
          </span>
        </div>
      </div>
    </el-dialog>
    <!-- 教师认证服务条款 -->
    <el-dialog
      title="《教师认证服务条款》"
      v-model="dialogVisibleTecher"
      :close-on-click-modal="false"
      width="40%"
    >
      <div class="protocolBox" v-html="protocolTxt"></div>
      <template #footer>
        <span class="myDialog-footer" v-if="editState">
          <el-button type="primary" class="btn" @click="dialogVisibleTecher = false"
            >确 定</el-button
          >
        </span>
      </template>
    </el-dialog>
    <!-- 查看原因 -->
    <el-dialog
      align-center
      :close-on-click-modal="false"
      v-model="dialogReason"
      title="驳回原因"
    >
      <div class="reason" v-if="reasonTxt">
        {{ reasonTxt }}
      </div>
      <div v-else>无</div>
    </el-dialog>
  </div>
</template>
<script setup lang="ts">
import { reactive, ref, watch, defineEmits, inject, onMounted } from "vue";
import type { FormInstance, FormRules } from "element-plus";
import { ElMessage } from "element-plus";
import tool from "@/assets/js/toolClass.js";
import { getTopicMsgCmsItemFile } from "@/assets/js/middleGround/tool.js";
import { useUserStore } from "@/store";
const userStore = useUserStore();
const MG: any = inject("MG");
const config: any = inject("config");
// 证件验证
const valiCertificate = (rule: any, value: any, callback: any) => {
  if (fileList.value.length == 0) {
    callback(new Error("请上传相关证件"));
  } else {
    callback();
  }
};
// const validateTelphone = (rule: any, value: any, callback: any) => {
//   if (value !== "" && !config.reg_telphone.test(value)) {
//     callback(new Error("请输入正确格式的座机号"));
//   }
//   callback();
// };
const validatePhone = (rule: any, value: any, callback: any) => {
  if (value === "") {
    callback(new Error("请输入联系电话"));
  } else {
    if (!config.reg_tel.test(value)) {
      callback(new Error("请输入正确格式的电话"));
    }
    callback();
  }
};
const validateEmail = (rule: any, value: any, callback: any) => {
  let myreg = /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/;
  if (value === "") {
    callback(new Error("请输入电子邮箱"));
  } else {
    if (!myreg.test(value)) {
      callback(new Error("请输入正确格式的电子邮箱"));
    }
    callback();
  }
};
const props = defineProps({
  isShow: Boolean,
});
let teacherDialog = ref(false); //弹窗
let loading = ref(false);
let subLoading = ref(false);
const teachPosts = ref([]);
const teacherInfo = reactive({
  schoolName: "", //学校名称
  fullName: "", //姓名
  positionalTitle: "", //职称
  courseName: "", //任课教程
  phone: "", //联系电话
  telphone: "", //座机
  email: "", //联系邮箱
  detailedAddress: "", //通讯地址
  relevantCertificates: [], //相关证件
  state: "", //审核状态默认待审核
  agree: false,
});
const topicMessageList = ref([]);
const topicId = ref();
const worksInfo = ref([]);
const userId = ref();
const teacherFormRef = ref<FormInstance>();
interface TeacherInfo {
  schoolName: string;
  fullName: string;
  courseName: string;
  telphone: string;
  phone: string;
  email: string;
  detailedAddress: string;
  relevantCertificates: string[];
}
const teacherRules = reactive<FormRules<TeacherInfo>>({
  schoolName: [{ required: true, message: "学校名称不能为空", trigger: "blur" }],
  fullName: [{ required: true, message: "真实姓名不能为空", trigger: "blur" }],
  courseName: [{ required: true, message: "任教课程不能为空", trigger: "blur" }],
  // telphone: [{ validator: validateTelphone, trigger: "blur" }],
  phone: [{ required: true, validator: validatePhone, trigger: "blur" }],
  email: [{ required: true, validator: validateEmail, trigger: "blur" }],
  detailedAddress: [{ required: true, message: "详细地址不能为空", trigger: "blur" }],
  relevantCertificates: [
    { required: true, validator: valiCertificate, trigger: "change" },
  ],
});
const fileList = ref([]);
const editState = ref<boolean>(true);
watch(props, (newValue) => {
  // 统监听props的值变化,动态修改isShow的值
  teacherDialog.value = newValue.isShow;
  if (teacherDialog.value) {
    getpositionalTitle();
    getAgreement();
    if (localStorage.getItem(config.tokenKey)) {
      getUserRole();
    }
  }
});
// 获取职称
function getpositionalTitle() {
  const data = {
    refCodes: ["positionalTitle"],
  };
  MG.store.getProductTypeField(data).then((res) => {
    try {
      const list = res[0];
      const options = JSON.parse(list.config).option;
      teachPosts.value = options;
    } catch (error) {
      teachPosts.value = [];
    }
  });
}
// 获取登录用户身份
function getUserRole() {
  loading.value = true;
  MG.identity.getCurrentAppUser().then((res) => {
    if (res) {
      getType();
      userId.value = res.userId;
      let userInfo = res.infoList.find((item: any) => item.type == "userInfo");
      let userTypeObj = res.infoList.find((item: any) => item.type == "userType");
      const userData = {
        userName: userInfo && userInfo.data ? JSON.parse(userInfo.data).name : "",
        school: userInfo && userInfo.data ? JSON.parse(userInfo.data).school : "",
        address: userInfo && userInfo.data ? JSON.parse(userInfo.data).address : "",
        userType:
          userTypeObj && userTypeObj.data ? JSON.parse(userTypeObj.data).userType : "",
      };
      let teacherRole = res.roleLinks.find((item) => item.role.refCode == "teacher");
      let teacherInfos = res.infoList.find((item) => item.type == "teacherInfo");
      let wechatInfo = res.infoList.find((item) => item.type == "WeChat");
      let studentInfo = res.infoList.find((item) => item.type == "Default");
      let phoneInfo = res.secretList.find((item) => item.type == "MobilePhone");
      let emailInfo = res.secretList.find((item) => item.type == "EMail");
      if (teacherRole && teacherInfos) {
        userStore.setUserInfo({
          ...userData,
          ...teacherInfos,
          phoneNumber: phoneInfo?.credential,
          Email: emailInfo ? emailInfo.credential : JSON.parse(teacherInfos.data).email,
          icon: wechatInfo?.icon,
          role: "Teacher",
          roleId: teacherRole.role.id,
          userId: res.userId,
        });
      } else if (wechatInfo) {
        userStore.setUserInfo({
          ...userData,
          ...wechatInfo,
          phoneNumber: phoneInfo?.credential,
          Email: emailInfo?.credential,
          role: "Student",
          userId: res.userId,
        });
      } else if (studentInfo) {
        if (!studentInfo?.fullName) {
          teacherInfo.fullName = userStore.userInfo!.userName;
        }
        userStore.setUserInfo({
          ...userData,
          ...studentInfo,
          icon: wechatInfo?.icon,
          phoneNumber: phoneInfo?.credential,
          Email: emailInfo?.credential,
          role: "Student",
          userId: res.userId,
        });
      } else if (phoneInfo) {
        userStore.setUserInfo({
          ...userData,
          ...phoneInfo,
          name: phoneInfo?.credential,
          icon: phoneInfo?.icon,
          phoneNumber: phoneInfo?.credential,
          role: "Student",
          userId: res.userId,
        });
      }
      teacherInfo.phone = userStore.userInfo!.phoneNumber;
      teacherInfo.schoolName = userStore.userInfo!.school;
      teacherInfo.detailedAddress = userStore.userInfo!.address;
    }
  });
}
function getType() {
  const data = {
    refCodes: ["teacherCertification"],
  };
  MG.resource.getCmsTypeByRefCode(data).then((res) => {
    worksInfo.value = res[0].cmsTypeLinks[0].children;
    newGetTeacherInfo();
  });
}
// 文件上传
function fileUpload(file) {
  console.log(file, 2);
  return new Promise((resolve, reject) => {
    const isJPG = file.file.type === "image/jpeg" || file.file.type === "image/png";
    const isLt2M = (0.5 * file.file.size) / 1024 / 1024 < 0.5;
    if (!isJPG) {
      ElMessage.error("上传文件只能是 jpg/png 格式!");
      return reject();
    }
    if (!isLt2M) {
      ElMessage.error("上传文件大小不能超过 500KB!");
      return reject();
    }
    const FileName = file.file.name.split(".")[0];
    const Extension = file.file.name.split(".")[1];
    const FileType = file.file.type;
    let size = 1024;
    tool
      .getFileMd5(file.file, size * 1024)
      .then((e) => {
        console.log(e, 2);
        if (!fileList.value.find((item) => item.md5 == e)) {
          const imgData = new FormData();
          imgData.append("Md5", e);
          imgData.append("FileName", FileName);
          imgData.append("Extension", Extension);
          imgData.append("FileType", FileType);
          imgData.append("MetaData", null);
          imgData.append("file", file.file);
          MG.file.upload(imgData).then(() => {
            fileList.value.push({
              md5: e,
              linkType: "LinkFile",
              linkProtectType: "Public",
              url: config.requestCtx + `/file/GetPreViewImage?md5=` + e,
            });
          });
        } else {
          ElMessage.error("当前文件已上传,请勿重复操作!");
        }
      })
      .catch((e) => {
        console.error(e);
      });
  });
}
// 证件删除
function handleRemove(file) {
  for (let i = 0; i < fileList.value.length; i++) {
    if (fileList.value[i].md5 == file.md5) {
      fileList.value.splice(i, 1);
    }
  }
}
// 监听文件列表变化,同步修改展示列表
const showFileList = ref([]);
watch(
  fileList,
  (newValue) => {
    showFileList.value = newValue.map(
      (item) => config.requestCtx + `/file/GetPreViewImage?md5=` + item.md5
    );
  },
  { immediate: true, deep: true }
);
// 关闭弹框,回调父层方法
const emit = defineEmits(["dialogChange"]);
const closeDialog = (formEl: FormInstance | undefined) => {
  if (!formEl) return;
  formEl.resetFields();
  teacherDialog.value = false;
  emit("dialogChange", teacherDialog.value);
};
// 教师协议
const dialogVisibleTecher = ref(false);
const protocolTxt = ref("");
const getAgreement = () => {
  let query = {
    path: "protocol",
    fields: {
      content: [],
    },
  };
  MG.resource.getItem(query).then((res) => {
    try {
      const data = res.datas.find(
        (e) => e.refCode == "teacherCertificationAgreement"
      );
      protocolTxt.value = data ? data.content : "暂无协议";
    } catch (error) {
      protocolTxt.value = "暂无协议";
    }
  });
};
//教师信息
function newGetTeacherInfo() {
  const data = {
    start: 0,
    size: 10,
    topicIdOrRefCode: "teacherRoleApproval",
    appRefCode: config.appRefCode,
    sort: {
      type: "Desc",
      field: "CreateDate",
    },
  };
  MG.ugc.getTopicMessageList(data).then((res) => {
    try {
      fileList.value = [];
      const resData = res.datas.find((i) => i.appUserCreator.userId === userId.value);
      if (resData) {
        if (resData.state == "WaitAudit") {
          editState.value = false;
        } else {
          editState.value = true;
        }
        let info = getTopicMsgCmsItemFile(worksInfo.value, resData.cmsItemDataList);
        teacherInfo.fullName = info.fullName;
        teacherInfo.schoolName = info.schoolName;
        teacherInfo.positionalTitle = info.positionalTitle;
        teacherInfo.courseName = info.courseName;
        teacherInfo.phone = info.phone;
        teacherInfo.telphone = info.telphone ? info.telphone : "";
        teacherInfo.email = info.email;
        teacherInfo.detailedAddress = info.detailedAddress ? info.detailedAddress : "";
        teacherInfo.relevantCertificates = info.relevantCertificates;
        teacherInfo.agree = true;
        teacherInfo.state = resData.state;
        topicId.value = resData.id;
        topicMessageList.value = resData.cmsItemDataList;
        if (resData.feedBack != null) {
          reasonTxt.value = JSON.parse(resData.feedBack).reason;
        }
        if (teacherInfo.relevantCertificates.length > 0) {
          if (typeof teacherInfo.relevantCertificates == "object") {
            teacherInfo.relevantCertificates.forEach((ele) => {
              let imgObj = {
                md5: ele.file.md5,
                linkType: "LinkFile",
                linkProtectType: "Public",
                url: config.requestCtx + `/file/GetPreViewImage?md5=` + ele.file.md5,
              };
              fileList.value.push(imgObj);
            });
          } else {
            let imgObj = {
              md5: teacherInfo.relevantCertificates,
              linkType: "LinkFile",
              linkProtectType: "Public",
              url:
                config.requestCtx +
                `/file/GetPreViewImage?md5=` +
                teacherInfo.relevantCertificates,
            };
            fileList.value.push(imgObj);
          }
        }
        loading.value = false;
      } else {
        loading.value = false;
      }
    } catch (error) {
      loading.value = false;
    }
  });
}
//教师认证提交
const submitBtn = async (formEl: FormInstance | undefined) => {
  if (!formEl) return;
  await formEl.validate((valid, fields) => {
    if (valid) {
      if (teacherInfo.agree) {
        subLoading.value = true;
        if (topicMessageList.value.length > 0) {
          let dataRequests = tool.UpdateworksDataBytool(
            worksInfo.value,
            topicMessageList.value,
            teacherInfo,
            fileList.value
          );
          const data = {
            description: "",
            icon: "",
            id: topicId.value,
            topicIdOrRefCode: "teacherRoleApproval",
            name: teacherInfo.fullName + "",
            content: "",
            state: "WaitAudit",
            type: "teacherRegister",
            newDataRequests: dataRequests.newData,
            updateDataRequests: dataRequests.updateData,
            delDataRequest: {
              ids: [],
            },
          };
          let basicInfo = JSON.parse(JSON.stringify(teacherInfo));
          delete basicInfo.worksInfo;
          delete basicInfo.state;
          const userInfo = {
            requests: [
              {
                data: JSON.stringify(basicInfo),
                name: teacherInfo.fullName + "",
                type: "newTeacherInfo",
              },
            ],
          };
          MG.identity.setAppUserInfo(userInfo).then((res) => {
            if (res) {
              MG.ugc.updateTopicMessage(data).then(() => {
                if (res !== false) {
                  ElMessage({
                    message: "提交成功!请等待审核...",
                    type: "success",
                  });
                  teacherDialog.value = false;
                  subLoading.value = false;
                  newGetTeacherInfo();
                } else {
                  subLoading.value = false;
                }
              });
            } else {
              subLoading.value = true;
            }
          });
        } else {
          const data = {
            topicIdOrRefCode: "teacherRoleApproval",
            name: teacherInfo.fullName + "",
            content: "",
            state: "WaitAudit",
            type: "teacherRegister",
            cmsTypeRefCode: "teacherCertification",
            newDataListRequest: tool.worksDataBytool(
              worksInfo.value,
              teacherInfo,
              fileList.value
            ),
          };
          let basicInfo = JSON.parse(JSON.stringify(teacherInfo));
          delete basicInfo.worksInfo;
          delete basicInfo.state;
          const userInfo = {
            requests: [
              {
                data: JSON.stringify(basicInfo),
                name: teacherInfo.fullName + "",
                type: "teacherInfo",
              },
            ],
          };
          MG.identity.setAppUserInfo(userInfo).then((res) => {
            MG.ugc.newTopicMessage(data).then(() => {
              if (res !== false) {
                ElMessage({
                  message: "提交成功!请等待审核...",
                  type: "success",
                });
                teacherDialog.value = false;
                newGetTeacherInfo();
                subLoading.value = false;
              } else {
                subLoading.value = false;
              }
            });
          });
        }
      } else {
        ElMessage({
          message: "请同意《教师认证服务条款》!",
          type: "warning",
        });
      }
    }
  });
};
//原因查看
const dialogReason = ref(false);
const reasonTxt = ref("");
const lookReason = () => {
  dialogReason.value = true;
};
defineExpose({ getUserRole });
</script>
<style lang="less">
.myDialog {
  width: 628px;
  .el-dialog__body {
    padding: 0;
  }
  .body-box {
    padding: 10px 20px 40px;
    height: 80vh;
    overflow-y: auto;
  }
  .el-dialog__header {
    padding: 15px;
    margin-right: 0;
    border-bottom: 1px solid #f4f4f4;
  }
  .el-dialog__title {
    font-weight: bold;
    font-size: 16px;
  }
  .el-dialog__headerbtn {
    top: 6px;
    right: 6px;
  }
  .footer-box {
    padding: 15px;
    border-top: 1px solid #f4f4f4;
    text-align: right;
    height: 63px;
  }
  .myDialog-footer {
    .el-button {
      padding: 0 20px;
    }
  }
}
</style>
<style lang="less" scoped>
.tipsBox {
  line-height: 24px;
  padding: 5px;
  border: 1px solid #019e58;
  background: rgba(116, 252, 188, 0.1);
  color: #019e58;
  text-align: center;
  width: 86%;
  margin: 0 auto 20px auto;
  .main {
    font-weight: bold;
    text-align: center;
  }
}
.teacherInfo {
  .el-select {
    width: 90%;
    height: 38px;
    /deep/.el-input__wrapper {
      height: 38px;
    }
  }
  .el-input {
    width: 90%;
    height: 38px;
  }
}
.uploadBox {
  padding: 10px 0 0 10px;
  border: 1px solid #e9e9eb;
  border-radius: 3px;
  width: 330px;
}
.fileList .upload {
  display: inline-block;
  vertical-align: middle;
  margin-right: 10px;
  margin-bottom: 10px;
}
.fileImgBox {
  display: inline-block;
  position: relative;
  width: 146px;
  height: 146px;
  border-radius: 6px;
  background: #ddd;
  margin-right: 10px;
  margin-bottom: 10px;
  vertical-align: middle;
  border: 1px solid #c0ccda;
}
.fileList .fileImgBox:hover {
  border-color: #019E58;
}
.agree-msg {
  margin-left: 140px;
  display: flex;
  justify-content: flex-start;
  align-items: center;
  .agree {
    margin-left: 5px;
  }
  .term {
    color: #019E58;
  }
}
.reason {
  word-wrap: break-word;
}
.protocolBox {
  height: 500px;
  overflow-y: auto;
}
</style>
src/views/personalCenter/userInfo.vue
@@ -8,7 +8,9 @@
          <div class="info-box flex">
            <span class="label">用户名:</span>
            <span class="text">{{ userStore?.userInfo.name }}</span>
            <span class="change-info hover" @click="changeUserInfo('password')">修改密码</span>
            <span class="change-info hover" @click="changeUserInfo('password')"
              >修改密码</span
            >
          </div>
          <div class="info-box flex">
            <span class="label">微信认证:</span>
@@ -25,16 +27,16 @@
            }}</span>
            <span class="text no" v-else>未绑定</span>
            <span class="change-info hover" @click="changeUserInfo('phone')">{{
              userStore?.userInfo?.phoneNumber ? '更换手机号' : '绑定'
              userStore?.userInfo?.phoneNumber ? "更换手机号" : "绑定"
            }}</span>
          </div>
          <div class="info-box flex">
            <span class="label">邮箱:</span>
            <span class="text">{{
              userStore?.userInfo?.Email ? userStore.userInfo?.Email : '--'
              userStore?.userInfo?.Email ? userStore.userInfo?.Email : "--"
            }}</span>
            <span class="change-info hover" @click="changeUserInfo('email')">{{
              userStore?.userInfo?.Email ? '更换邮箱' : '绑定邮箱'
              userStore?.userInfo?.Email ? "更换邮箱" : "绑定邮箱"
            }}</span>
          </div>
        </div>
@@ -56,7 +58,10 @@
        <div class="item-title flex jc-sb">
          <span>教师认证</span>
          <div>
            <span class="change-info hover" v-if="teacherState == ''" @click="showTeacherDialog()"
            <span
              class="change-info hover"
              v-if="teacherState == ''"
              @click="showTeacherDialog()"
              >认证</span
            >
            <span
@@ -104,7 +109,11 @@
      destroy-on-close
      v-model="userInfoDialog"
      :title="
        changeType == 'email' ? '更换邮箱' : changeType == 'password' ? '修改密码' : '更换手机号'
        changeType == 'email'
          ? '更换邮箱'
          : changeType == 'password'
          ? '修改密码'
          : '更换手机号'
      "
      width="500"
      class="myDialogs"
@@ -154,10 +163,10 @@
              >
                {{
                  countDown > 0
                    ? '验证码(' + countDown + 's)'
                    : changeType == 'email'
                      ? '获取邮箱验证码'
                      : '获取短信验证码'
                    ? "验证码(" + countDown + "s)"
                    : changeType == "email"
                    ? "获取邮箱验证码"
                    : "获取短信验证码"
                }}
              </el-button>
            </div>
@@ -170,7 +179,11 @@
              placeholder="请输入8-16位新密码,且不能为纯数字"
            />
          </el-form-item>
          <el-form-item label="确认密码:" prop="confirmPassword" v-if="changeType == 'password'">
          <el-form-item
            label="确认密码:"
            prop="confirmPassword"
            v-if="changeType == 'password'"
          >
            <el-input
              type="password"
              v-model="userInfoForm.confirmPassword"
@@ -183,7 +196,11 @@
      <template #footer>
        <span class="myDialogs-footer">
          <el-button @click="closeUserInfoDialog(userFormRef)">取消</el-button>
          <el-button type="primary" @click="confirmInfo(userFormRef)" :loading="subLoading">
          <el-button
            type="primary"
            @click="confirmInfo(userFormRef)"
            :loading="subLoading"
          >
            确定
          </el-button>
        </span>
@@ -244,9 +261,9 @@
          <li v-for="item in integralRecord.recordList" :key="item.key" class="body">
            <span class="label">{{ item.type }}</span>
            <span class="value" :class="item.value > 0 ? 'yes' : 'no'">{{
              item.value > 0 ? '+' + item.value : item.value
              item.value > 0 ? "+" + item.value : item.value
            }}</span>
            <span>{{ item.createDate ? item.createDate : '-' }}</span>
            <span>{{ item.createDate ? item.createDate : "-" }}</span>
          </li>
          <li class="total" v-if="integralRecord.recordList.length > 0">
            <span class="label">总计</span>
@@ -271,260 +288,261 @@
</template>
<script setup lang="ts">
import { reactive, ref, inject, onMounted, watch } from 'vue'
import type { FormInstance, FormRules } from 'element-plus'
import { reactive, ref, inject, onMounted, watch } from "vue";
import type { FormInstance, FormRules } from "element-plus";
// import verify from '@/components/sliderImg/component/verify.vue'
// import '@/components/sliderImg/sliderImg.js'
// import '@/components/sliderImg/sliderImg.css'
import { ElMessage } from 'element-plus'
import tool from '@/assets/js/toolClass.js'
// import { useUserStore } from '@/store'
import { ElMessage } from "element-plus";
import tool from "@/assets/js/toolClass.js";
import { useUserStore } from "@/store";
// import wxlogin from 'vue-wxlogin'
// import teacherCertification from '@/views/components/teacherCertification.vue'
import teacherCertification from "./teacherCertification.vue";
// import login from '@/layout/components/login.vue'
// const userStore = useUserStore()
import { useRoute } from 'vue-router'
import moment from 'moment'
const route = useRoute()
const MG: any = inject('MG')
const config: any = inject('config')
const userStore = useUserStore();
import { useRoute } from "vue-router";
import moment from "moment";
const route = useRoute();
const MG: any = inject("MG");
const config: any = inject("config");
const validatePhone = (rule: any, value: any, callback: any) => {
  if (value === '') {
    callback(new Error('请输入联系电话'))
  if (value === "") {
    callback(new Error("请输入联系电话"));
  } else {
    if (!config.reg_tel.test(value)) {
      callback(new Error('请输入正确格式的电话'))
      callback(new Error("请输入正确格式的电话"));
    }
    callback()
    callback();
  }
}
};
const validateEmail = (rule: any, value: any, callback: any) => {
  let myreg = /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/
  if (value === '') {
    callback(new Error('请输入电子邮箱'))
  let myreg = /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/;
  if (value === "") {
    callback(new Error("请输入电子邮箱"));
  } else {
    if (!myreg.test(value)) {
      callback(new Error('请输入正确格式的电子邮箱'))
      callback(new Error("请输入正确格式的电子邮箱"));
    }
    callback()
    callback();
  }
}
};
const validatePassword = (rule: any, value: any, callback: any) => {
  let myreg = /^(?!^\d+$)(?!^[a-zA-Z]+$)(?!^\W+$)[a-zA-Z\d\W]{8,16}$/
  if (value === '') {
    callback(new Error('请输入密码'))
  let myreg = /^(?!^\d+$)(?!^[a-zA-Z]+$)(?!^\W+$)[a-zA-Z\d\W]{8,16}$/;
  if (value === "") {
    callback(new Error("请输入密码"));
  } else {
    if (!myreg.test(value)) {
      callback(new Error('请输入正确格式的密码,8-16位,且不能为纯数字'))
      callback(new Error("请输入正确格式的密码,8-16位,且不能为纯数字"));
    }
    callback()
    callback();
  }
}
};
// onMounted(() => {
//   getWechatAuthenticationState()
//   getIntegral()
//   if (localStorage.getItem(config.tokenKey)) {
//     getUserRole()
//   }
// })
onMounted(() => {
  getWechatAuthenticationState();
  getIntegral();
  if (localStorage.getItem(config.tokenKey)) {
    getUserRole();
  }
});
// watch(route, () => {
//   bindWeChat()
// })
let subLoading = ref(false)
let subLoading = ref(false);
//用户信息
let weChatState = ref(false)
let weChatState = ref(false);
const userInfo = reactive({
  userType: '',
  userType: "",
  integral: 0,
})
});
//基础信息
const userInfoDialog = ref(false)
let changeType = ref('password')
const imgCode = ref<string>() // 图形验证码url
let countDown = ref(0)
// function changeUserInfo(type) {
//   changeType.value = type
//   if (type == 'password') {
//     if (userStore.userInfo?.phoneNumber) {
//       getImgCapcha()
//       userInfoDialog.value = true
//     } else {
//       ElMessage({
//         message: '修改密码需短信验证,请绑定手机号后再修改密码!',
//         type: 'warning'
//       })
//     }
//   } else {
//     getImgCapcha()
//     userInfoDialog.value = true
//   }
// }
// const getImgCapcha = () => {
//   MG.identity.getImgCode().then((res) => {
//     imgCode.value = 'data:image/png;base64,' + res
//   })
// }
const userInfoDialog = ref(false);
let changeType = ref("password");
const imgCode = ref<string>(); // 图形验证码url
let countDown = ref(0);
const userFormRef = ref<FormInstance>()
const changeUserInfo = (type) => {
  changeType.value = type;
  if (type == "password") {
    if (userStore.userInfo?.phoneNumber) {
      getImgCapcha();
      userInfoDialog.value = true;
    } else {
      ElMessage({
        message: "修改密码需短信验证,请绑定手机号后再修改密码!",
        type: "warning",
      });
    }
  } else {
    getImgCapcha();
    userInfoDialog.value = true;
  }
};
const getImgCapcha = () => {
  MG.identity.getImgCode().then((res) => {
    imgCode.value = "data:image/png;base64," + res;
  });
};
const userFormRef = ref<FormInstance>();
const userInfoForm = reactive({
  phone: '',
  email: '',
  captcha: '',
  code: '',
  password: '',
  confirmPassword: '',
})
const formDisabled = ref(false)
  phone: "",
  email: "",
  captcha: "",
  code: "",
  password: "",
  confirmPassword: "",
});
const formDisabled = ref(false);
const userFormRules = reactive<FormRules<userInfoForm>>({
  phone: [{ required: true, validator: validatePhone, trigger: 'blur' }],
  email: [{ required: true, validator: validateEmail, trigger: 'blur' }],
  phone: [{ required: true, validator: validatePhone, trigger: "blur" }],
  email: [{ required: true, validator: validateEmail, trigger: "blur" }],
  captcha: [
    { required: true, message: '图形验证码不能为空', trigger: 'blur' },
    { min: 4, max: 4, message: '请输入 4 位验证码', trigger: 'blur' },
    { required: true, message: "图形验证码不能为空", trigger: "blur" },
    { min: 4, max: 4, message: "请输入 4 位验证码", trigger: "blur" },
  ],
  code: [{ required: true, message: '验证码不能为空', trigger: 'blur' }],
  password: [{ required: true, validator: validatePassword, trigger: 'blur' }],
  confirmPassword: [{ required: true, message: '确认密码不能为空', trigger: 'blur' }],
})
  code: [{ required: true, message: "验证码不能为空", trigger: "blur" }],
  password: [{ required: true, validator: validatePassword, trigger: "blur" }],
  confirmPassword: [{ required: true, message: "确认密码不能为空", trigger: "blur" }],
});
const getVerifyCode = async () => {
  sliderImgDialogVisable.value = true
}
  sliderImgDialogVisable.value = true;
};
// 验证码倒计时
function getSecond(time: number) {
  let timer: ReturnType<typeof setInterval> | null = null
  let timer: ReturnType<typeof setInterval> | null = null;
  if (!timer) {
    countDown.value = time
    countDown.value = time;
    timer = setInterval(() => {
      countDown.value--
      countDown.value--;
      if (countDown.value == 0) {
        if (timer) clearInterval(timer)
        timer = null
        if (timer) clearInterval(timer);
        timer = null;
      }
    }, 1000)
    }, 1000);
  }
}
const closeUserInfoDialog = (formEl: FormInstance | undefined) => {
  if (!formEl) return
  formEl.resetFields()
  countDown.value = 0
  userInfoDialog.value = false
}
  if (!formEl) return;
  formEl.resetFields();
  countDown.value = 0;
  userInfoDialog.value = false;
};
const confirmInfo = async (formEl: FormInstance | undefined) => {
  if (!formEl) return
  if (!formEl) return;
  await formEl.validate((valid, fields) => {
    if (valid) {
      subLoading.value = true
      if (changeType.value == 'password') {
      subLoading.value = true;
      if (changeType.value == "password") {
        if (userInfoForm.password != userInfoForm.confirmPassword) {
          ElMessage({
            message: '两次密码输入不一致',
            type: 'warning',
          })
          return false
            message: "两次密码输入不一致",
            type: "warning",
          });
          return false;
        }
        let query = {
          phoneNumber: userInfoForm.phone,
          phoneCaptcha: userInfoForm.code,
          password: userInfoForm.password,
        }
        };
        MG.identity.changePasswordByMobilePhone(query).then((res) => {
          if (res) {
            ElMessage({
              message: '密码重置成功!',
              type: 'success',
            })
            userInfoDialog.value = false
              message: "密码重置成功!",
              type: "success",
            });
            userInfoDialog.value = false;
          } else {
            ElMessage({
              message: '密码重置失败,请填写正确的验证码。',
              type: 'error',
            })
              message: "密码重置失败,请填写正确的验证码。",
              type: "error",
            });
          }
          subLoading.value = false
        })
      } else if (changeType.value == 'phone') {
          subLoading.value = false;
        });
      } else if (changeType.value == "phone") {
        let query = {
          phoneNumber: userInfoForm.phone,
          phoneCaptcha: userInfoForm.code,
        }
        };
        MG.identity.userSetPhoneNumber(query).then((res) => {
          if (res == '验证码过期或错误') {
          if (res == "验证码过期或错误") {
            ElMessage({
              message: res + ',请稍后重试',
              type: 'error',
            })
          } else if (res == '此手机号码已被其它账号绑定') {
              message: res + ",请稍后重试",
              type: "error",
            });
          } else if (res == "此手机号码已被其它账号绑定") {
            ElMessage({
              message: res + ',请更换其他手机号。',
              type: 'error',
            })
              message: res + ",请更换其他手机号。",
              type: "error",
            });
          } else {
            ElMessage({
              message: res,
              type: 'success',
            })
              type: "success",
            });
            userStore.setUserInfo({
              ...userStore.userInfo,
              phoneNumber: userInfoForm.phone,
            })
            userInfoDialog.value = false
            });
            userInfoDialog.value = false;
          }
          subLoading.value = false
        })
      } else if (changeType.value == 'email') {
          subLoading.value = false;
        });
      } else if (changeType.value == "email") {
        let query = {
          eMail: userInfoForm.email,
          captcha: userInfoForm.code,
        }
        };
        MG.identity.bindingEmail(query).then((res) => {
          if (res == '验证码过期') {
          if (res == "验证码过期") {
            ElMessage({
              message: res + ',请稍后重试',
              type: 'error',
            })
          } else if (res == '此邮箱已被其它账号绑定') {
              message: res + ",请稍后重试",
              type: "error",
            });
          } else if (res == "此邮箱已被其它账号绑定") {
            ElMessage({
              message: res + ',请更换其他邮箱。',
              type: 'error',
            })
          } else if (res == '验证码无效') {
              message: res + ",请更换其他邮箱。",
              type: "error",
            });
          } else if (res == "验证码无效") {
            ElMessage({
              message: res,
              type: 'error',
            })
              type: "error",
            });
          } else {
            ElMessage({
              message: res,
              type: 'success',
            })
              type: "success",
            });
            userStore.setUserInfo({
              ...userStore.userInfo,
              Email: userInfoForm.email,
            })
            userInfoDialog.value = false
            });
            userInfoDialog.value = false;
          }
          subLoading.value = false
        })
          subLoading.value = false;
        });
      }
    } else {
      subLoading.value = false
      subLoading.value = false;
    }
  })
}
  });
};
// 滑动验证
const sliderImgDialogVisable = ref<boolean>(false)
const sliderImgDialogVisable = ref<boolean>(false);
const loginImgVerify = (code: string) => {
  userInfoForm.captcha = code
  sliderImgDialogVisable.value = false
  if (changeType.value == 'phone' || changeType.value == 'password') {
  userInfoForm.captcha = code;
  sliderImgDialogVisable.value = false;
  if (changeType.value == "phone" || changeType.value == "password") {
    MG.identity
      .getPhoneCode({
        phoneNumber: userInfoForm.phone,
@@ -532,21 +550,21 @@
        appRefCode: config.appRefCode,
      })
      .then((res: any) => {
        if (res == '验证码发送成功') {
        if (res == "验证码发送成功") {
          ElMessage({
            message: res,
            type: 'success',
          })
            type: "success",
          });
          // 开启短信验证倒计时
          getSecond(60)
          getSecond(60);
        } else {
          ElMessage({
            message: res,
            type: 'error',
          })
            type: "error",
          });
        }
      })
  } else if (changeType.value == 'email') {
      });
  } else if (changeType.value == "email") {
    MG.identity
      .getEmailCode({
        sendEmail: userInfoForm.email,
@@ -556,50 +574,50 @@
      .then((res) => {
        if (res == true) {
          ElMessage({
            message: '邮件已发送',
            type: 'success',
          })
            message: "邮件已发送",
            type: "success",
          });
        } else {
          ElMessage({
            message: '邮件发送失败',
            type: 'error',
          })
            message: "邮件发送失败",
            type: "error",
          });
        }
      })
      });
  }
}
};
//微信认证
let wxLogin = reactive({
  appid: 'wx5cfe8b007a3c6f8c',
  scope: 'snsapi_login',
  redirectURL: encodeURIComponent(config.requestCtx + '/home/#/personalCenter'),
})
  appid: "wx5cfe8b007a3c6f8c",
  scope: "snsapi_login",
  redirectURL: encodeURIComponent(config.requestCtx + "/home/#/personalCenter"),
});
const getWechatAuthenticationState = () => {
  MG.identity.checkBuildingWeChat({}).then((res: any) => {
    if (res) {
      weChatState.value = true
      weChatState.value = true;
    } else {
      weChatState.value = false
      weChatState.value = false;
    }
  })
}
const weChartDialog = ref(false)
  });
};
const weChartDialog = ref(false);
function goBindWeChat() {
  window.location.href = `https://open.weixin.qq.com/connect/qrconnect?appid=${wxLogin.appid}&scope=${wxLogin.scope}&redirect_uri=${wxLogin.redirectURL}&state=WeChatScanningCodeBind`
  window.location.href = `https://open.weixin.qq.com/connect/qrconnect?appid=${wxLogin.appid}&scope=${wxLogin.scope}&redirect_uri=${wxLogin.redirectURL}&state=WeChatScanningCodeBind`;
}
//绑定微信
const bindWeChat = () => {
  var url = window.location.href
  if (url.indexOf('WeChatScanningCodeBind') > -1) {
    var querys = url.substring(url.indexOf('?') + 1).split('&')
    var result = {}
  var url = window.location.href;
  if (url.indexOf("WeChatScanningCodeBind") > -1) {
    var querys = url.substring(url.indexOf("?") + 1).split("&");
    var result = {};
    for (var i = 0; i < querys.length; i++) {
      var temp = querys[i].split('=')
      var temp = querys[i].split("=");
      if (temp.length < 2) {
        result[temp[0]] = ''
        result[temp[0]] = "";
      } else {
        result[temp[0]] = temp[1]
        result[temp[0]] = temp[1];
      }
    }
    if (result && result.code) {
@@ -610,153 +628,162 @@
        .then((res) => {
          if (res) {
            ElMessage({
              message: '绑定成功!',
              type: 'success',
            })
            getWechatAuthenticationState()
            weChartDialog.value = false
              message: "绑定成功!",
              type: "success",
            });
            getWechatAuthenticationState();
            weChartDialog.value = false;
          } else {
            ElMessage({
              message: '绑定失败,该微信已被绑定!',
              type: 'error',
            })
              message: "绑定失败,该微信已被绑定!",
              type: "error",
            });
          }
        })
        });
    }
  }
}
};
//用户类型
const loginRef = ref()
const loginRef = ref();
// const userTypeDialog = ref(false)
const userTypeActive = ref('')
const teacherType = ref('')
const userTypeActive = ref("");
const teacherType = ref("");
const teacherList = ref([
  {
    value: 'vocSchoolTeachers',
    label: '中职教师',
    value: "vocSchoolTeachers",
    label: "中职教师",
  },
  {
    value: 'vocCollegeTeachers',
    label: '高职教师',
    value: "vocCollegeTeachers",
    label: "高职教师",
  },
  {
    value: 'ordUniversityTeachers',
    label: '本科教师',
    value: "ordUniversityTeachers",
    label: "本科教师",
  },
  {
    value: 'primarySchoolTeachers',
    label: '中小学教师',
    value: "primarySchoolTeachers",
    label: "中小学教师",
  },
  {
    value: 'kindergarteTeachers',
    label: '幼儿园教师',
    value: "kindergarteTeachers",
    label: "幼儿园教师",
  },
])
]);
const userTypeList = ref([
  {
    value: 'Teacher',
    label: '教师',
    value: "Teacher",
    label: "教师",
    checked: false,
  },
  {
    value: 'Student',
    label: '学生',
    value: "Student",
    label: "学生",
    checked: false,
  },
  {
    value: 'otherReaders',
    label: '其他读者',
    value: "otherReaders",
    label: "其他读者",
    checked: false,
  },
])
]);
// 修改用户类型调用注册时用户信息填写弹窗
const updateUserInfo = () => {
  loginRef.value.updateUserInfo()
  loginRef.value.signUp()
}
  loginRef.value.updateUserInfo();
  loginRef.value.signUp();
};
//教师认证
let teacherDialog = ref(false) //弹窗
let loading = ref(false)
const teacherState = ref(null)
const reasonTxt = ref('')
const userId = ref()
let teacherDialog = ref(false); //弹窗
let loading = ref(false);
const teacherState = ref(null);
const reasonTxt = ref("");
const userId = ref();
const dialogReason = ref(false)
const dialogReason = ref(false);
//教师认证弹窗
function showTeacherDialog() {
  teacherDialog.value = true
  teacherDialog.value = true;
}
const dialogChange = (val: any) => {
  getTeacherInfo()
  getTeacherInfo();
  if (val == false) {
    teacherDialog.value = false
    teacherDialog.value = false;
  } else {
    teacherDialog.value = true
    teacherDialog.value = true;
  }
}
};
// 修改密码弹窗打开
const openChangePassword = () => {
  if (changeType.value == 'password' && userStore.userInfo?.phoneNumber) {
    userInfoForm.phone = userStore.userInfo?.phoneNumber
  if (changeType.value == "password" && userStore.userInfo?.phoneNumber) {
    userInfoForm.phone = userStore.userInfo?.phoneNumber;
  }
}
};
// 获取登录用户身份
function getUserRole() {
  loading.value = true
  loading.value = true;
  MG.identity.getCurrentAppUser().then((res: any) => {
    if (res) {
      if (res.lastLoginTime) {
        localStorage.setItem('lastLoginTime', res.lastLoginTime)
        localStorage.setItem("lastLoginTime", res.lastLoginTime);
      }
      //获取用户类型
      let userTypeData = res.infoList.find((item: any) => item.type == 'userType')
      let userTypeData = res.infoList.find((item: any) => item.type == "userType");
      if (userTypeData) {
        userTypeActive.value = JSON.parse(userTypeData.data).userType
        if (userTypeActive.value !== 'Student' && userTypeActive.value !== 'otherReaders') {
          const index = userTypeList.value.findIndex((item) => item.value === 'Teacher')
        userTypeActive.value = JSON.parse(userTypeData.data).userType;
        if (
          userTypeActive.value !== "Student" &&
          userTypeActive.value !== "otherReaders"
        ) {
          const index = userTypeList.value.findIndex((item) => item.value === "Teacher");
          if (index !== -1) {
            userTypeList.value[index].checked = true
            teacherType.value = JSON.parse(userTypeData.data).userType
            userTypeList.value[index].checked = true;
            teacherType.value = JSON.parse(userTypeData.data).userType;
          }
          userInfo.userType =
            teacherList.value.find((item) => item.value === userTypeActive.value)?.label ?? ''
            teacherList.value.find((item) => item.value === userTypeActive.value)
              ?.label ?? "";
        } else {
          const index = userTypeList.value.findIndex((item) => item.value === userTypeActive.value)
          const index = userTypeList.value.findIndex(
            (item) => item.value === userTypeActive.value
          );
          if (index !== -1) {
            userTypeList.value[index].checked = true
            userTypeList.value[index].checked = true;
          }
          userInfo.userType =
            userTypeList.value.find((item) => item.value === userTypeActive.value)?.label ?? ''
            userTypeList.value.find((item) => item.value === userTypeActive.value)
              ?.label ?? "";
        }
      } else {
        userInfo.userType = '-'
        userInfo.userType = "-";
      }
      getTeacherInfo()
      userId.value = res.userId
      let customUser = res.infoList.find((item: any) => item.type == 'userInfo')
      let teacherRole = res.roleLinks.find((item: any) => item.role.refCode == 'teacher')
      let teacherInfos = res.infoList.find((item: any) => item.type == 'teacherInfo')
      let wechatInfo = res.infoList.find((item: any) => item.type == 'WeChat')
      let studentInfo = res.infoList.find((item: any) => item.type == 'Default')
      let phoneInfo = res.secretList.find((item: any) => item.type == 'MobilePhone')
      let emailInfo = res.secretList.find((item: any) => item.type == 'EMail')
      getTeacherInfo();
      userId.value = res.userId;
      let customUser = res.infoList.find((item: any) => item.type == "userInfo");
      let teacherRole = res.roleLinks.find((item: any) => item.role.refCode == "teacher");
      let teacherInfos = res.infoList.find((item: any) => item.type == "teacherInfo");
      let wechatInfo = res.infoList.find((item: any) => item.type == "WeChat");
      let studentInfo = res.infoList.find((item: any) => item.type == "Default");
      let phoneInfo = res.secretList.find((item: any) => item.type == "MobilePhone");
      let emailInfo = res.secretList.find((item: any) => item.type == "EMail");
      const userData = {
        userName: customUser && customUser.data ? JSON.parse(customUser.data).name : '',
        school: customUser && customUser.data ? JSON.parse(customUser.data).school : '',
        cityCode: customUser && customUser.data ? JSON.parse(customUser.data).cityCode : '',
        address: customUser && customUser.data ? JSON.parse(customUser.data).address : '',
        userType: userTypeData && userTypeData.data ? JSON.parse(userTypeData.data).userType : '',
      }
        userName: customUser && customUser.data ? JSON.parse(customUser.data).name : "",
        school: customUser && customUser.data ? JSON.parse(customUser.data).school : "",
        cityCode:
          customUser && customUser.data ? JSON.parse(customUser.data).cityCode : "",
        address: customUser && customUser.data ? JSON.parse(customUser.data).address : "",
        userType:
          userTypeData && userTypeData.data ? JSON.parse(userTypeData.data).userType : "",
      };
      if (teacherRole && teacherInfos) {
        if (JSON.parse(teacherInfos.data).email && !emailInfo) {
          userInfoForm.email = JSON.parse(teacherInfos.data).email
          formDisabled.value = true
          userInfoForm.email = JSON.parse(teacherInfos.data).email;
          formDisabled.value = true;
        }
        userStore.setUserInfo({
          ...userData,
@@ -764,19 +791,19 @@
          phoneNumber: phoneInfo?.credential,
          Email: emailInfo ? emailInfo.credential : JSON.parse(teacherInfos.data).email,
          icon: wechatInfo?.icon,
          role: 'Teacher',
          role: "Teacher",
          roleId: teacherRole.role.id,
          userId: res.userId,
        })
        });
      } else if (wechatInfo) {
        userStore.setUserInfo({
          ...userData,
          ...wechatInfo,
          phoneNumber: phoneInfo?.credential,
          Email: emailInfo?.credential,
          role: 'Student',
          role: "Student",
          userId: res.userId,
        })
        });
      } else if (studentInfo) {
        userStore.setUserInfo({
          ...userData,
@@ -784,12 +811,12 @@
          icon: wechatInfo?.icon,
          phoneNumber: phoneInfo?.credential,
          Email: emailInfo?.credential,
          role: 'Student',
          role: "Student",
          userId: res.userId,
        })
        });
      }
    }
  })
  });
}
//教师信息
@@ -797,56 +824,56 @@
  const data = {
    start: 0,
    size: 10,
    topicIdOrRefCode: 'teacherRoleApproval',
    topicIdOrRefCode: "teacherRoleApproval",
    appRefCode: config.appRefCode,
    sort: {
      type: 'Desc',
      field: 'CreateDate',
      type: "Desc",
      field: "CreateDate",
    },
  }
  };
  MG.ugc.getTopicMessageList(data).then((res) => {
    try {
      const resData = res.datas.find((i) => i.appUserCreator.userId == userId.value)
      const resData = res.datas.find((i) => i.appUserCreator.userId == userId.value);
      if (resData) {
        teacherState.value = resData.state
        teacherState.value = resData.state;
        if (resData.feedBack != null) {
          reasonTxt.value = JSON.parse(resData.feedBack).reason
          reasonTxt.value = JSON.parse(resData.feedBack).reason;
        }
      } else {
        teacherState.value = ''
        teacherState.value = "";
      }
      loading.value = false
      loading.value = false;
    } catch (error) {
      loading.value = false
      loading.value = false;
    }
  })
  });
}
//原因查看
const lookReason = () => {
  dialogReason.value = true
}
  dialogReason.value = true;
};
//积分
function getIntegral() {
  MG.store
    .getUserWallet({
      type: 'integral',
      type: "integral",
    })
    .then((res) => {
      userInfo.integral = res.balance
    })
      userInfo.integral = res.balance;
    });
}
const integralRecord = reactive({
  recordDialog: false,
  recordList: [],
})
});
// 积分记录弹窗
function recordDialog() {
  integralRecord.recordDialog = true
  getRecordList()
  integralRecord.recordDialog = true;
  getRecordList();
}
//获取积分记录
@@ -856,38 +883,38 @@
      Size: 999,
      Start: 0,
      sort: {
        type: 'Desc',
        field: 'CreateDate',
        type: "Desc",
        field: "CreateDate",
      },
      type: 'integral',
      type: "integral",
    })
    .then((res) => {
      console.log(res, '积分记录')
      console.log(res, "积分记录");
      if (res.datas.length > 0) {
        res.datas.forEach((element) => {
          element.createDate = moment(element.createDate).format('YYYY-MM-DD HH:mm:ss')
          if (element.refType == 'sign') {
            element.type = '每日登录'
          element.createDate = moment(element.createDate).format("YYYY-MM-DD HH:mm:ss");
          if (element.refType == "sign") {
            element.type = "每日登录";
          }
          if (element.refType == 'Reward') {
            element.type = '上传资源奖励'
          if (element.refType == "Reward") {
            element.type = "上传资源奖励";
          }
          if (element.refType == 'OrderCoinBonus') {
            element.type = '订单支付奖励'
          if (element.refType == "OrderCoinBonus") {
            element.type = "订单支付奖励";
          }
          if (element.refType == 'Order' && element.value < 0) {
            element.type = '订单支付抵扣'
          if (element.refType == "Order" && element.value < 0) {
            element.type = "订单支付抵扣";
          }
          if (element.refType == 'Order' && element.value > 0) {
            element.type = '订单取消退回'
          if (element.refType == "Order" && element.value > 0) {
            element.type = "订单取消退回";
          }
          if (element.refType == 'AdminRecharge') {
            element.type = '管理员充值'
          if (element.refType == "AdminRecharge") {
            element.type = "管理员充值";
          }
        })
        integralRecord.recordList = res.datas
        });
        integralRecord.recordList = res.datas;
      }
    })
    });
}
</script>
<style lang="less" scoped>
@@ -896,9 +923,7 @@
    line-height: 20px;
    padding: 0 10px;
    border-left: 3px solid #019e58;
    font-family:
      Microsoft YaHei UI,
      Microsoft YaHei UI;
    font-family: Microsoft YaHei UI, Microsoft YaHei UI;
    font-weight: 400;
    font-size: 16px;
  }
@@ -908,9 +933,7 @@
    .info-box {
      padding: 10px 0;
      font-family:
        Microsoft YaHei UI,
        Microsoft YaHei UI;
      font-family: Microsoft YaHei UI, Microsoft YaHei UI;
      font-weight: 400;
      font-size: 14px;
      color: #333333;