user1
2024-06-25 651c888579b8b37aafe02b608ac1a4bb4a5a36de
Merge branch 'master' of http://182.92.203.7:2001/r/testbookLayout
8个文件已修改
5个文件已添加
1355 ■■■■ 已修改文件
.env.product 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/books/mathBook/view/components/chapter001.vue 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/books/mathBook/view/components/index.vue 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/books/sportsAndHealth/css/default.less 118 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/books/sportsAndHealth/view/components/index.vue 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/books/sportsAndHealth/view/components/testPp.vue 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/dragQuestion/index.vue 527 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/examinations/index.vue 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/graffiti/components/brushSize.vue 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/graffiti/components/colorPicker.vue 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/graffiti/components/toolBtns.vue 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/graffiti/index.vue 234 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/pdfview/index.vue 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.product
@@ -1,6 +1,6 @@
VUE_APP_ENV = 'product'
VUE_APP_API_URL = "https://jsek.bnuic.com"
VUE_APP_RESOURCE_CTX = 'https://jsek.bnuic.com/books/resource/'
VUE_APP_PUBLIC_PATH = 'https://jsek.bnuic.com/books/book/mathBook'
VUE_APP_PUBLIC_PATH = 'https://jsek.bnuic.com/books/book/sportsAndHealth'
VUE_APP_BOOK_LIST = "childHealth/lifeCare/sportsAndHealth/embedded/english/artAndDance/artAndDrama/mathBook"
VUE_APP_BOOK_ID = 'mathBook'
VUE_APP_BOOK_ID = 'sportsAndHealth'
src/books/mathBook/view/components/chapter001.vue
@@ -575,7 +575,10 @@
        </div>
      </div>
    </div>
    <div class="page-box padding-116" page="10">
      <!-- <drag :question="dragQuestion" :page="10"/> -->
       <graffiti style="width:100%" />
    </div>
    <!-- 函数控件弹窗 -->
    <el-dialog
      :visible.sync="dialogVisible"
@@ -820,9 +823,11 @@
import examinations from "@/components/examinations/index.vue";
import { getResourcePath } from "@/assets/methods/resources";
import { getCollectResource,setCollectResource } from "@/assets/methods/resources";
import drag from '@/components/dragQuestion/index.vue'
import graffiti from "@/components/graffiti/index.vue"
export default {
  name: "chapter-one",
  components: { examinations },
  components: { examinations,drag,graffiti },
  props: {
    showPageList: {
      type: Array,
@@ -886,6 +891,80 @@
          isShow: false,
        },
      ],
      dragQuestion:[
          {
          analysisCon: null,
          answer: ['A','B','C'],
          difficulty: 0,
          id: "7BC7B760",
          isCollect: false,
          isComplete: false,
          isRight: null,
          isUnfold: "",
          isUserAnswer: false,
          number: 1,
          option: [
            {
              img: "",
              index: "010311",
              txt: "胆小的",
              value: "A",
              isShow:true
            },
            {
              img: "",
              index: "010312",
              txt: "善良的",
              value: "B",
              isShow:true
            },
            {
              img: "",
              index: "010313",
              txt: "沉稳的",
              value: "C",
              isShow:true
            },
          ],
          optionStyle: "Txt",
          questionType: "drag",
          score: 2,
          stem: {
            0: "蚂蚁队长走路昂首挺胸、步伐坚定,它是一只(",
            1:{
              data: "span",
              num: 0
            },
            2: ")蚂蚁;小蚂蚁走起路来小心翼翼,眼神飘忽不定,它是一只(",
            3:{
              data:"span",
              num:1
            },
            4:")蚂蚁;蚂蚁小妹面带微笑,时刻愿意帮助大家,它是一只(",
            5:{
              data:"span",
              num:2,
            },
            6:" )蚂蚁"
          },
          stemStyle: "RichTxt",
          type: "拖拽题",
          userAnswer:[
            {
              vlaue:'',
              txt:''
            },
            {
              vlaue:'',
              txt:''
            },
            {
              vlaue:'',
              txt:''
            },
          ]
        },
      ]
    };
  },
  methods: {
src/books/mathBook/view/components/index.vue
@@ -162,8 +162,8 @@
    }, 500);
    // 测试页面跳转
    // setTimeout(() => {
    //   this.gotoPage(1, 10);
    setTimeout(() => {
      this.gotoPage(2, 10);
    //   setTimeout(() => {
    //     this.renderSign("Highlight", {
    //       id: "2ACA9359",
@@ -191,7 +191,7 @@
    //   txt: " 运动系统是由骨、骨连结和骨骼肌三部分组成的。全身的骨通过骨连结组成人体骨骼(见图1-1)。骨骼是人体的支架,具有保护内脏器官、供肌肉附着和作为肌肉运动的杠杆等作用。在神经系统的支配下,肌肉收缩牵动所附着的骨绕着关节转动,使身体产生各种动作。所以,运动系统具有运动、支持和保护等功能,幼年时期的骨骼还具有造血功能。 ",
    //   txtIndex: 57
    // });
    // }, 5000);
    }, 500);
    // 获取题目id列表
    this.getQuestionId();
src/books/sportsAndHealth/css/default.less
@@ -20,7 +20,7 @@
      .chapter {
        textarea {
          width: 100%;
          font-family: 'FZLTXIHJW';
          font-family: "FZLTXIHJW";
          padding: 2px 5px;
          box-sizing: border-box;
        }
@@ -86,7 +86,7 @@
          span {
            margin-right: 28px;
            font-family: 'FZLTXIHJW';
            font-family: "FZLTXIHJW";
            font-size: 18px;
          }
          span:nth-child(3) {
@@ -116,7 +116,7 @@
          width: 1px;
          height: 100%;
          border-left: 2px dotted #eee;
          margin:0 14px;
          margin: 0 14px;
        }
        .header-even {
@@ -127,7 +127,7 @@
          align-items: flex-end;
          span {
            margin-left: 28px;
            font-family: 'FZLTXIHJW';
            font-family: "FZLTXIHJW";
            font-size: 18px;
          }
@@ -140,7 +140,7 @@
            font-weight: 700;
          }
          span:nth-child(3){
          span:nth-child(3) {
            margin-left: 0;
          }
        }
@@ -172,7 +172,7 @@
              span {
                font-size: 14px;
                text-align: center;
                font-family: 'FZLTXIHJW';
                font-family: "FZLTXIHJW";
                display: flex;
                align-items: center;
                justify-content: flex-start;
@@ -240,7 +240,7 @@
              justify-content: center;
              font-size: 14px;
              text-align: center;
              font-family: 'FZLTXIHJW';
              font-family: "FZLTXIHJW";
            }
          }
        }
@@ -264,7 +264,7 @@
          justify-content: center;
          align-items: center;
          padding-top: 20px;
          font-family: 'FZLTXIHJW';
          font-family: "FZLTXIHJW";
          .table-bottom {
            width: 100%;
@@ -303,7 +303,7 @@
              span:hover {
                background-color: #89a0d0;
                color: #FFF;
                color: #fff;
              }
              span:nth-child(1) {
@@ -326,9 +326,9 @@
              text-align: center;
              padding: 5px 10px;
              font-size: 14px;
              font-family: 'FZLTXIHJW';
              font-family: "FZLTXIHJW";
              font-weight: 900;
              min-width: 35px
              min-width: 35px;
            }
            tr td {
@@ -337,7 +337,7 @@
              padding: 3px 10px;
              font-size: 14px;
              line-height: 25px;
              font-family: 'FZLTXIHJW';
              font-family: "FZLTXIHJW";
              input {
                cursor: pointer;
@@ -356,7 +356,7 @@
          top: 0;
          width: 100%;
          height: 100%;
          background: url('../image/dy1.jpg');
          background: url("../image/dy1.jpg");
          background-position: top;
          background-size: 100% 50%;
          background-repeat: no-repeat;
@@ -384,7 +384,7 @@
          top: 0;
          width: 100%;
          height: 100%;
          background: url('../image/dy2.jpg');
          background: url("../image/dy2.jpg");
          background-position: top;
          background-size: 100% 50%;
          background-repeat: no-repeat;
@@ -398,7 +398,7 @@
          top: 0;
          width: 100%;
          height: 98%;
          background: url('../image/dy3.jpg');
          background: url("../image/dy3.jpg");
          background-position: top;
          background-size: 100% 45%;
          background-repeat: no-repeat;
@@ -419,9 +419,8 @@
          p {
            font-size: 16px;
            font-family: 'FZLTXIHJW';
            font-family: "FZLTXIHJW";
          }
        }
        .bk-xyx {
@@ -568,7 +567,7 @@
              span {
                font-size: 14px;
                text-align: center;
                font-family: 'FZLTXIHJW';
                font-family: "FZLTXIHJW";
                display: flex;
                align-items: center;
                justify-content: flex-start;
@@ -612,7 +611,6 @@
          text-indent: 2em;
        }
        .xyx-title {
          font-family: "FZLTXIHJW";
          text-align: center;
@@ -624,7 +622,7 @@
        .xyx-text {
          p {
            font-size: 16px;
            font-family: 'FZLTXIHJW';
            font-family: "FZLTXIHJW";
            padding: 0 10px;
            margin: 0;
          }
@@ -715,7 +713,7 @@
          background-color: transparent;
          border-bottom: 1px solid #000;
          font-size: 18px;
          font-family: '宋体';
          font-family: "宋体";
        }
        .assess:focus {
@@ -1551,7 +1549,7 @@
              border: 1px solid #89a0d0;
              border-radius: 5px;
              font-size: 14px;
              font-family: 'FZLTXIHJW';
              font-family: "FZLTXIHJW";
              text-indent: 2em;
              line-height: 22px;
              padding: 2px 5px;
@@ -1690,7 +1688,7 @@
        }
        .bj1-mk {
          background-image: url('../image/mk.png');
          background-image: url("../image/mk.png");
          background-position: center;
          background-repeat: no-repeat;
          background-size: 100% 100%;
@@ -1713,8 +1711,6 @@
      }
      @media screen and (max-width: 800px) {
        .chapter {
          .coverImg {
            height: 100%;
@@ -1759,7 +1755,7 @@
                span {
                  font-size: 14px;
                  text-align: center;
                  font-family: 'FZLTXIHJW';
                  font-family: "FZLTXIHJW";
                  line-height: 20px;
                }
@@ -1786,7 +1782,6 @@
            justify-content: center;
            flex-wrap: wrap;
          }
          .bj-img {
            display: flex;
@@ -1820,7 +1815,7 @@
            box-sizing: border-box;
          }
          .tablePublic table tr th{
          .tablePublic table tr th {
            padding: 0;
            width: auto !important;
          }
@@ -1831,10 +1826,8 @@
        }
      }
      @media screen and (max-width:700px) {
      @media screen and (max-width: 700px) {
        .chapter {
          .coverImg {
            margin: 0;
          }
@@ -1855,13 +1848,11 @@
              position: relative;
            }
          }
        }
      }
      @media screen and (max-width:600px) {
      @media screen and (max-width: 600px) {
        .chapter {
          .coverImg {
            margin: 0;
            height: 100%;
@@ -1914,7 +1905,6 @@
            }
          }
          .bj-empyt-chapter {
            height: 30%;
          }
@@ -1931,60 +1921,8 @@
  }
}
.pdfModal {
  width: 100%;
  height: 90vh;
}
@media screen and (max-width:800px) {
  .pdfModal {
    width: 100%;
    height: 60vh;
  }
  .custom-dialog {
    .el-dialog {
      width: 90vw !important;
    }
  }
}
.custom-dialog {
  overflow: hidden !important;
  .el-dialog__body {
    padding: 0;
  }
  .el-dialog__header {
    background-color: rgba(0, 0, 0, 0.8);
    .header_title {
      display: flex;
      justify-content: space-between;
      align-items: center;
      color: #fff;
      font-weight: 900;
      font-size: 16px;
      font-family: 'FZLTXIHJW';
      span:nth-child(2):hover {
        cursor: pointer;
      }
    }
    .el-dialog__title,
    .el-dialog__headerbtn .el-dialog__close {
      color: #fff;
      font-weight: 900;
      font-size: 16px;
      font-family: 'FZLTXIHJW';
    }
  }
}
.tablePublic input[type="checkbox"]:checked+.checkmark {
.tablePublic input[type="checkbox"]:checked + .checkmark {
  background: #000 !important;
  /* 选中时的背景色 */
}
@@ -2014,8 +1952,6 @@
  user-select: none;
}
body {
  background-color: #e6e6e6;
}
}
src/books/sportsAndHealth/view/components/index.vue
@@ -26,6 +26,7 @@
      <ChapterThree
        v-if="showCatalogList.indexOf(4) > -1"
        :showPageList="loadPageList"
        @openPDFChange="swdtChange"
      />
      <!-- <ChapterFour
        v-if="showCatalogList.indexOf(5) > -1"
@@ -281,11 +282,18 @@
  },
  methods: {
    swdtChange(data) {
      if (this.$store.state.qiankun && this.$store.state.qiankun.chooseWords) {
        this.$store.state.qiankun.chooseWords({
          type: data.type,
          data: data.data,
        });
      console.log("子应用pdf", data);
      if (this.$store.state.qiankun && this.$store.state.qiankun.openPDF) {
        if (data.type == "pdf") {
          this.$store.state.qiankun.openPDF({
            data: data.data,
          });
        } else {
          this.$store.state.qiankun.chooseWords({
            type: data.type,
            data: data.data,
          });
        }
      }
    },
    changeDomViewer() {
@@ -724,23 +732,19 @@
          autoplay: {
            //自动开始
            delay: 3000, //时间间隔
            disableOnInteraction: false //*手动操作轮播图后不会暂停*
            disableOnInteraction: false, //*手动操作轮播图后不会暂停*
          },
          paginationClickable: true,
          slidesPerView: 1, // 一组三个
          spaceBetween: 30, // 间隔
          // 如果需要前进后退按钮
          navigation: {
            nextEl: dom.querySelector(
              ".swiper-button-next"
            ),
            prevEl: dom.querySelector(
              ".swiper-button-prev"
            )
            nextEl: dom.querySelector(".swiper-button-next"),
            prevEl: dom.querySelector(".swiper-button-prev"),
          },
          // 窗口变化,重新init,针对F11全屏和放大缩小,必须加
          observer: true,
          observeParents: true
          observeParents: true,
          // // 如果需要分页器
          // pagination: {
          //   el: (this.container ? this.container : document).querySelector(
@@ -763,12 +767,8 @@
          spaceBetween: 30, // 间隔
          // 如果需要前进后退按钮
          navigation: {
            nextEl: dom.querySelector(
              ".swiper-button-next"
            ),
            prevEl: dom.querySelector(
              ".swiper-button-prev"
            )
            nextEl: dom.querySelector(".swiper-button-next"),
            prevEl: dom.querySelector(".swiper-button-prev"),
          },
          // 窗口变化,重新init,针对F11全屏和放大缩小,必须加
          observer: true,
@@ -787,8 +787,8 @@
              var paginationInfoEl = dom.querySelector(".pageBox");
              if (paginationInfoEl)
                paginationInfoEl.textContent = currentPage + "/" + totalPages;
            }
          }
            },
          },
        });
      }
    },
src/books/sportsAndHealth/view/components/testPp.vue
@@ -1461,7 +1461,6 @@
        :visible.sync="dialogVisible"
        width="60vw"
        top="2vh"
        append-to-body
        lock-scroll
        :show-close="false"
        class="custom-dialog"
@@ -1693,9 +1692,18 @@
    },
    toUrl(val) {
      if (val) {
        this.dialogVisible = true;
        this.p_md5 = this.chapter002.pdfMd5[val].md5;
        this.pdfTitle = this.chapter002.pdfMd5[val].name;
        const obj = {
          type: "pdf",
          data: {
            md5: this.chapter002.pdfMd5[val].md5,
            title: this.chapter002.pdfMd5[val].name,
          },
        };
        this.$emit("openPDFChange", obj);
        // this.dialogVisible = true;
        // this.p_md5 = this.chapter002.pdfMd5[val].md5;
        // this.pdfTitle = this.chapter002.pdfMd5[val].name;
      }
    },
    async getVideo(md5, val) {
@@ -1730,3 +1738,69 @@
  },
};
</script>
<style lang="less">
.pdfModal {
  width: 100%;
  height: 90vh;
}
.custom-dialog {
  overflow: hidden !important;
  /deep/ .el-dialog__body {
    padding: 0;
  }
  /deep/ .el-dialog__header {
    background-color: rgba(0, 0, 0, 0.8);
    .header_title {
      display: flex;
      justify-content: space-between;
      align-items: center;
      color: #fff;
      font-weight: 900;
      font-size: 16px;
      font-family: "FZLTXIHJW";
      span:nth-child(2):hover {
        cursor: pointer;
      }
    }
    /deep/.el-dialog__title,
    /deep/ .el-dialog__headerbtn .el-dialog__close {
      color: #fff;
      font-weight: 900;
      font-size: 16px;
      font-family: "FZLTXIHJW";
    }
  }
}
@media screen and (max-width: 1024px) {
  .pdfModal {
    width: 100%;
    height: 80vh;
  }
  .custom-dialog {
    /deep/ .el-dialog {
      width: 90vw !important;
    }
  }
}
@media screen and (max-width: 800px) {
  .pdfModal {
    width: 100%;
    height: 60vh;
  }
  .custom-dialog {
    /deep/ .el-dialog {
      width: 90vw !important;
    }
  }
}
</style>
src/components/dragQuestion/index.vue
New file
@@ -0,0 +1,527 @@
<template>
  <div class="drag-box">
    <div
      v-for="(item, index) in drageQuestion"
      :key="item.id"
      @mousemove="mousemove"
      @mouseup="mouseup"
      :num="index"
      class="question-box"
    >
      <!-- 题干 -->
      <div class="stem">
        <span v-for="(ditem, dindex) in item.stem" :key="dindex">
          <span v-if="typeof ditem == 'string'">
            {{ ditem }}
          </span>
          <span class="space" v-else>{{ item.userAnswer[ditem.num].txt }}</span>
        </span>
      </div>
      <!-- 选项 -->
      <ul class="option-box">
        <li v-for="(citem, cindex) in item.option" :key="cindex">
          <span class="drag-option" @mousedown="mousedown" v-show="item.userAnswer.findIndex(eitem => eitem.value == citem.value) == -1"
            >{{ citem.value }}、{{ citem.txt }}</span
          >
        </li>
      </ul>
      <!-- 提交后解析 -->
      <div class="analysis" v-if="item.isComplete">
        <el-collapse
          v-model="item.isUnfold"
          @change="handleChange(item)"
          accordion
          v-if="item.questionType != 'shortAnswer'"
        >
          <el-collapse-item :name="item.id" class="objective">
            <template #title>
              <div class="headerBox">
                <div class="headerConent">
                  <p class="correct" :title="arrayToString(item.answer)">
                    <span>正确答案:</span>
                    <span
                      v-html="arrayToString(item.answer)"
                      class="correctBox"
                    ></span>
                  </p>
                  <p
                    :class="
                      item.isRight
                        ? 'correct textOverFlow'
                        : 'error textOverFlow'
                    "
                    :title="arrayToString(item.userAnswer)"
                    style="margin-left: 20px"
                  >
                    <span>您的答案:</span>
                    <span
                      class="errorBox"
                      v-if="item.userAnswer.length"
                      >
                      <span v-for="(answerItem,answerIdnex) in item.userAnswer" :key="answerIdnex">{{answerItem.value}}<span v-if="answerIdnex !== item.userAnswer.length - 1 && answerItem.value">,</span></span>
                      </span
                    >
                  </p>
                </div>
                <p class="difficulty" v-if="item.difficulty">
                  <span style="color: #333">难度等级:</span>
                  <el-rate
                    v-model="item.difficulty"
                    :max="3"
                    size="large"
                    disabled
                    disabled-void-color="#949494"
                  />
                </p>
                <!-- 正确错误图标 -->
                <span class="mr-10 judge-icon">
                  <svg
                    v-if="item.isRight"
                    t="1716986419862"
                    class="icon"
                    viewBox="0 0 1820 1024"
                    version="1.1"
                    xmlns="http://www.w3.org/2000/svg"
                    p-id="18767"
                    xmlns:xlink="http://www.w3.org/1999/xlink"
                    width="40"
                    height="20"
                  >
                    <path
                      d="M1439.374222 216.007111s-169.472 56.490667-367.179852 282.443852C888.604444 703.222519 846.241185 787.949037 775.632593 900.93037 768.568889 893.866667 662.651259 689.095111 380.207407 540.814222l148.290371-141.226666s134.162963 91.790222 225.953185 261.262222c0 0 233.016889-360.116148 684.923259-536.642371v91.799704z m0 0"
                      fill="#1AFA29"
                      p-id="18768"
                    ></path>
                  </svg>
                  <svg
                    v-if="item.isRight == false"
                    t="1716987085767"
                    class="icon"
                    viewBox="0 0 1024 1024"
                    version="1.1"
                    xmlns="http://www.w3.org/2000/svg"
                    p-id="25745"
                    xmlns:xlink="http://www.w3.org/1999/xlink"
                    width="20"
                    height="20"
                  >
                    <path
                      d="M116.579135 38.64349531L38.703935 103.74399781c138.82075969 102.96027281 268.24660875 221.31426938 381.68489719 339.96758156C246.29374906 618.40145938 109.95003031 790.19602344 38.10817906 859.25288281l148.35573469 123.62658094c52.61360812-108.17625656 167.23381594-272.86683656 320.56281844-445.01635875 153.50744156 173.21056312 268.36844625 338.43166313 321.38977781 447.49243969 0 0 144.5682225-152.96636906 157.47435281-129.29729625-55.80632344-62.49011156-191.37776625-244.16501625-374.17990593-430.27403438 104.68422375-107.1132975 222.15274031-213.10127719 347.60304468-306.24740437L925.17746562 56.03842156C782.85412063 126.51895625 647.69328031 231.09093594 526.07845437 342.39755 403.34886594 226.82662719 264.46095125 116.16373719 116.579135 38.64349531L116.579135 38.64349531zM116.579135 38.64349531"
                      fill="#d81e06"
                      p-id="25746"
                    ></path>
                  </svg>
                </span>
                <div class="headerConent sitgBox">
                  <p v-if="!item.isUnfold">
                    <span class="analysisColor">展开解析</span><el-image />
                  </p>
                  <p v-else>
                    <span class="analysisColor">收起解析</span><el-image />
                  </p>
                </div>
              </div>
            </template>
            <!-- 解析 -->
            <div v-if="item.analysisCon" v-html="item.analysisCon"></div>
            <div v-else>暂无数据</div>
          </el-collapse-item>
          <!-- 主观题 -->
        </el-collapse>
      </div>
    </div>
    <div class="drag-bottom-btn">
      <el-button @click="saveData">保存</el-button>
      <el-button @click="submitData">提交</el-button>
      <el-button @click="redio">重做</el-button>
      <el-button @click="seeAnswer">查看答案</el-button>
    </div>
  </div>
</template>
<script>
export default {
  name: "drag",
  props: {
    question: {
      type: Array,
      default: [],
    },
    page: {
      type: Number,
    },
  },
  watch: {
    question: {
      handler(newValue, oldValue) {
        console.log("监听传值", newValue);
        let oldAnswer = localStorage.getItem(
          this.config.activeBook.name + "-drag-" + this.page
        );
        if (oldAnswer) {
          oldAnswer = JSON.parse(oldAnswer);
          for (let index = 0; index < newValue.length; index++) {
            // 旧答案赋值
            const item = newValue[index];
            const oldItem = oldAnswer.find((citem) => citem.id == item.id);
            item.userAnswer = oldItem.userAnswer;
            // 隐藏旧答案里的选项
            // for (let cindex = 0; cindex < item.option.length; cindex++) {
            //   const citem = item.option[cindex];
            //   if(item.userAnswer.findIndex(ditem => ditem.value == citem.value) > -1) {
            //     citem.isShow = false
            //   }
            // }
          }
        }
        this.drageQuestion = newValue;
      },
      immediate: true,
    },
  },
  data() {
    return {
      questionIndex: null,
      drageQuestion: [],
      moveDom: null,
      parentDom: null,
      parentX: null, // 父盒子在页面的位置
      parentY: null,
      maxX: null,
      maxY: null,
      spaceList: [],
    };
  },
  mounted() {
    console.log("传值", this.question);
  },
  methods: {
    // 1.选项拖拽
    // 2.限制拖拽移动只能在题范围内
    // 3.限制鼠标松开要在空格的范围内
    mousedown(e) {
      e.preventDefault();
      console.log("鼠标按下", e);
      this.moveDom = e.toElement;
      this.parentDom = this.getParentWithClass(e.toElement, "question-box");
      this.questionIndex = this.parentDom.getAttribute("num");
      this.getSpaceInfo(this.parentDom);
      const parentInfo = this.parentDom.getBoundingClientRect();
      this.parentX = parentInfo.x;
      this.parentY = parentInfo.y;
      this.maxX = parentInfo.x + this.parentDom.clientWidth;
      this.maxY = parentInfo.y + this.parentDom.clientHeight;
      console.log("父盒子", parentInfo);
    },
    // 鼠标移动
    mousemove(e) {
      if (!this.moveDom) return false;
      this.moveDom.style.position = "absolute";
      // 限制在此题范围内拖动
      if (e.x <= this.maxX && e.y < this.maxY) {
        const moveX = e.x - this.parentX;
        const moveY = e.y - this.parentY;
        this.moveDom.style.left = moveX + "px";
        this.moveDom.style.top = moveY + "px";
        // 如果移动到空格范围内,将答案填入空格
        if (this.spaceList.length) {
          for (let index = 0; index < this.spaceList.length; index++) {
            const item = this.spaceList[index];
            if (
              e.x >= item.minX &&
              e.x <= item.maxX &&
              e.y >= item.minY &&
              e.y <= item.maxY
            ) {
              this.$set(
                this.drageQuestion[this.questionIndex].userAnswer,
                index,
                {
                  value:this.moveDom.textContent.charAt(0),
                  txt:this.moveDom.textContent.slice(2)
                }
              );
              this.moveDom.style.display = "none";
            }
          }
        }
      }
    },
    // 鼠标抬起
    mouseup(e) {
      if (this.moveDom) {
        this.moveDom = null;
      }
    },
    // 获取题目空格坐标
    getSpaceInfo(parentDom) {
      let arr = [];
      const list = parentDom.querySelectorAll(".space");
      for (let index = 0; index < list.length; index++) {
        const itemInfo = list[index].getBoundingClientRect();
        arr.push({
          minX: itemInfo.x,
          maxX: itemInfo.x + itemInfo.width,
          minY: itemInfo.y,
          maxY: itemInfo.y + itemInfo.height,
        });
      }
      this.spaceList = arr;
      console.log("空格范围", arr);
    },
    // 获取父元素
    getParentWithClass(element, className) {
      while (element.parentElement) {
        element = element.parentElement;
        if (element.classList.contains(className)) {
          return element;
        }
      }
    },
    // 保存答案
    saveData(e) {
      let answerArr = [];
      for (let index = 0; index < this.drageQuestion.length; index++) {
        const item = this.drageQuestion[index];
        answerArr.push({
          id: item.id,
          userAnswer: item.userAnswer,
        });
      }
      localStorage.setItem(
        this.config.activeBook.name + "-drag-" + this.page,
        JSON.stringify(answerArr)
      );
    },
    // 提交
    submitData() {
      let arr = [];
      const data = this.drageQuestion;
      for (let index = 0; index < data.length; index++) {
        const item = data[index];
        for (let cindex = 0; cindex < item.userAnswer.length; cindex++) {
          let citem = item.userAnswer[cindex];
          arr[cindex] = citem.value
        }
        item.isRight = arr == item.answer
        item.isComplete = true
      }
      console.log(arr,this.drageQuestion[0]);
    },
    redio() {
      localStorage.removeItem(
        this.config.activeBook.name + "-drag-" + this.page
      );
      let arr = []
      // const data = []
      for (let index = 0; index < this.drageQuestion.length; index++) {
        const item = this.drageQuestion[index];
        item.isComplete = false
        for (let cindex = 0; cindex < item.option.length; cindex++) {
          const citem = item.option[cindex];
          arr.push(
            {
              txt:'',
              value:''
            }
          )
        }
        item.userAnswer = arr
      }
      console.log(this.drageQuestion);
      const optionList = (
        this.container ? this.container : document
      ).querySelectorAll(".drag-option");
      for (let index = 0; index < optionList.length; index++) {
        const item = optionList[index];
        item.style.position = null
        item.style.top = null
        item.style.left = null
      }
    },
    seeAnswer() {
      for (let index = 0; index < this.drageQuestion.length; index++) {
        const item = this.drageQuestion[index];
        item.isComplete = true;
      }
    },
    // 数组转为字符串方法
    arrayToString(data) {
      // 检查是否为数组
      if (Array.isArray(data)) {
        // 使用 join 方法将数组转换为字符串,默认使用逗号分隔
        return data.join(",").replace(/<[^>]*>/g, "");
      } else {
        // 如果不是数组,直接返回原始值
        return data.replace(/<[^>]*>/g, "");
      }
    },
    isHaveAnswer(data) {
      if (typeof data == "string") {
        data = data
          .replace(/<[^>]*>/g, "")
          .replace(/&nbsp;/g, "")
          .trim();
        if (data.length) {
          return true;
        } else {
          return false;
        }
      } else {
        const answer = data.find((item) => item.length > 0);
        if (answer) {
          return true;
        } else {
          return false;
        }
      }
    },
  },
};
</script>
<style lang="less" scoped>
p {
  text-indent: 0 !important;
}
.drag-box {
  position: relative;
}
.space {
  display: inline-block;
  text-align: center;
  min-width: 60px !important;
  min-height: 15px !important;
}
.option-box {
  display: flex;
  justify-content: space-evenly;
}
.drag-option {
  cursor: pointer;
}
.stem {
  line-height: 2em;
}
.drag-bottom-btn {
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  .el-button {
    height: 30px;
    width: 78px;
    padding: 4px 10px;
  }
}
.analysis {
  margin: 20px 0;
  width: 94%;
  // margin-left: 12px;
}
.judge-icon {
  display: flex;
  align-items: center;
}
.el-collapse {
  width: 100%;
}
/** 解析 */
.objective {
  /deep/ .el-collapse-item__header {
    min-height: 48px;
    height: min-content;
    padding: 0 20px;
    font-size: 16px;
    background-color: #f4f4f4;
    .headerBox {
      width: 100%;
      height: 100%;
      display: flex;
      justify-content: space-between;
      align-items: center;
      flex-wrap: wrap;
      .headerConent {
        height: 100%;
        display: flex;
        align-items: center;
        flex-wrap: wrap;
        p {
          margin-bottom: 0;
        }
        .el-image {
          width: 9px;
          height: 9px;
        }
        .correct {
          color: #1fbc1f;
          width: 180px;
          text-align: left;
          display: flex;
          span:nth-child(1) {
            display: inline-block;
            text-indent: 0;
            width: 82px;
          }
        }
        .correctBox {
          width: 100px;
          height: min-content;
          word-break: break-all;
          text-overflow: ellipsis;
          display: -webkit-box;
          -webkit-box-orient: vertical;
          -webkit-line-clamp: 1; /* 这里是超出几行省略 */
          overflow: hidden;
        }
        .error {
          display: flex;
          text-align: left;
          width: 170px;
          color: #ee1818;
          span {
            height: min-content;
            width: 100px;
          }
          .errorBox {
            width: 100px;
            // height: 48px;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            ::v-deep {
              img {
                max-height: 48px;
              }
            }
          }
        }
      }
    }
    .checkAnalysis {
      cursor: pointer;
      height: 100%;
      display: flex;
      align-items: center;
    }
  }
}
/deep/ .el-collapse-item__header:focus:not(:hover) {
  color: #333;
}
/deep/ .el-collapse-item__content {
  width: 93%;
  padding: 0 20px;
  background-color: #f4f4f4;
  font-size: 16px;
  color: #333;
}
/deep/ .el-collapse-item__arrow {
  display: none;
}
</style>
src/components/examinations/index.vue
@@ -561,7 +561,7 @@
  },
  mounted() {
    this.getCollectIdList();
    this.getErrorList()
    this.getErrorList();
  },
  methods: {
    // 数组转为字符串方法
@@ -621,7 +621,7 @@
    },
    // 批改题目  (练习,我的做题,我的收藏模式下)
    handleQuestion() {
      let errorId = []
      let errorId = [];
      const list = this.cardData;
      for (let index = 0; index < list.length; index++) {
        const item = list[index];
@@ -665,36 +665,38 @@
              }
            }
          }
          if(citem.isRight == false) {
            errorId.push(citem.id)
          if (citem.isRight == false) {
            errorId.push(citem.id);
          }
        }
      }
      this.cardData = list;
      let oldErrodId = this.allError.find(item => item.type == this.sourceType).errorList
      let oldErrodId = this.allError.find(
        (item) => item.type == this.sourceType
      ).errorList;
      for (let index = 0; index < errorId.length; index++) {
        const item = errorId[index];
        if(oldErrodId.indexOf(item == -1)) {
          oldErrodId.push(item)
        if (oldErrodId.indexOf(item == -1)) {
          oldErrodId.push(item);
        }
      }
      for (let cindex = 0; cindex < this.allError.length; cindex++) {
        const citem = this.allError[cindex];
        if(citem.type == this.sourceType) citem.errorList = oldErrodId
        if (citem.type == this.sourceType) citem.errorList = oldErrodId;
      }
      this.MG.identity
        .setUserKey({
          setKeyRequests: [
            {
              domain: 'errorData',
              domain: "errorData",
              key: this.config.activeBook.bookId,
              value: JSON.stringify(this.allError)
            }
          ]
              value: JSON.stringify(this.allError),
            },
          ],
        })
        .then((res) => {
          console.log('错题已保存',this.allError)
        })
          console.log("错题已保存", this.allError);
        });
    },
    getParentWithClass(element, className) {
      while (element.parentElement) {
@@ -792,6 +794,8 @@
    },
    // 获取收藏id列表
    getCollectIdList() {
      const token = localStorage.getItem("token");
      if (!token) return false;
      this.MG.identity
        .getUserKey({
          domain: "collectData",
@@ -821,6 +825,8 @@
    },
    // 获取错题id列表
    getErrorList() {
      const token = localStorage.getItem("token");
      if (!token) return false;
      this.MG.identity
        .getUserKey({
          domain: "errorData",
@@ -841,7 +847,8 @@
              ).errorList;
            }
          } catch (error) {}
        });
        })
        .catch(() => {});
    },
  },
};
src/components/graffiti/components/brushSize.vue
New file
@@ -0,0 +1,83 @@
<template>
  <div class="wrap-range">
    <!-- 为了不在子组件中变更值,不用v-model -->
    <input
      type="range"
      :value="brushSize"
      min="1"
      max="30"
      title="调整笔刷粗细"
      @change="(event) => $emit('change-size', +event.target.value)"
    />
  </div>
</template>
<script>
export default {
  props: {
    size: {
      type: Number,
      default: 5,
    },
  },
  computed:{
    brushSize() {
      return this.size
    }
  }
};
// const brushSize = computed(() => props.size);
</script>
<style scoped>
.wrap-range input {
  width: 150px;
  height: 20px;
  margin: 0;
  transform-origin: 75px 75px;
  border-radius: 15px;
  -webkit-appearance: none;
  appearance: none;
  outline: none;
  position: relative;
}
.wrap-range input::after {
  display: block;
  content: "";
  width: 0;
  height: 0;
  border: 5px solid transparent;
  border-right: 150px solid #00ccff;
  border-left-width: 0;
  position: absolute;
  left: 0;
  top: 5px;
  border-radius: 15px;
  z-index: 0;
}
.wrap-range input[type="range"]::-webkit-slider-thumb,
.wrap-range input[type="range"]::-moz-range-thumb {
  -webkit-appearance: none;
}
.wrap-range input[type="range"]::-webkit-slider-runnable-track,
.wrap-range input[type="range"]::-moz-range-track {
  height: 10px;
  border-radius: 10px;
  box-shadow: none;
}
.wrap-range input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  height: 20px;
  width: 20px;
  margin-top: -1px;
  background: #ffffff;
  border-radius: 50%;
  box-shadow: 0 0 8px #00ccff;
  position: relative;
  z-index: 999;
}
</style>
src/components/graffiti/components/colorPicker.vue
New file
@@ -0,0 +1,61 @@
<template>
  <div>
    <span
      v-for="(color, index) of colorList"
      class="color-item"
      :class="{ active: colorSelected === color }"
      :style="{ backgroundColor: color }"
      :key="index"
      @click="onChangeColor(color)"
    ></span>
  </div>
</template>
<script>
export default {
  props: {
    color: {
      type: String,
    },
  },
  computed:{
    colorSelected() {
      return this.color
    }
  },
  data() {
    return {
      colorList: [
        "#000000",
        "#808080",
        "#FF3333",
        "#0066FF",
        "#FFFF33",
        "#33CC66",
      ],
    };
  },
  methods: {
    onChangeColor(color) {
      this.$emit("change-color", color);
    },
  },
};
</script>
<style scoped>
.color-item {
  display: inline-block;
  width: 32px;
  height: 32px;
  margin: 0 4px;
  box-sizing: border-box;
  border: 4px solid white;
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.2);
  cursor: pointer;
  transition: 0.3s;
}
.color-item.active {
  box-shadow: 0 0 15px #00ccff;
}
</style>
src/components/graffiti/components/toolBtns.vue
New file
@@ -0,0 +1,74 @@
<template>
  <div class="tools">
    <button
      v-for="(item, index) of toolList"
      :key="index"
      :class="{ active: toolSelected === item.name }"
      :title="item.title"
      @click="onChangeTool(item.name)"
    >
      <i :class="['iconfont', item.icon]"></i>
    </button>
  </div>
</template>
<script>
export default {
  props: {
    tool: {
      type: String,
      default: "brush",
    },
  },
  computed:{
    toolSelected() {
      return this.tool
    }
  },
  data() {
    return {
      toolList: [
        { name: "brush", title: "画笔", icon: "icon-qianbi" },
        { name: "eraser", title: "橡皮擦", icon: "icon-xiangpi" },
        { name: "clear", title: "清空", icon: "icon-qingchu" },
        { name: "undo", title: "撤销", icon: "icon-chexiao" },
        { name: "save", title: "保存", icon: "icon-fuzhi" },
      ],
    };
  },
  methods: {
    onChangeTool(tool) {
      this.$emit("change-tool", tool);
    },
  },
};
</script>
<style scoped>
.tools button {
  /* border-radius: 50%; */
  width: 32px;
  height: 32px;
  background-color: rgba(255, 255, 255, 0.7);
  border: 1px solid #eee;
  outline: none;
  cursor: pointer;
  box-sizing: border-box;
  margin: 0 8px;
  padding: 0;
  text-align: center;
  color: #ccc;
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
  transition: 0.3s;
}
.tools button.active,
.tools button:active {
  /* box-shadow: 0 0 15px #00CCFF; */
  color: #00ccff;
}
.tools button i {
  font-size: 20px;
}
</style>
src/components/graffiti/index.vue
New file
@@ -0,0 +1,234 @@
<template>
  <div class="page">
    <div class="main">
      <div id="canvas_panel">
        <canvas
          id="canvas"
          :style="{
            backgroundImage: `url(${backgroundImage})`,
            backgroundSize: 'cover',
            backgroundPosition: 'center',
          }"
          >当前浏览器不支持canvas。</canvas
        >
      </div>
    </div>
    <div class="footer">
      <BrushSize :size="brushSize" @change-size="onChangeSize" />
      <ColorPicker :color="brushColor" @change-color="onChangeColor" />
      <ToolBtns :tool="brushTool" @change-tool="onChangeTool" />
    </div>
  </div>
</template>
<script>
import BrushSize from "./components/brushSize.vue";
import ColorPicker from "./components/colorPicker.vue";
import ToolBtns from "./components/toolBtns.vue";
export default {
  name: "graffiti",
  components: { BrushSize, ColorPicker, ToolBtns },
  data() {
    return {
      canvas: null,
      context: null,
      painting: false,
      historyData: [], // 存储历史数据,用于撤销
      brushSize: 5, // 笔刷大小
      brushColor: "#000000", // 笔刷颜色
      brushTool: "brush",
      canvasOffset: {
        left: 0,
        top: 0,
      },
      backgroundImage:
        "https://t7.baidu.com/it/u=1819248061,230866778&fm=193&f=GIF",
    };
  },
  mounted() {
    this.canvas = document.getElementById("canvas");
    if (this.canvas.getContext) {
      this.context = this.canvas.getContext("2d", { willReadFrequently: true });
      this.initCanvas();
      // window.addEventListener('resize', updateCanvasPosition);
      window.addEventListener("scroll", this.updateCanvasOffset); // 添加滚动条滚动事件监听器
      this.getCanvasOffset();
      this.context.lineGap = "round";
      this.context.lineJoin = "round";
      this.canvas.addEventListener("mousedown", this.downCallback);
      this.canvas.addEventListener("mousemove", this.moveCallback);
      this.canvas.addEventListener("mouseup", this.closePaint);
      this.canvas.addEventListener("mouseleave", this.closePaint);
    }
    this.toolClear();
  },
  methods: {
    changeBackground(imgUrl) {
      this.backgroundImage = imgUrl;
    },
    initCanvas() {
      const that = this
      function resetCanvas() {
        const elPanel = document.getElementById("canvas_panel");
        that.canvas.width = elPanel.clientWidth;
        that.canvas.height = elPanel.clientHeight;
        that.context = that.canvas.getContext("2d", {
          willReadFrequently: true,
        }); // 添加这一行
        that.context.fillStyle = "white";
        that.context.fillRect(0, 0, that.canvas.width, that.canvas.height);
        that.context.fillStyle = "black";
        that.getCanvasOffset(); // 更新画布位置
      }
      resetCanvas();
      window.addEventListener("resize", resetCanvas);
    },
    // 获取canvas的偏移值
    getCanvasOffset() {
      const rect = this.canvas.getBoundingClientRect();
      this.canvasOffset.left = rect.left * (this.canvas.width / rect.width); // 兼容缩放场景
      this.canvasOffset.top = rect.top * (this.canvas.height / rect.height);
    },
    // 计算当前鼠标相对于canvas的坐标
    calcRelativeCoordinate(x, y) {
      return {
        x: x - this.canvasOffset.left,
        y: y - this.canvasOffset.top,
      };
    },
    downCallback(event) {
      // 先保存之前的数据,用于撤销时恢复(绘制前保存,不是绘制后再保存)
      const data = this.context.getImageData(
        0,
        0,
        this.canvas.width,
        this.canvas.height
      );
      this.saveData(data);
      const { clientX, clientY } = event;
      const { x, y } = this.calcRelativeCoordinate(clientX, clientY);
      this.context.beginPath();
      this.context.moveTo(x, y);
      this.context.lineWidth = this.brushSize;
      this.context.strokeStyle =
        this.brushTool === "eraser" ? "#FFFFFF" : this.brushColor;
      this.painting = true;
    },
    moveCallback(event) {
      if (!this.painting) {
        return;
      }
      const { clientX, clientY } = event;
      const { x, y } = this.calcRelativeCoordinate(clientX, clientY);
      this.context.lineTo(x, y);
      this.context.stroke();
    },
    closePaint() {
      this.painting = false;
    },
    updateCanvasOffset() {
      this.getCanvasOffset(); // 重新计算画布的偏移值
    },
    onChangeSize(size) {
      this.brushSize = size;
    },
    onChangeColor(color) {
      this.brushColor = color;
    },
    onChangeTool(tool) {
      this.brushTool = tool;
      switch (tool) {
        case "clear":
          this.toolClear();
          break;
        case "undo":
          this.toolUndo();
          break;
        case "save":
          this.toolSave();
          break;
      }
    },
    toolClear() {
      this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
      this.resetToolActive();
    },
    toolSave() {
      const imageDataUrl = this.canvas.toDataURL("image/png");
      console.log(imageDataUrl);
      // const imgUrl = canvas.toDataURL('image/png');
      // const el = document.createElement('a');
      // el.setAttribute('href', imgUrl);
      // el.setAttribute('target', '_blank');
      // el.setAttribute('download', `graffiti-${Date.now()}`);
      // document.body.appendChild(el);
      // el.click();
      // document.body.removeChild(el);
      // resetToolActive();
    },
    toolUndo() {
      if (this.historyData.length <= 0) {
        this.resetToolActive();
        return;
      }
      const lastIndex = this.historyData.length - 1;
      this.context.putImageData(this.historyData[lastIndex], 0, 0);
      this.historyData.pop();
      this.resetToolActive();
    },
    // 存储数据
    saveData(data) {
      this.historyData.length >= 50 && this.historyData.shift(); // 设置储存上限为50步
      this.historyData.push(data);
    },
    // 清除、撤销、保存状态不需要保持,操作完后恢复笔刷状态
    resetToolActive() {
      setTimeout(() => {
        this.brushTool = "brush";
      }, 1000);
    },
  },
};
</script>
<style scoped>
.page {
  display: flex;
  flex-direction: column;
  width: 1038px;
  height: 866px;
}
.main {
  flex: 1;
}
.footer {
  display: flex;
  justify-content: space-around;
  align-items: center;
  height: 88px;
  background-color: #fff;
}
#canvas_panel {
  margin: 12px;
  height: calc(100% - 24px);
  /* 消除空格影响 */
  font-size: 0;
  background-color: #fff;
  box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
}
#canvas {
  cursor: crosshair;
  /* background: url('https://t7.baidu.com/it/u=1819248061,230866778&fm=193&f=GIF') no-repeat !important; */
}
</style>
src/components/pdfview/index.vue
@@ -78,7 +78,9 @@
      window.open(await getResourcePath(this.preViewMd5));
    },
    domViewer() {
      let ele = document.getElementById("imageParent");
      let ele = (this.container ? this.container : document).getElementById(
        "imageParent"
      );
      this.viewerCon = new Viewer(ele, {
        inline: false,
        container: this.container