Przeglądaj źródła

选择填空题卡结构修改

zhangjie 3 lat temu
rodzic
commit
b50f9e792a
27 zmienionych plików z 463 dodań i 1749 usunięć
  1. 1 0
      src/main.js
  2. 7 19
      src/modules/card/api.js
  3. 3 3
      src/modules/card/assets/styles/card-design.scss
  4. 165 453
      src/modules/card/assets/styles/card-preview.scss
  5. 8 44
      src/modules/card/components/CardDesign.vue
  6. 6 2
      src/modules/card/elementModel.js
  7. 36 159
      src/modules/card/elements/card-head/CardHead.vue
  8. 0 105
      src/modules/card/elements/card-head/CardHeadBodyAutoResize.vue
  9. 0 48
      src/modules/card/elements/card-head/CardHeadSample.vue
  10. 0 156
      src/modules/card/elements/card-head/cardHeadSpin/HeadDynamic.vue
  11. 0 41
      src/modules/card/elements/card-head/cardHeadSpin/HeadNotice.vue
  12. 0 55
      src/modules/card/elements/card-head/cardHeadSpin/HeadStdinfo.vue
  13. 0 57
      src/modules/card/elements/card-head/cardHeadSpin/HeadStdno.vue
  14. 7 15
      src/modules/card/elements/card-head/model.js
  15. 7 170
      src/modules/card/elements/fill-line/EditFillLine.vue
  16. 8 43
      src/modules/card/elements/fill-line/ElemFillLine.vue
  17. 31 81
      src/modules/card/elements/fill-line/model.js
  18. 7 102
      src/modules/card/elements/fill-question/EditFillQuestion.vue
  19. 14 72
      src/modules/card/elements/fill-question/ElemFillQuestion.vue
  20. 82 49
      src/modules/card/elements/fill-question/model.js
  21. 2 0
      src/modules/card/enumerate.js
  22. 48 0
      src/modules/card/pageModel.js
  23. 13 17
      src/modules/card/router/index.js
  24. 11 54
      src/modules/card/views/CardEdit.vue
  25. 2 2
      src/modules/card/views/CardHeadEdit.vue
  26. 3 2
      src/modules/questions/routes/routes.js
  27. 2 0
      src/store/index.js

+ 1 - 0
src/main.js

@@ -11,6 +11,7 @@ import "./plugins/vueAwesome";
 import "./directives/directives.js";
 import "./filters/filters.js";
 import "./assets/styles/index.scss";
+import "./modules/card/assets/styles/module.scss";
 
 import globalVuePlugins from "./plugins/globalVuePlugins";
 Vue.use(globalVuePlugins);

+ 7 - 19
src/modules/card/api.js

@@ -62,25 +62,13 @@ export const cardConfigInfos = () => {
     createTime: 1632291806278,
     updateId: null,
     updateTime: 1632291806278,
-    schoolId: "2",
-    orgId: "173436480729907200",
-    name: "测试题卡规则1",
-    examNumberStyle: "PRINT",
-    paperType: "PRINT",
-    examAbsent: true,
-    writeSign: true,
-    requiredFields:
-      '[{"code":"ticketNumber","name":"考号","enable":true,"selected":false},{"code":"siteNumber","name":"座位号","enable":true,"selected":false},{"code":"studentName","name":"姓名","enable":true,"selected":false},{"code":"courseName","name":"课程名称","enable":true,"selected":false}]',
-    extendFields: "[]",
-    // extendFields:
-    //   '[{"code":"studentCode","name":"学号","enable":true,"selected":false},{"code":"courseCode","name":"课程代码","enable":true,"selected":false},{"code":"paperNumber","name":"试卷编号","enable":true,"selected":false},{"code":"campusName","name":"校区","enable":true,"selected":false},{"code":"examPlace","name":"考点","enable":true,"selected":false},{"code":"examRoom","name":"考场","enable":true,"selected":false},{"code":"examDate","name":"考试日期","enable":true,"selected":false},{"code":"examTime","name":"考试时间","enable":true,"selected":false}]',
-    titleRule: "测试题卡规则1",
-    attention: "测试题卡规则1",
-    objectiveAttention: "测试题卡规则1",
-    subjectiveAttention: "测试题卡规则1",
-    enable: true,
-    remark: "测试题卡规则1",
-    orgIds: null,
+    name: "测试题卡规则1-版头名称",
+    cardTitle: "测试题卡规则1-题卡标题",
+    attention:
+      "测试题卡规则1-注意事项\n测试题卡规则1-注意事项\n测试题卡规则1-注意事项",
+    objectiveAttention: "测试题卡规则1-客观题-注意事项",
+    subjectiveAttention: "测试题卡规则1-主观题-注意事项",
+    templateType: "MODEL_ONE",
   });
 };
 export const cardDetail = () => {

+ 3 - 3
src/modules/card/assets/styles/card-design.scss

@@ -377,13 +377,13 @@
 }
 
 .design-main {
-  padding: 70px 20px 50px 260px;
+  padding: 0 20px 50px 240px;
   min-height: 100%;
 }
 
 .design-control {
   position: fixed;
-  top: 50px;
+  top: 0;
   left: 240px;
   right: 0;
   height: 70px;
@@ -414,7 +414,7 @@
 .design-body {
   position: relative;
   min-height: 1242px;
-  padding-top: 50px;
+  padding-top: 70px;
 }
 
 // topic-list

+ 165 - 453
src/modules/card/assets/styles/card-preview.scss

@@ -58,11 +58,22 @@
     width: 1586px;
     height: 1122px;
 
-    .page-main {
-      &-inner {
-        padding: 60px 80px 86px;
+    &.page-box-0 {
+      .page-main-inner {
+        padding: 60px 40px 86px 160px;
+      }
+    }
+    &.page-box-1 {
+      .page-main-inner {
+        padding: 60px 160px 86px 40px;
       }
+      .page-main-side {
+        right: 40px;
+        left: auto;
+      }
+    }
 
+    .page-main {
       &-1 {
         .page-column-forbid-area {
           &::before {
@@ -141,7 +152,15 @@
     }
   }
 }
-
+.page-main-side {
+  position: absolute;
+  top: 60px;
+  bottom: 86px;
+  width: 120px;
+  left: 40px;
+  z-index: auto;
+  background-color: #e0e0e0;
+}
 // 分栏间距,默认20px
 // page-main-inner
 .page-main-inner {
@@ -150,7 +169,7 @@
   height: 100%;
   top: 0;
   left: 0;
-  padding: 60px 80px 86px;
+  padding: 60px 40px 86px 40px;
   z-index: 9;
   font-size: 0;
 }
@@ -304,14 +323,21 @@
       }
     }
   }
+
+  .page-number {
+    position: absolute;
+    width: 100%;
+    text-align: center;
+    bottom: -20px;
+  }
 }
 
 // locator
 .page-locators {
   position: absolute;
   top: 60px;
-  left: 80px;
-  right: 80px;
+  left: 160px;
+  right: 40px;
   bottom: 86px;
   z-index: 8;
 }
@@ -329,7 +355,7 @@
   }
   &:last-child {
     left: auto;
-    right: 96px;
+    right: 0;
   }
   li {
     position: absolute;
@@ -345,18 +371,17 @@
   }
 }
 .page-box-1 {
-  .page-locator-group {
-    &:first-child {
-      left: -30px;
-    }
+  .page-locators {
+    left: 40px;
+    right: 160px;
   }
 }
 // page-number
 .page-number {
-  position: absolute;
-  bottom: 40px;
   &-rect {
-    left: 152px;
+    position: absolute;
+    bottom: 40px;
+    left: 240px;
   }
   &-rect-list {
     font-size: 0;
@@ -365,23 +390,22 @@
       display: inline-block;
       vertical-align: top;
       font-size: 14px;
-      width: 24px;
-      height: 16px;
+      width: 30px;
+      height: 20px;
       border: 1px solid #000;
       margin-right: 10px;
       &.rect-li-act {
         height: 0;
         border: none;
-        border-bottom: 16px solid #000;
+        border-bottom: 20px solid #000;
       }
     }
   }
   &-text {
-    right: 25%;
-  }
-  &-text-cont {
-    height: 16px;
-    line-height: 16px;
+    &-cont {
+      height: 16px;
+      line-height: 16px;
+    }
   }
 }
 
@@ -426,51 +450,28 @@
 }
 // card-head
 .card-head {
-  &-top {
+  &-title {
     text-align: center;
     color: #000;
-  }
-  &-title {
     font-size: 24px;
     font-weight: bold;
     overflow: hidden;
 
     > h1 {
-      line-height: 33px;
+      line-height: 40px;
       white-space: nowrap;
       letter-spacing: -1px;
     }
   }
-  &-subtitle {
-    height: 44px;
-    font-family: $--font-family;
-    font-size: 14px;
-    overflow: hidden;
-    white-space: normal;
-    margin-bottom: 10px;
-
-    > p {
-      padding: 0 10px;
-      line-height: 22px;
-      white-space: pre;
-    }
-  }
-  &-body {
-    font-weight: normal;
-    .el-col {
-      padding-top: 5px;
-      padding-bottom: 5px;
-    }
-    &-spin {
-      padding: 5px 12px;
-      white-space: normal;
-      word-break: break-all;
-    }
-    .stdinfo-item {
+  &-info {
+    padding-bottom: 30px;
+    .info-item {
       height: 30px;
       line-height: 30px;
       position: relative;
       overflow: hidden;
+      width: 80%;
+      margin: 0 auto;
 
       &::after {
         content: "";
@@ -508,436 +509,147 @@
           background-color: #fff;
         }
         &:last-child {
-          margin-left: 80px;
           height: 100%;
         }
       }
     }
-    .head-stdno {
-      height: 100%;
-      padding: 0;
-      .stdno-empty {
-        font-weight: bold;
-        letter-spacing: 3px;
-        text-align: center;
-      }
-      .stdno-fill {
-        min-height: 284px;
-        height: 100%;
-        position: relative;
+  }
 
-        &-rect {
-          font-size: 0;
-          height: 27px;
-          border-bottom: 1px solid #333;
-        }
-        &-number {
-          display: inline-block;
-          vertical-align: top;
-          width: 7.692%;
-          height: 100%;
-          &:not(:last-child) {
-            border-right: 1px solid #333;
-          }
-        }
+  &-notice {
+    border: 1px solid #000;
+    padding: 6px 8px;
+    > h4 {
+      font-weight: normal;
+      margin-bottom: 8px;
+    }
+    &-cont {
+      line-height: 1.5;
+      font-size: 12px;
+      margin-bottom: 5px;
 
-        &-head {
-          position: absolute;
-          width: 100%;
-          height: 51px;
-          top: 0;
-          left: 0;
-          z-index: 9;
-
-          > h5 {
-            border-bottom: 1px solid #333;
-            line-height: 24px;
-            font-size: 16px;
-            font-weight: bold;
-            text-align: center;
-          }
-        }
+      > span {
+        display: block;
 
-        &-body {
-          position: absolute;
-          top: 0;
-          bottom: 0;
-          padding-top: 51px;
-          display: table;
-          width: 100%;
-        }
-        &-list {
-          display: table-cell;
-          width: 7.692%;
-          padding: 1px 0;
-        }
-        &-option {
-          margin: 8px auto;
+        &:first-child {
           width: 20px;
-          height: 14px;
-          font-size: 12px;
-          line-height: 1;
-          text-align: center;
-          color: #000;
-          // border-rect
-          border: 1px solid #000;
-          font-family: "Times New Roman", Arial, sans-serif;
-          > i {
-            display: inline-block;
-            transform: scale(0.67, 0.67);
-          }
+          white-space: nowrap;
+          float: left;
         }
-      }
-      .stdno-auto {
-        &-barcode {
-          height: 70px;
-          text-align: center;
-
-          > img {
-            display: block;
-            height: 50px;
-            width: 300px;
-            margin: 0 auto;
-          }
-          > p {
-            line-height: 20px;
-          }
+        &:last-child {
+          margin-left: 20px;
         }
       }
     }
+  }
 
-    .head-notice {
-      > h4 {
-        font-weight: normal;
-        margin-bottom: 8px;
-      }
-      &-cont {
-        line-height: 1.5;
-        font-size: 12px;
-        margin-bottom: 5px;
-
-        > span {
-          display: block;
-
-          &:first-child {
-            width: 20px;
-            white-space: nowrap;
-            float: left;
-          }
-          &:last-child {
-            margin-left: 20px;
-          }
-        }
-      }
+  &-dynamic {
+    padding: 6px 8px;
+    border: 1px solid #000;
+    border-top: none;
 
-      &-exam-number-fill {
-        span {
-          display: inline;
+    p {
+      display: inline-block;
+      vertical-align: middle;
+      line-height: 18px;
+      word-wrap: normal;
 
-          &:first-child {
-            float: none;
-          }
-          &:last-child {
-            margin: 0;
-          }
-        }
+      &:first-child {
+        margin-right: 100px;
       }
-    }
 
-    .head-dynamic {
-      padding: 0;
-      font-size: 12px;
-      border-spacing: 0;
-      border-collapse: collapse;
-
-      &-part:not(:last-child) {
-        border-bottom: 1px solid #000;
+      > span,
+      > i {
+        display: inline-block;
+        vertical-align: middle;
+        box-sizing: border-box;
       }
-      &-write {
-        padding: 5px 12px;
-        .stdinfo-item {
-          margin-bottom: 0;
-        }
-        > p {
-          line-height: 18px;
+      &:first-child {
+        i {
+          width: 28px;
+          height: 14px;
+          background-color: #000;
         }
       }
-      &-missfill {
-        display: table;
-        width: 100%;
-      }
-      &-miss {
-        padding: 10px;
-        display: table-cell;
-        vertical-align: middle;
-
-        &:nth-of-type(2) {
-          border-left: 1px solid #000;
-        }
-        span {
-          display: block;
-        }
-        .dynamic-miss-title {
-          width: 54px;
-          float: left;
-        }
-        .dynamic-miss-body {
-          margin-left: 54px;
+      &:last-child {
+        > i {
+          width: 28px;
+          height: 14px;
+          border: 1px solid #000;
+          font-size: 14px;
+          font-weight: bold;
+          margin-right: 6px;
+          line-height: 12px;
           text-align: center;
-        }
-        .head-dynamic-rect {
-          margin: auto;
-          vertical-align: middle;
-        }
-      }
-      &-fill {
-        padding: 10px;
 
-        p {
-          display: inline-block;
-          vertical-align: middle;
-          line-height: 18px;
-          word-wrap: normal;
-
-          &:first-child {
-            margin-right: 20px;
-          }
-
-          > span,
-          > i {
-            display: inline-block;
-            vertical-align: middle;
-            box-sizing: border-box;
+          &:last-child {
+            margin-right: 0;
           }
-          &:first-child {
-            i {
-              width: 28px;
-              height: 14px;
+          // wkhtmltopdf 工具无法渲染如下样式:
+          // &:nth-of-type(1) {
+          //   position: relative;
+          //   &::before {
+          //     content: "";
+          //     display: block;
+          //     position: absolute;
+          //     left: 30%;
+          //     top: 1px;
+          //     height: 5px;
+          //     width: 11px;
+          //     transform: rotate(-45deg);
+          //     border-left: 1px solid #000;
+          //     border-bottom: 1px solid #000;
+          //   }
+          // }
+          // &:nth-of-type(2) {
+          //   position: relative;
+          //   &::before {
+          //     content: "";
+          //     display: block;
+          //     position: absolute;
+          //     left: 7px;
+          //     top: 5px;
+          //     width: 11px;
+          //     transform: rotate(-45deg);
+          //     transform-origin: center center;
+          //     border-bottom: 1px solid #000;
+          //   }
+          //   &::after {
+          //     content: "";
+          //     display: block;
+          //     position: absolute;
+          //     left: 8px;
+          //     top: 5px;
+          //     width: 11px;
+          //     transform: rotate(45deg);
+          //     transform-origin: center center;
+          //     border-bottom: 1px solid #000;
+          //   }
+          // }
+
+          &:nth-of-type(3) {
+            &::before {
+              content: "";
+              display: inline-block;
+              vertical-align: top;
+              margin-left: -5px;
+              height: 100%;
+              width: 5px;
               background-color: #000;
             }
           }
-          &:last-child {
-            > i {
-              width: 28px;
-              height: 14px;
-              border: 1px solid #000;
-              font-size: 14px;
-              font-weight: bold;
-              margin-right: 6px;
-              line-height: 12px;
-              text-align: center;
-
-              &:last-child {
-                margin-right: 0;
-              }
-              // wkhtmltopdf 工具无法渲染如下样式:
-              // &:nth-of-type(1) {
-              //   position: relative;
-              //   &::before {
-              //     content: "";
-              //     display: block;
-              //     position: absolute;
-              //     left: 30%;
-              //     top: 1px;
-              //     height: 5px;
-              //     width: 11px;
-              //     transform: rotate(-45deg);
-              //     border-left: 1px solid #000;
-              //     border-bottom: 1px solid #000;
-              //   }
-              // }
-              // &:nth-of-type(2) {
-              //   position: relative;
-              //   &::before {
-              //     content: "";
-              //     display: block;
-              //     position: absolute;
-              //     left: 7px;
-              //     top: 5px;
-              //     width: 11px;
-              //     transform: rotate(-45deg);
-              //     transform-origin: center center;
-              //     border-bottom: 1px solid #000;
-              //   }
-              //   &::after {
-              //     content: "";
-              //     display: block;
-              //     position: absolute;
-              //     left: 8px;
-              //     top: 5px;
-              //     width: 11px;
-              //     transform: rotate(45deg);
-              //     transform-origin: center center;
-              //     border-bottom: 1px solid #000;
-              //   }
-              // }
-
-              &:nth-of-type(3) {
-                &::before {
-                  content: "";
-                  display: inline-block;
-                  vertical-align: top;
-                  margin-left: -5px;
-                  height: 100%;
-                  width: 5px;
-                  background-color: #000;
-                }
-              }
-              &:nth-of-type(4) {
-                &::before {
-                  content: "";
-                  display: inline-block;
-                  margin-top: 1px;
-                  width: 10px;
-                  height: 10px;
-                  border-radius: 50%;
-                  background-color: #000;
-                }
-              }
+          &:nth-of-type(4) {
+            &::before {
+              content: "";
+              display: inline-block;
+              margin-top: 1px;
+              width: 10px;
+              height: 10px;
+              border-radius: 50%;
+              background-color: #000;
             }
           }
         }
       }
-      &-rect {
-        display: inline-block;
-        width: 30px;
-        height: 14px;
-        // border-rect
-        border: 1px solid #000;
-        font-size: 12px;
-        text-align: center;
-        line-height: 1;
-        color: #000;
-        margin: 0 5px;
-        font-family: "Times New Roman", Arial, sans-serif;
-
-        > i {
-          display: inline-block;
-          transform: scale(0.67, 0.67);
-        }
-      }
-      &-aorb {
-        display: table;
-        width: 100%;
-        .dynamic-aorb-item {
-          display: table-cell;
-          vertical-align: middle;
-          text-align: center;
-          &:not(:last-child) {
-            border-right: 1px solid #333;
-          }
-        }
-        &-fill {
-          .dynamic-aorb-item:first-child {
-            border: none;
-          }
-        }
-
-        .dynamic-aorb-title {
-          width: 83px;
-        }
-        .dynamic-aorb-info {
-          width: 50px;
-          font-size: 16px;
-          position: relative;
-          overflow: hidden;
-          .dynamic-aorb-content {
-            position: absolute;
-            top: 50%;
-            left: 0;
-            width: 100%;
-            transform: translateY(-50%);
-            z-index: auto;
-          }
-        }
-        .dynamic-aorb-barcode {
-          img {
-            display: block;
-            position: relative;
-            margin: 0 auto;
-            width: 200px;
-            height: 26px;
-            padding: 7px 0;
-          }
-        }
-        .dynamic-aorb-rects {
-          padding: 16px 10px;
-        }
-      }
-    }
-  }
-  &-part {
-    border: 1px solid #333;
-    &:not(:last-child) {
-      margin-bottom: 10px;
-    }
-  }
-  &-normal {
-    .head-dynamic {
-      &-1 {
-        .head-dynamic-part {
-          height: 100%;
-        }
-      }
-    }
-  }
-  &-narrow {
-    .head-stdno {
-      height: 138px;
-      .stdno-auto {
-        position: relative;
-        top: 50%;
-        margin-top: -40px;
-      }
-    }
-  }
-
-  &-handle {
-    &.card-head-narrow {
-      .head-stdno {
-        height: 286px;
-      }
-    }
-  }
-}
-// card-head-body-auto-resize
-.card-head-body-auto-resize {
-  margin-left: -5px;
-  margin-right: -5px;
-  overflow: hidden;
-
-  &.col-item-auto-height {
-    .card-head-body-spin {
-      height: auto;
-    }
-  }
-
-  .head-dynamic-2 {
-    .head-dynamic-part {
-      height: auto;
-    }
-  }
-
-  .rect-col {
-    padding: 5px;
-    &:first-child {
-      float: left;
-      width: 289px;
-    }
-    &:last-child {
-      float: right;
-      width: 424px;
-    }
-
-    &-item {
-      border: 1px solid #333;
-      &:nth-of-type(2) {
-        margin-top: 10px;
-      }
-      &-none {
-        border: none;
-        margin: 0 !important;
-      }
     }
   }
 }

+ 8 - 44
src/modules/card/components/CardDesign.vue

@@ -1,14 +1,5 @@
 <template>
   <div class="card-design">
-    <div class="design-header">
-      <div class="design-steps">
-        <div v-for="(step, index) in steps" :key="index" class="step-item">
-          <i>{{ index + 1 }}</i>
-          <span>{{ step }}</span>
-        </div>
-      </div>
-    </div>
-
     <!-- actions -->
     <div class="design-action">
       <div class="design-logo">
@@ -62,16 +53,6 @@
         <!-- <br /><br /> -->
         <!-- <el-button @click="initCard">新建页面</el-button> -->
       </div>
-      <!-- <div class="action-part">
-          <div class="action-part-title"><h2>阅卷参数</h2></div>
-          <div class="action-part-body">
-            <el-button type="primary" @click="modifyParams"
-              >上传阅卷参数<span class="color-danger"
-                >({{ paperParams["pageSumScore"] || 0 }}分)</span
-              ></el-button
-            >
-          </div>
-        </div> -->
     </div>
 
     <div class="design-main">
@@ -170,9 +151,16 @@
                     </div>
                   </div>
                 </div>
+                <page-number
+                  type="text"
+                  :total="pages.length * 2"
+                  :current="curPageNo * 2 + columnNo + 1"
+                ></page-number>
               </div>
             </div>
           </div>
+          <!-- side edit erea -->
+          <div class="page-main-side"></div>
           <!-- outer edit area -->
           <div class="page-main-outer">
             <page-number
@@ -180,11 +168,6 @@
               :total="pages.length"
               :current="curPageNo + 1"
             ></page-number>
-            <page-number
-              type="text"
-              :total="pages.length"
-              :current="curPageNo + 1"
-            ></page-number>
           </div>
         </div>
       </div>
@@ -229,13 +212,6 @@
     <element-prop-edit ref="ElementPropEdit"></element-prop-edit>
     <!-- right-click-menu -->
     <right-click-menu @inset-topic="insetNewTopic"></right-click-menu>
-    <!-- paper-params -->
-    <paper-params
-      ref="PaperParams"
-      :pages="pages"
-      :paper-params="paperParams"
-      @confirm="paperParamsModified"
-    ></paper-params>
     <!-- topic select dialog -->
     <topic-select-dialog
       ref="TopicSelectDialog"
@@ -261,7 +237,6 @@ import PagePropEdit from "../components/PagePropEdit";
 import ElementPropEdit from "../components/ElementPropEdit";
 import RightClickMenu from "../components/RightClickMenu";
 import PageNumber from "../components/PageNumber";
-import PaperParams from "../components/PaperParams";
 import CardHeadSample from "../elements/card-head/CardHead";
 import TopicSelectDialog from "../components/TopicSelectDialog";
 
@@ -276,7 +251,6 @@ export default {
     RightClickMenu,
     CardHeadSample,
     PageNumber,
-    PaperParams,
     TopicSelectDialog,
   },
   props: {
@@ -300,7 +274,6 @@ export default {
       ELEMENT_LIST,
       TOPIC_LIST,
       topicList: [],
-      steps: ["添加标题", "基本设置", "试题配置", "预览生成"],
       columnWidth: 0,
       isSubmit: false,
       canSave: false,
@@ -338,7 +311,6 @@ export default {
       "setOpenElementEditDialog",
       "setCurDragElement",
       "setPages",
-      "setPaperParams",
       "setInsetTarget",
       "initState",
     ]),
@@ -352,9 +324,8 @@ export default {
       "initTopicsFromPages",
     ]),
     async initCard() {
-      const { cardConfig, pages, paperParams } = this.content;
+      const { cardConfig, pages } = this.content;
       this.setCardConfig(cardConfig);
-      this.setPaperParams(paperParams);
 
       if (pages && pages.length) {
         this.setPages(pages);
@@ -413,13 +384,6 @@ export default {
       this.setCurPage(pindex);
       this.setCurElement({});
     },
-    // paper-params
-    modifyParams() {
-      this.$refs.PaperParams.open();
-    },
-    paperParamsModified(paperParams) {
-      this.setPaperParams(paperParams);
-    },
     // save
     getCardData(htmlContent = "", model = "") {
       const data = {

+ 6 - 2
src/modules/card/elementModel.js

@@ -66,6 +66,10 @@ const EDITABLE_ELEMENT = [
 const EDITABLE_TOPIC = ["FILL_QUESTION", "FILL_LINE", "EXPLAIN", "COMPOSITION"];
 
 const ELEMENT_INFOS = {
+  CARD_HEAD: {
+    name: "",
+    getModel: getCardHeadModel,
+  },
   LINES: {
     name: "多横线",
     getModel: createLines,
@@ -123,8 +127,8 @@ const TOPIC_LIST = EDITABLE_TOPIC.map((type) => {
 });
 
 // 获取元件默认数据结构
-const getElementModel = (type) => {
-  return ELEMENT_INFOS[type].getModel();
+const getElementModel = (type, optionData = {}) => {
+  return ELEMENT_INFOS[type].getModel(optionData);
 };
 
 const getElementName = (type) => {

+ 36 - 159
src/modules/card/elements/card-head/CardHead.vue

@@ -1,120 +1,46 @@
 <template>
   <div :class="classes">
-    <div class="card-head-top">
-      <!-- 高度变化之后会印象内容排版,先固定高度 -->
-      <div class="card-head-title">
-        <el-input
-          v-if="!preview && !data.isSimple"
-          id="cardTitleInput"
-          v-model="cardTitle"
-          size="small"
-          placeholder="请输入题卡标题"
-          :disabled="disabledEditCardName"
-          @blur="nameChange"
-        >
-        </el-input>
-        <h1 v-else>{{ data.cardTitle }}</h1>
-      </div>
-      <div class="card-head-subtitle">
-        <div v-if="!preview && !data.isSimple">
-          <el-input
-            v-model="cardDescLineOne"
-            placeholder="请输入题卡描述信息"
-            @blur="nameChange"
-          >
-          </el-input>
-          <el-input
-            v-model="cardDescLineTwo"
-            placeholder="更多题卡描述信息"
-            @blur="nameChange"
-          >
-          </el-input>
-        </div>
-        <p v-else>{{ data.cardDesc }}</p>
-      </div>
+    <div class="card-head-title">
+      <h1>{{ data.cardTitle }}</h1>
     </div>
-
-    <template v-if="!narrowCard">
-      <div v-if="data.examNumberStyle !== 'FILL'" class="card-head-body">
-        <div class="grid-container">
-          <div class="grid-row">
-            <div class="grid-col grid-col-dash">
-              <head-stdno :data="data"></head-stdno>
-            </div>
-            <div class="grid-col">
-              <head-stdinfo :data="data"></head-stdinfo>
-            </div>
-          </div>
-          <div v-if="!data.isSimple" class="grid-row">
-            <div class="grid-col">
-              <head-notice :data="data"></head-notice>
-            </div>
-            <div class="grid-col">
-              <head-dynamic :data="data"></head-dynamic>
-            </div>
-          </div>
-        </div>
+    <div class="card-head-info">
+      <div v-for="(info, index) in fields" :key="index" class="info-item">
+        <span>{{ info }}</span>
+        <span>:</span>
+        <span></span>
       </div>
-      <div v-else class="card-head-body">
-        <card-head-body-auto-resize>
-          <head-stdinfo slot="stdinfo" :data="data"></head-stdinfo>
-          <head-notice slot="notice" :data="data"></head-notice>
-          <head-stdno slot="stdno" :data="data"></head-stdno>
-          <head-dynamic
-            v-if="!data.isSimple && hasDynamicArea"
-            slot="dynamic"
-            :data="data"
-          ></head-dynamic>
-        </card-head-body-auto-resize>
-      </div>
-    </template>
+    </div>
 
-    <template v-if="narrowCard">
-      <div v-if="data.examNumberStyle !== 'FILL'" class="card-head-body">
-        <head-stdno class="card-head-part" :data="data"></head-stdno>
-        <head-stdinfo class="card-head-part" :data="data"></head-stdinfo>
-        <head-dynamic
-          v-if="!data.isSimple && hasDynamicArea"
-          class="card-head-part"
-          :data="data"
-        ></head-dynamic>
-        <head-notice
-          v-if="!data.isSimple"
-          class="card-head-part"
-          :data="data"
-        ></head-notice>
+    <div class="card-head-notice">
+      <h4>注意事项:</h4>
+      <div
+        v-for="(cont, index) in notices"
+        :key="index"
+        class="head-notice-cont"
+      >
+        <span>{{ index + 1 }}、</span>
+        <span>{{ cont }}</span>
       </div>
-      <div v-else class="card-head-body">
-        <head-stdinfo class="card-head-part" :data="data"></head-stdinfo>
-        <head-stdno class="card-head-part" :data="data"></head-stdno>
-        <head-dynamic
-          v-if="!data.isSimple && hasDynamicArea"
-          class="card-head-part"
-          :data="data"
-        ></head-dynamic>
-        <head-notice class="card-head-part" :data="data"></head-notice>
-      </div>
-    </template>
+    </div>
+    <div class="card-head-dynamic">
+      <p><span>正确填涂:</span><i></i></p>
+      <p>
+        <span>错误填涂:</span>
+        <i>√</i>
+        <i>×</i>
+        <i></i>
+        <i></i>
+      </p>
+    </div>
   </div>
 </template>
 
 <script>
-import HeadDynamic from "./cardHeadSpin/HeadDynamic";
-import HeadNotice from "./cardHeadSpin/HeadNotice";
-import HeadStdinfo from "./cardHeadSpin/HeadStdinfo";
-import HeadStdno from "./cardHeadSpin/HeadStdno";
-import CardHeadBodyAutoResize from "./CardHeadBodyAutoResize";
-import { mapMutations, mapState } from "vuex";
-
+/**
+ * TODO:默认MODEL_ONE,后续扩展其他类型
+ */
 export default {
   name: "CardHead",
-  components: {
-    HeadStdno,
-    HeadStdinfo,
-    HeadNotice,
-    HeadDynamic,
-    CardHeadBodyAutoResize,
-  },
   props: {
     data: {
       type: Object,
@@ -122,69 +48,20 @@ export default {
         return {};
       },
     },
-    preview: {
-      type: Boolean,
-      default: false,
-    },
   },
   data() {
     return {
-      cardTitle: this.data.cardTitle,
-      cardDesc: this.data.cardDesc,
-      cardDescLineOne: "",
-      cardDescLineTwo: "",
+      fields: ["招生单位代码及名称", "考试科目代码及名称"],
     };
   },
   computed: {
-    ...mapState("card", ["cardConfig"]),
     classes() {
-      return [
-        "page-element",
-        "card-head",
-        {
-          "card-head-narrow": this.narrowCard,
-          "card-head-handle": this.data.examNumberStyle === "FILL",
-          "card-head-normal":
-            this.data.examNumberStyle !== "FILL" && !this.narrowCard,
-        },
-      ];
-    },
-    narrowCard() {
-      return (
-        (this.data.pageSize === "A3" && this.data.columnNumber > 2) ||
-        (this.data.pageSize === "A4" && this.data.columnNumber === 2)
-      );
-    },
-    hasDynamicArea() {
-      const noDynamic =
-        this.data.examNumberStyle === "FILL"
-          ? !this.data.examAbsent && !this.data.aOrB && !this.data.discipline
-          : !this.data.examAbsent &&
-            !this.data.writeSign &&
-            !this.data.aOrB &&
-            !this.data.discipline;
-
-      return !noDynamic;
+      return ["page-element", "card-head"];
     },
-    disabledEditCardName() {
-      // 客服制卡不可修改标题
-      return this.cardConfig["makeMethod"] === "CUST";
-    },
-  },
-  created() {
-    const contents = this.data.cardDesc.split("\n");
-    this.cardDescLineOne = contents[0];
-    this.cardDescLineTwo = contents[1];
-  },
-
-  methods: {
-    ...mapMutations("card", ["setCardConfig"]),
-    nameChange() {
-      this.setCardConfig({
-        cardTitle: this.cardTitle,
-        cardDesc: [this.cardDescLineOne, this.cardDescLineTwo].join("\n"),
-      });
+    notices() {
+      return this.data.attention.split("\n") || [];
     },
   },
+  methods: {},
 };
 </script>

+ 0 - 105
src/modules/card/elements/card-head/CardHeadBodyAutoResize.vue

@@ -1,105 +0,0 @@
-<template>
-  <div :class="classes">
-    <div class="rect-col">
-      <div
-        ref="stdinfoContainer"
-        class="rect-col-item"
-        :style="{ height: heights.stdinfo + 'px' }"
-      >
-        <slot name="stdinfo"></slot>
-      </div>
-      <div
-        ref="noticeContainer"
-        class="rect-col-item"
-        :style="{ height: heights.notice + 'px' }"
-      >
-        <slot name="notice"></slot>
-      </div>
-    </div>
-    <div class="rect-col">
-      <div
-        ref="stdnoContainer"
-        class="rect-col-item"
-        :style="{ height: heights.stdno + 'px' }"
-      >
-        <slot name="stdno"></slot>
-      </div>
-      <div
-        ref="dynamicContainer"
-        :class="['rect-col-item', { 'rect-col-item-none': !$slots.dynamic }]"
-        :style="{ height: heights.dynamic + 'px' }"
-      >
-        <slot name="dynamic"></slot>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script>
-export default {
-  name: "CardHeadBodyAutoResize",
-  data() {
-    return {
-      orgHeights: {
-        stdinfo: 40,
-        notice: 40,
-        stdno: 40,
-        dynamic: 40,
-      },
-      heights: {
-        stdinfo: 40,
-        notice: 40,
-        stdno: 40,
-        dynamic: 40,
-      },
-    };
-  },
-  computed: {
-    classes() {
-      return ["card-head-body-auto-resize", "col-item-auto-height"];
-    },
-  },
-  mounted() {
-    this.initStyles();
-  },
-  methods: {
-    initStyles() {
-      const containers = ["stdinfo", "notice", "stdno", "dynamic"];
-      containers.forEach((container) => {
-        const dom =
-          this.$refs[`${container}Container`] &&
-          this.$refs[`${container}Container`].firstChild;
-        this.orgHeights[container] = dom ? dom.offsetHeight : 0;
-      });
-      Object.keys(this.orgHeights).map((key) => {
-        this.heights[key] = this.orgHeights[key] + 2;
-      });
-      this.resizeRect();
-    },
-    resizeRect() {
-      let col1 = this.orgHeights.stdinfo + this.orgHeights.notice;
-      let col2 = this.orgHeights.stdno + this.orgHeights.dynamic;
-      if (this.$slots.dynamic) {
-        if (col1 > col2) {
-          this.heights.stdno = col1 - col2 + this.orgHeights.stdno + 2;
-          this.heights.dynamic = this.orgHeights.dynamic + 2;
-        } else {
-          const splitHeight = (col2 - col1) / 2;
-          this.heights.stdinfo = splitHeight + this.orgHeights.stdinfo + 2;
-          this.heights.notice = splitHeight + this.orgHeights.notice + 2;
-        }
-      } else {
-        col1 += 14;
-        col2 -= 2;
-        if (col1 > col2) {
-          this.heights.stdno = col1;
-        } else {
-          const splitHeight = (col2 - col1) / 2;
-          this.heights.stdinfo = splitHeight + this.orgHeights.stdinfo + 2;
-          this.heights.notice = splitHeight + this.orgHeights.notice + 2;
-        }
-      }
-    },
-  },
-};
-</script>

+ 0 - 48
src/modules/card/elements/card-head/CardHeadSample.vue

@@ -1,48 +0,0 @@
-<template>
-  <div class="card-head-sample">
-    <div class="page-box">
-      <!-- inner edit area -->
-      <div class="page-main-inner">
-        <div :class="['page-main', `page-main-${page.columns.length}`]">
-          <div class="page-column">
-            <div class="page-column-main">
-              <div class="page-column-body">
-                <edit-card-head
-                  id="simple-card-head"
-                  :data="cardHeadData"
-                ></edit-card-head>
-              </div>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script>
-import { mapState } from "vuex";
-import EditCardHead from "./CardHead";
-import { getCardHeadModel } from "../../elementModel";
-
-export default {
-  name: "CardHeadSample",
-  components: { EditCardHead },
-  data() {
-    return {};
-  },
-  computed: {
-    ...mapState("card", ["cardConfig", "pages"]),
-    page() {
-      return this.pages[0];
-    },
-    cardHeadData() {
-      const data = getCardHeadModel(this.cardConfig);
-      data.isSimple = true;
-      return data;
-    },
-  },
-  mounted() {},
-  methods: {},
-};
-</script>

+ 0 - 156
src/modules/card/elements/card-head/cardHeadSpin/HeadDynamic.vue

@@ -1,156 +0,0 @@
-<template>
-  <div :class="classes">
-    <!-- write -->
-    <div
-      v-if="data.examNumberStyle !== 'FILL' && data.writeSign"
-      class="head-dynamic-part head-dynamic-write"
-    >
-      <div class="stdinfo-item">
-        <span>手写签名</span>
-        <span>:</span>
-        <span></span>
-      </div>
-      <p>
-        注意:签名则表示您认可答题卡提供的信息与您本人信息相符;如签名与信息不符或者未签名,试卷作废。
-      </p>
-    </div>
-    <!-- file -->
-    <div class="head-dynamic-part head-dynamic-fill">
-      <div class="head-dynamic-content">
-        <p><span>正确填涂:</span><i></i></p>
-        <p>
-          <span>错误填涂:</span>
-          <i>√</i>
-          <i>×</i>
-          <i></i>
-          <i></i>
-        </p>
-      </div>
-    </div>
-    <!-- miss discipline -->
-    <div
-      v-if="data.examAbsent || data.discipline"
-      class="head-dynamic-part head-dynamic-missfill"
-    >
-      <div v-if="data.examAbsent" class="head-dynamic-miss">
-        <div class="head-dynamic-content">
-          <span class="dynamic-miss-title">缺考标记</span>
-          <span class="dynamic-miss-body"
-            ><i id="dynamic-miss-area" class="head-dynamic-rect"></i
-          ></span>
-        </div>
-      </div>
-      <div v-if="data.discipline" class="head-dynamic-miss">
-        <div class="head-dynamic-content">
-          <span class="dynamic-miss-title">违纪标记</span>
-          <span class="dynamic-miss-body"
-            ><i id="dynamic-miss-area" class="head-dynamic-rect"></i
-          ></span>
-        </div>
-      </div>
-    </div>
-    <!-- aorb -->
-    <div
-      v-if="data.aOrB"
-      id="head-dynamic-aorb"
-      :class="[
-        'head-dynamic-part',
-        'head-dynamic-aorb',
-        `head-dynamic-aorb-${data.paperType.toLowerCase()}`,
-      ]"
-    >
-      <div class="dynamic-aorb-item dynamic-aorb-title">
-        <p class="dynamic-aorb-content">试卷类型:</p>
-      </div>
-      <div
-        v-if="data.paperType === 'FILL'"
-        class="dynamic-aorb-item dynamic-aorb-rects"
-      >
-        <div class="dynamic-aorb-content">
-          <span class="head-dynamic-rect"><i>A</i></span>
-          <span class="head-dynamic-rect"><i>B</i></span>
-        </div>
-      </div>
-      <!-- <div
-        class="dynamic-aorb-item dynamic-aorb-info"
-        v-if="data.paperType === 'PRINT'"
-      >
-        <div class="dynamic-aorb-content">
-          <i>{{ aorbBarcodeName }}</i>
-        </div>
-      </div> -->
-      <div
-        v-if="data.paperType === 'PRINT'"
-        id="dynamic-aorb-barcode"
-        class="dynamic-aorb-item dynamic-aorb-barcode"
-      >
-        <div class="dynamic-aorb-content">
-          <img v-if="aorbBarcodeSrc" :src="aorbBarcodeSrc" />
-          <img v-else src="../../../assets/images/barcode-sample-notext.png" />
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script>
-import { calcSum } from "../../../plugins/utils";
-
-export default {
-  name: "HeadDynamic",
-  props: {
-    data: {
-      type: Object,
-      default() {
-        return {};
-      },
-    },
-  },
-  data() {
-    return {
-      aorbBarcodeSrc:
-        this.data["fieldInfos"] && this.data["fieldInfos"]["paperType"],
-      aorbBarcodeName:
-        (this.data["fieldInfos"] && this.data["fieldInfos"]["paperTypeName"]) ||
-        "A",
-    };
-  },
-  computed: {
-    classes() {
-      let partNum = 1;
-      if (this.data.examNumberStyle !== "FILL" && this.data.writeSign)
-        partNum++;
-      if (this.data.aOrB) partNum++;
-      if (this.data.examAbsent || this.data.discipline) partNum++;
-
-      return ["head-dynamic", "card-head-body-spin", `head-dynamic-${partNum}`];
-    },
-  },
-  mounted() {
-    this.initStyles();
-  },
-  methods: {
-    initStyles() {
-      const { examNumberStyle, columnNumber, pageSize } = this.data;
-      if (
-        examNumberStyle === "FILL" ||
-        (pageSize === "A3" && columnNumber !== 2) ||
-        (pageSize === "A4" && columnNumber !== 1)
-      )
-        return;
-      const parentHeight = this.$el.parentNode.offsetHeight;
-      this.$el.style.height = parentHeight + "px";
-      const childrenCount = this.$el.children.length;
-      if (childrenCount > 1) {
-        let heights = [];
-        for (let i = 0; i < childrenCount; i++) {
-          heights[i] = this.$el.children[i].offsetHeight;
-        }
-        const lastChildHeight = parentHeight - calcSum(heights.slice(0, -1));
-        this.$el.children[childrenCount - 1].style.height =
-          lastChildHeight + "px";
-      }
-    },
-  },
-};
-</script>

+ 0 - 41
src/modules/card/elements/card-head/cardHeadSpin/HeadNotice.vue

@@ -1,41 +0,0 @@
-<template>
-  <div :class="classes">
-    <h4>注意事项:</h4>
-    <div v-for="(cont, index) in notices" :key="index" class="head-notice-cont">
-      <span>{{ index + 1 }}、</span>
-      <span>{{ cont }}</span>
-    </div>
-  </div>
-</template>
-
-<script>
-export default {
-  name: "HeadNotice",
-  props: {
-    data: {
-      type: Object,
-      default() {
-        return {};
-      },
-    },
-  },
-  data() {
-    return {};
-  },
-  computed: {
-    classes() {
-      return [
-        "head-notice",
-        "card-head-body-spin",
-        {
-          "head-notice-exam-number-fill": this.data.examNumberStyle === "fill",
-        },
-      ];
-    },
-    notices() {
-      return this.data.attention.split("\n") || [];
-    },
-  },
-  methods: {},
-};
-</script>

+ 0 - 55
src/modules/card/elements/card-head/cardHeadSpin/HeadStdinfo.vue

@@ -1,55 +0,0 @@
-<template>
-  <div class="head-stdinfo card-head-body-spin">
-    <div v-for="(info, index) in fields" :key="index" class="stdinfo-item">
-      <span :style="paramStyle">{{ info.name }}</span>
-      <span>:</span>
-      <span>{{ fieldInfos[info.code] }}</span>
-    </div>
-  </div>
-</template>
-
-<script>
-export default {
-  name: "HeadStdinfo",
-  props: {
-    data: {
-      type: Object,
-      default() {
-        return {};
-      },
-    },
-  },
-  data() {
-    return {
-      fieldInfos: this.data["fieldInfos"] || {},
-      fields: [],
-      paramStyle: {},
-      lenWidths: {
-        3: 44,
-        4: 62,
-        5: 72,
-        6: 86,
-        7: 100,
-        8: 114,
-      },
-    };
-  },
-  created() {
-    this.init();
-  },
-  methods: {
-    init() {
-      this.fields = [
-        ...this.data.requiredFields,
-        ...this.data.extendFields,
-      ].filter((item) => item.enable);
-      const nameNums = this.fields.map((item) => item.name.length);
-      const maxNameLen = Math.max.apply(null, nameNums);
-      const num = maxNameLen < 3 ? 3 : maxNameLen > 8 ? 8 : maxNameLen;
-      this.paramStyle = {
-        width: this.lenWidths[num] + "px",
-      };
-    },
-  },
-};
-</script>

+ 0 - 57
src/modules/card/elements/card-head/cardHeadSpin/HeadStdno.vue

@@ -1,57 +0,0 @@
-<template>
-  <div :class="classes">
-    <div v-if="data.examNumberStyle === 'PASTE'" class="stdno-empty">
-      <p class="">粘贴条形码区</p>
-    </div>
-    <div v-if="data.examNumberStyle === 'PRINT'" class="stdno-auto">
-      <div class="stdno-auto-barcode">
-        <img v-if="examNumberBarcodeSrc" :src="examNumberBarcodeSrc" />
-        <img v-else src="../../../assets/images/barcode-sample-notext.png" />
-        <p>{{ examNumberBarcodeName || "123456789" }}</p>
-      </div>
-    </div>
-    <div v-if="data.examNumberStyle === 'FILL'" class="stdno-fill">
-      <div class="stdno-fill-head">
-        <h5>准考证号</h5>
-        <div class="stdno-fill-rect">
-          <div v-for="n in 13" :key="n" class="stdno-fill-number"></div>
-        </div>
-      </div>
-      <div class="stdno-fill-body">
-        <div v-for="n in 13" :key="n" class="stdno-fill-list">
-          <div v-for="m in 10" :key="m" class="stdno-fill-option">
-            <i>{{ m - 1 }}</i>
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script>
-export default {
-  name: "HeadStdno",
-  props: {
-    data: {
-      type: Object,
-      default() {
-        return {};
-      },
-    },
-  },
-  data() {
-    return {
-      examNumberBarcodeSrc:
-        this.data["fieldInfos"] && this.data["fieldInfos"]["examNumber"],
-      examNumberBarcodeName:
-        this.data["fieldInfos"] && this.data["fieldInfos"]["examNumberStr"],
-    };
-  },
-  computed: {
-    classes() {
-      return ["head-stdno", "card-head-body-spin"];
-    },
-  },
-  methods: {},
-};
-</script>

+ 7 - 15
src/modules/card/elements/card-head/model.js

@@ -1,4 +1,4 @@
-import { getElementId, randomCode, deepCopy } from "../../plugins/utils";
+import { getElementId, randomCode, objAssign } from "../../plugins/utils";
 
 const MODEL = {
   type: "CARD_HEAD",
@@ -6,24 +6,16 @@ const MODEL = {
   y: 0,
   w: 0,
   h: 0,
-  cardTitle: "",
-  cardDesc: "",
-  aOrB: false,
-  paperType: "PRINT", // PRINT: "印刷",FILL: "填涂"
-  examAbsent: true,
-  writeSign: true,
-  examNumberStyle: "PRINT", // PRINT:印刷条码, PASTE:粘贴条码, FILL:考号填涂
-  businessParams: [],
-  attention: [],
-  objectiveAttention: [],
-  subjectiveAttention: [],
-  columnNumber: 2,
-  isSimple: false, // 是否是简化形式
+  cardTitle: "测试题卡规则1-题卡标题",
+  attention: "测试题卡规则1-注意事项",
+  objectiveAttention: "测试题卡规则1-客观题-注意事项",
+  subjectiveAttention: "测试题卡规则1-主观题-注意事项",
+  modelType: "MODEL_ONE",
   sign: "head",
 };
 
 const getModel = (cardConfig) => {
-  const model = Object.assign({}, deepCopy(MODEL), cardConfig);
+  const model = objAssign(MODEL, cardConfig);
   model.id = getElementId();
   model.key = randomCode();
 

+ 7 - 170
src/modules/card/elements/fill-line/EditFillLine.vue

@@ -7,7 +7,7 @@
       :rules="rules"
       label-width="120px"
     >
-      <el-form-item prop="topicName" label="题目名称:">
+      <el-form-item v-if="instance.parent" prop="topicName" label="题目名称:">
         <el-input
           v-model="modalForm.topicName"
           type="textarea"
@@ -17,29 +17,6 @@
           clearable
         ></el-input>
       </el-form-item>
-      <el-form-item prop="endNumber" label="起止题号:">
-        <el-input-number
-          v-model="modalForm.startNumber"
-          style="width: 40px"
-          :min="0"
-          :max="999"
-          :step="1"
-          step-strictly
-          :controls="false"
-          @change="lineTypeChange"
-        ></el-input-number>
-        <span class="el-input-split"></span>
-        <el-input-number
-          v-model="modalForm.endNumber"
-          style="width: 40px"
-          :min="0"
-          :max="999"
-          :step="1"
-          step-strictly
-          :controls="false"
-          @change="lineTypeChange"
-        ></el-input-number>
-      </el-form-item>
       <el-form-item prop="lineSpacing" label="空位上下间距:">
         <el-input-number
           v-model.number="modalForm.lineSpacing"
@@ -51,9 +28,9 @@
           :controls="false"
         ></el-input-number>
       </el-form-item>
-      <el-form-item prop="questionNumberPerLine" label="每行空数:">
+      <el-form-item prop="fillCountPerLine" label="每行空数:">
         <el-input-number
-          v-model="modalForm.questionNumberPerLine"
+          v-model="modalForm.fillCountPerLine"
           style="width: 125px"
           :min="1"
           :max="10"
@@ -63,73 +40,6 @@
         ></el-input-number>
         <span class="el-input-tips">*指一行显示空位数量</span>
       </el-form-item>
-      <el-form-item label="题号前缀:">
-        <el-input
-          v-model.trim="modalForm.numberPre"
-          style="width: 125px"
-          :maxlength="6"
-          clearable
-        ></el-input>
-      </el-form-item>
-      <el-form-item label="空位排列方向:" required>
-        <el-radio-group v-model="modalForm.questionDirection" size="small">
-          <el-radio-button
-            v-for="(val, key) in DIRECTION_TYPE"
-            :key="key"
-            :label="key"
-            >{{ val }}</el-radio-button
-          >
-        </el-radio-group>
-      </el-form-item>
-      <el-form-item label="小题空数类型:">
-        <el-radio-group
-          v-model="modalForm.questionLineType"
-          size="small"
-          @change="lineTypeChange"
-        >
-          <el-radio-button label="norm">标准</el-radio-button>
-          <el-radio-button label="custom">自定义</el-radio-button>
-        </el-radio-group>
-      </el-form-item>
-      <el-form-item
-        v-if="modalForm.questionLineType === 'norm'"
-        prop="lineNumberPerQuestion"
-        label="每题空数:"
-      >
-        <el-input-number
-          v-model="modalForm.lineNumberPerQuestion"
-          style="width: 125px"
-          :min="1"
-          :max="15"
-          :step="1"
-          step-strictly
-          :controls="false"
-        ></el-input-number>
-        <span class="el-input-tips">*指每一小题的空位数量</span>
-      </el-form-item>
-      <el-form-item v-else prop="questionLineNums" label="各小题空数:">
-        <table class="table table-white table-narrow">
-          <tr>
-            <th>题号</th>
-            <th>空数</th>
-          </tr>
-          <tr v-for="option in questionLineNumOptions" :key="option.no">
-            <td>{{ option.no }}</td>
-            <td>
-              <el-input-number
-                v-model="option.count"
-                size="mini"
-                :min="1"
-                :max="50"
-                :step="1"
-                step-strictly
-                :controls="false"
-                style="width: 125px"
-              ></el-input-number>
-            </td>
-          </tr>
-        </table>
-      </el-form-item>
     </el-form>
   </div>
 </template>
@@ -140,16 +50,8 @@ import { DIRECTION_TYPE } from "../../enumerate";
 const initModalForm = {
   id: "",
   topicName: "",
-  startNumber: 1,
-  endNumber: 2,
-  questionsCount: 2,
-  questionNumberPerLine: 1,
-  lineNumberPerQuestion: 1,
+  fillCountPerLine: 1,
   lineSpacing: 40,
-  questionDirection: "horizontal",
-  questionLineType: "norm",
-  questionLineNums: [],
-  numberPre: "",
 };
 
 export default {
@@ -163,17 +65,6 @@ export default {
     },
   },
   data() {
-    const numberRangeValidater = (rule, value, callback) => {
-      if (!this.modalForm.startNumber || !this.modalForm.endNumber) {
-        return callback(new Error("请输入起止题号"));
-      }
-      if (this.modalForm.startNumber > this.modalForm.endNumber) {
-        callback(new Error("开始题号不能大于结束题号"));
-      } else {
-        callback();
-      }
-    };
-
     return {
       modalForm: { ...initModalForm },
       DIRECTION_TYPE,
@@ -186,18 +77,6 @@ export default {
             trigger: "change",
           },
         ],
-        endNumber: [
-          {
-            required: true,
-            message: "请输入起止题号",
-            trigger: "change",
-          },
-          {
-            type: "number",
-            validator: numberRangeValidater,
-            trigger: "change",
-          },
-        ],
         lineSpacing: [
           {
             required: true,
@@ -206,7 +85,7 @@ export default {
             trigger: "change",
           },
         ],
-        questionNumberPerLine: [
+        fillCountPerLine: [
           {
             required: true,
             type: "number",
@@ -214,14 +93,6 @@ export default {
             trigger: "change",
           },
         ],
-        lineNumberPerQuestion: [
-          {
-            required: true,
-            type: "number",
-            message: "请输入每题空数",
-            trigger: "change",
-          },
-        ],
       },
     };
   },
@@ -230,47 +101,13 @@ export default {
   },
   methods: {
     initData(val) {
-      const valInfo = val.parent || val;
-      this.modalForm = { ...valInfo };
-      this.modalForm.endNumber =
-        this.modalForm.startNumber + this.modalForm.questionsCount - 1;
-      this.questionLineNumOptions = this.modalForm.questionLineNums;
-    },
-    lineTypeChange() {
-      // check start end number
-      if (
-        !this.modalForm.startNumber ||
-        !this.modalForm.endNumber ||
-        this.modalForm.startNumber > this.modalForm.endNumber
-      )
-        return;
-
-      if (this.modalForm.questionLineType === "custom") {
-        let questionLineNumOptions = [];
-        for (
-          let i = this.modalForm.startNumber;
-          i <= this.modalForm.endNumber;
-          i++
-        ) {
-          questionLineNumOptions.push({
-            no: i,
-            count: this.modalForm.lineNumberPerQuestion,
-          });
-        }
-        this.questionLineNumOptions = questionLineNumOptions;
-      } else {
-        this.questionLineNumOptions = [];
-      }
+      this.modalForm = this.$objAssign(initModalForm, val);
     },
     async submit() {
       const valid = await this.$refs.modalFormComp.validate().catch(() => {});
       if (!valid) return;
 
-      this.modalForm.questionsCount =
-        this.modalForm.endNumber - this.modalForm.startNumber + 1;
-      this.modalForm.questionLineNums = this.questionLineNumOptions;
-      this.modalForm.topicName = this.modalForm.topicName.trim();
-      this.$emit("modified", this.modalForm);
+      this.$emit("modified", this.$objAssign(this.instance, this.modalForm));
     },
   },
 };

+ 8 - 43
src/modules/card/elements/fill-line/ElemFillLine.vue

@@ -1,53 +1,21 @@
 <template>
   <div class="elem-fill-line">
-    <div
-      v-if="data.parent && data.startNumber === data.parent.startNumber"
-      class="elem-title"
-    >
-      {{ data.parent.topicName }}
+    <div v-if="isFirst" class="elem-title">
+      {{ data.topicName }}
     </div>
-    <div v-if="data.questionDirection === 'vertical'" class="elem-body">
-      <ul
-        v-for="question in data.questionLineNums"
-        :key="question.no"
-        class="elem-fill-quesiton"
-        :style="groupStyles"
-      >
+    <div class="elem-body">
+      <ul class="elem-fill-quesiton" :style="groupStyles">
         <li class="elem-fill-no">
-          <span :style="lineNoStyles"
-            >{{ data.numberPre }}{{ question.no }}.</span
-          >
+          <span :style="lineNoStyles">{{ data.questionNo }}.</span>
         </li>
         <li
-          v-for="line in question.count"
+          v-for="line in data.fillCount"
           :key="line"
           class="elem-fill-line"
           :style="lineStyles"
         ></li>
       </ul>
     </div>
-    <div v-else class="elem-body">
-      <ul
-        v-for="line in data.questionLineNums[0].count"
-        :key="line"
-        class="elem-fill-quesiton"
-        :style="groupStyles"
-      >
-        <li v-if="line === 1" class="elem-fill-no">
-          <span :style="lineNoStyles">
-            {{ data.numberPre }}{{ data.questionLineNums[0].no }}.
-          </span>
-        </li>
-        <li
-          v-if="line !== data.questionLineNums[0].count"
-          class="elem-fill-comma"
-          :style="lineNoStyles"
-        >
-          ,
-        </li>
-        <li class="elem-fill-line" :style="lineStyles"></li>
-      </ul>
-    </div>
   </div>
 </template>
 
@@ -63,9 +31,7 @@ export default {
     },
   },
   data() {
-    return {
-      questions: [],
-    };
+    return {};
   },
   computed: {
     lineStyles() {
@@ -80,11 +46,10 @@ export default {
     },
     groupStyles() {
       return {
-        width: 100 / this.data.questionNumberPerLine + "%",
+        width: 100 / this.data.fillCountPerLine + "%",
       };
     },
   },
-  mounted() {},
   methods: {},
 };
 </script>

+ 31 - 81
src/modules/card/elements/fill-line/model.js

@@ -1,4 +1,4 @@
-import { getElementId, randomCode } from "../../plugins/utils";
+import { getElementId, randomCode, objAssign } from "../../plugins/utils";
 
 const MODEL = {
   type: "FILL_LINE",
@@ -10,94 +10,44 @@ const MODEL = {
   sign: "subjective",
   topicName: "",
   topicNo: null,
-  startNumber: 1,
-  questionsCount: 4,
-  questionNumberPerLine: 2,
-  lineNumberPerQuestion: 1,
+  fillCountPerLine: 4,
   lineSpacing: 40,
-  questionDirection: "vertical",
-  questionLineType: "norm",
-  questionLineNums: [],
-  numberPre: "",
+  questionNo: "1",
+  fillCount: 1,
   isCovered: false,
 };
 
-const getModel = () => {
-  return {
-    id: getElementId(),
-    key: randomCode(),
-    ...MODEL,
-  };
+const getModel = (presetData) => {
+  const model = objAssign(MODEL, presetData);
+  model.id = getElementId();
+  model.key = randomCode();
+  return model;
 };
 
-const getFullModel = (model) => {
-  const parent = { ...model };
-  const numPerLine = model.questionNumberPerLine;
-  let elements = [];
-
-  let questionLineNums = model.questionLineNums;
-  if (model.questionLineType === "norm") {
-    questionLineNums = [];
-    for (
-      let j = model.startNumber;
-      j < model.startNumber + model.questionsCount;
-      j++
-    ) {
-      questionLineNums.push({
-        no: j,
-        count: model.lineNumberPerQuestion,
-      });
-    }
-  }
+// questions:[{questionNo,fillCount}]
+const getFullModel = (presetData, questions) => {
+  const parent = getModel(presetData);
 
-  if (model.questionDirection === "vertical") {
-    const total = Math.ceil(model.questionsCount / numPerLine);
-    for (let i = 0; i < total; i++) {
-      const childQuestionLineNums = questionLineNums.slice(
-        i * numPerLine,
-        (i + 1) * numPerLine
-      );
-      const maxLineNumberPerQuestion = Math.max.apply(
-        null,
-        childQuestionLineNums.map((item) => item.count)
-      );
-      const questionHeight = model.lineSpacing * maxLineNumberPerQuestion;
-      let child = Object.assign({}, parent, {
-        id: getElementId(),
-        key: randomCode(),
-        h: i ? questionHeight : questionHeight + 34,
-        startNumber: model.startNumber + i * numPerLine,
-        questionsCount:
-          i === total - 1 ? model.questionsCount - numPerLine * i : numPerLine,
-        parent: parent,
-        isLast: i === total - 1,
-        questionLineNums: childQuestionLineNums,
-      });
-      child.minHeight = child.h;
-
-      elements[i] = child;
-    }
-  } else {
-    for (let i = 0; i < model.questionsCount; i++) {
-      const childQuestionLineNums = questionLineNums[i];
-      const maxLineNumberPerQuestion = Math.ceil(
-        childQuestionLineNums.count / numPerLine
-      );
-      const questionHeight = model.lineSpacing * maxLineNumberPerQuestion;
-      let child = Object.assign({}, parent, {
-        id: getElementId(),
-        h: i ? questionHeight : questionHeight + 34,
-        startNumber: model.startNumber + i,
-        questionsCount: 1,
-        parent: parent,
-        isLast: i === model.questionsCount - 1,
-        questionLineNums: [childQuestionLineNums],
-      });
-      child.minHeight = child.h;
+  const numPerLine = parent.fillCountPerLine;
+  let elements = [];
 
-      elements[i] = child;
-    }
-  }
+  questions.forEach((question, index) => {
+    const maxLineNumberPerQuestion = Math.ceil(question.fillCount / numPerLine);
+
+    const questionHeight = parent.lineSpacing * maxLineNumberPerQuestion;
+    let child = Object.assign({}, parent, {
+      id: getElementId(),
+      h: index ? questionHeight : questionHeight + 34,
+      parent: parent,
+      isLast: false,
+      isFirst: !index,
+      questionNo: question.questionNo,
+      fillCount: question.fillCount,
+    });
+    child.minHeight = child.h;
+    elements[index] = child;
+  });
+  elements[elements.length - 1].isLast = true;
 
   return elements;
 };

+ 7 - 102
src/modules/card/elements/fill-question/EditFillQuestion.vue

@@ -17,65 +17,13 @@
           clearable
         ></el-input>
       </el-form-item>
-      <el-form-item prop="endNumber" label="起止题号:">
-        <el-input-number
-          v-model="modalForm.startNumber"
-          style="width: 40px"
-          :min="0"
-          :max="999"
-          :step="1"
-          step-strictly
-          :controls="false"
-        ></el-input-number>
-        <span class="el-input-split"></span>
-        <el-input-number
-          v-model="modalForm.endNumber"
-          style="width: 40px"
-          :min="modalForm.startNumber"
-          :max="999"
-          :step="1"
-          step-strictly
-          :controls="false"
-        ></el-input-number>
+      <el-form-item label="题型:">
+        <el-tag v-if="modalForm.isMultiply" effect="dark">多选</el-tag>
+        <el-tag v-else-if="modalForm.isBoolean" effect="dark">判断</el-tag>
+        <el-tag v-else effect="dark">单选</el-tag>
       </el-form-item>
-      <el-form-item prop="optionCount" label="选项个数:">
-        <el-input-number
-          v-model="modalForm.optionCount"
-          style="width: 125px"
-          :min="2"
-          :max="22"
-          :step="1"
-          step-strictly
-          :controls="false"
-          :disabled="modalForm.isBoolean"
-        ></el-input-number>
-      </el-form-item>
-      <el-form-item label="小题排列方向:" required>
-        <el-radio-group v-model="modalForm.questionDirection" size="small">
-          <el-radio-button
-            v-for="(val, key) in DIRECTION_TYPE"
-            :key="key"
-            :label="key"
-            >{{ val }}</el-radio-button
-          >
-        </el-radio-group>
-      </el-form-item>
-      <el-form-item>
-        <el-checkbox
-          v-model="modalForm.isMultiply"
-          :disabled="modalForm.isBoolean"
-          >多选</el-checkbox
-        >
-      </el-form-item>
-      <el-form-item>
-        <el-checkbox
-          v-model="modalForm.isBoolean"
-          :disabled="modalForm.isMultiply"
-          @change="selectTypeChange"
-          >判断题</el-checkbox
-        >
+      <el-form-item v-if="modalForm.isBoolean">
         <el-select
-          v-if="modalForm.isBoolean"
           v-model="modalForm.booleanType"
           style="margin-left: 20px; width: 125px"
           placeholder="请选择"
@@ -88,7 +36,7 @@
             :value="item"
           ></el-option>
         </el-select>
-        <span v-if="modalForm.isBoolean">(备选是否配置)</span>
+        <span>(备选是否配置)</span>
       </el-form-item>
       <el-form-item
         v-if="modalForm.isBoolean"
@@ -115,16 +63,11 @@
 </template>
 
 <script>
-import { BOOLEAN_TYPE, DIRECTION_TYPE } from "../../enumerate";
+import { BOOLEAN_TYPE } from "../../enumerate";
 
 const initModalForm = {
   id: "",
   topicName: "",
-  startNumber: 1,
-  endNumber: 5,
-  questionsCount: 10,
-  optionCount: 5,
-  questionDirection: "horizontal",
   isBoolean: false,
   booleanType: BOOLEAN_TYPE[0],
   isMultiply: false,
@@ -141,17 +84,6 @@ export default {
     },
   },
   data() {
-    const numberRangeValidater = (rule, value, callback) => {
-      if (!this.modalForm.startNumber || !this.modalForm.endNumber) {
-        return callback(new Error("请输入起止题号"));
-      }
-      if (this.modalForm.startNumber > this.modalForm.endNumber) {
-        callback(new Error("开始题号不能大于结束题号"));
-      } else {
-        callback();
-      }
-    };
-
     const booleanTypeValidater = (rule, value, callback) => {
       if (this.modalForm.isBoolean) {
         if (
@@ -172,7 +104,6 @@ export default {
     return {
       modalForm: { ...initModalForm },
       BOOLEAN_TYPE,
-      DIRECTION_TYPE,
       booleanTypes: {
         yes: "",
         no: "",
@@ -185,26 +116,6 @@ export default {
             trigger: "change",
           },
         ],
-        endNumber: [
-          {
-            required: true,
-            message: "请输入起止题号",
-            trigger: "change",
-          },
-          {
-            type: "number",
-            validator: numberRangeValidater,
-            trigger: "change",
-          },
-        ],
-        optionCount: [
-          {
-            required: true,
-            type: "number",
-            message: "请输入选项个数",
-            trigger: "change",
-          },
-        ],
         booleanType: [
           {
             required: true,
@@ -222,14 +133,10 @@ export default {
     initData(val) {
       const valInfo = val.parent || val;
       this.modalForm = Object.assign({}, this.initModalForm, valInfo);
-      this.modalForm.endNumber =
-        this.modalForm.startNumber + this.modalForm.questionsCount - 1;
       this.booleanTypeChange();
     },
     selectTypeChange(val) {
       if (val) {
-        this.modalForm.optionCount = 2;
-        this.modalForm.isMultiply = false;
         this.modalForm.booleanType = BOOLEAN_TYPE[0];
         this.booleanTypeChange();
       }
@@ -243,8 +150,6 @@ export default {
       const valid = await this.$refs.modalFormComp.validate().catch(() => {});
       if (!valid) return;
 
-      this.modalForm.questionsCount =
-        this.modalForm.endNumber - this.modalForm.startNumber + 1;
       this.modalForm.booleanType = [
         this.booleanTypes.yes,
         this.booleanTypes.no,

+ 14 - 72
src/modules/card/elements/fill-question/ElemFillQuestion.vue

@@ -5,10 +5,12 @@
     </div>
     <div class="elem-body">
       <ul
-        v-for="(group, gindex) in questions"
+        v-for="(group, gindex) in data.questionGroup"
         :key="gindex"
         class="group-item"
-        :style="gindex !== questions.length - 1 ? groupGapStyles : null"
+        :style="
+          gindex !== data.questionGroup.length - 1 ? groupGapStyles : null
+        "
       >
         <li
           v-for="(question, qindex) in group"
@@ -16,8 +18,11 @@
           class="question-item"
           :style="getQuestionGapStyles(group, qindex)"
         >
+          <span class="option-item" :style="optionGapStyles">
+            <i>{{ question.questionNo }}</i>
+          </span>
           <span
-            v-for="(option, oindex) in question"
+            v-for="(option, oindex) in question.choiceList"
             :key="oindex"
             class="option-item"
             :style="optionGapStyles"
@@ -42,27 +47,18 @@ export default {
     },
   },
   data() {
-    return {
-      questions: [],
-    };
+    return {};
   },
   computed: {
-    isFirstSpin() {
-      return (
-        this.data.parent &&
-        this.data.startNumber === this.data.parent.startNumber
-      );
-    },
     classes() {
       return [
         "elem-fill-question",
-        `elem-fill-question-${this.data.optionDirection}`,
         {
           "elem-fill-question-simple":
             !this.data.isMultiply && !this.data.isBoolean,
           "elem-fill-question-multiply": this.data.isMultiply,
           "elem-fill-question-boolean": this.data.isBoolean,
-          "elem-fill-question-first": this.isFirstSpin,
+          "elem-fill-question-first": this.data.isFirst,
         },
       ];
     },
@@ -71,71 +67,17 @@ export default {
         marginRight: this.data.groupGap + "px",
       };
     },
-    // questionGapStyles() {
-    //   return this.data.optionDirection === "vertical"
-    //     ? { marginRight: this.data.questionGap + "px" }
-    //     : { marginBottom: this.data.questionGap + "px" };
-    // },
     optionGapStyles() {
-      const styles =
-        this.data.optionDirection === "vertical"
-          ? { marginBottom: this.data.optionGap + "px" }
-          : { marginRight: this.data.optionGap + "px" };
-      // styles.fontSize = this.data.fontSize;
+      const styles = { marginRight: this.data.optionGap + "px" };
       return styles;
     },
   },
-  watch: {
-    data: {
-      immediate: true,
-      handler(val) {
-        this.parseQuestion(val);
-      },
-    },
-  },
   methods: {
-    parseQuestion(data) {
-      let questionNo = data.startNumber;
-      let questions = [];
-      const choiceList = this.getChoiceList(data);
-      if (data.questionDirection === "vertical") {
-        const groupNum = Math.ceil(
-          data.questionsCount / data.questionCountPerGroup
-        );
-        for (let i = 0; i < groupNum; i++) {
-          questions[i] = [];
-          const questionCountPerGroup =
-            i === groupNum - 1
-              ? data.questionsCount - data.questionCountPerGroup * i
-              : data.questionCountPerGroup;
-          for (let j = 0; j < questionCountPerGroup; j++) {
-            questions[i][j] = [questionNo++, ...choiceList];
-          }
-        }
-      } else {
-        for (let i = 0; i < data.questionsCount; i++) {
-          const groupIndex = i % data.groupPerLine;
-          if (!questions[groupIndex]) questions[groupIndex] = [];
-          questions[groupIndex].push([questionNo++, ...choiceList]);
-        }
-      }
-      this.questions = questions;
-    },
-    getChoiceList(data) {
-      if (data.isBoolean) {
-        return data.booleanType.split(",");
-      } else {
-        return "abcdefghijklmnopqrstuv"
-          .toUpperCase()
-          .slice(0, data.optionCount)
-          .split("");
-      }
-    },
     getQuestionGapStyles(group, qindex) {
       const size = group.length - 1 === qindex ? 0 : this.data.questionGap;
-      return this.data.optionDirection === "vertical"
-        ? { marginRight: size + "px" }
-        : { marginBottom: size + "px" };
+      return {
+        marginBottom: size + "px",
+      };
     },
   },
 };

+ 82 - 49
src/modules/card/elements/fill-question/model.js

@@ -1,5 +1,5 @@
-import { getElementId, randomCode } from "../../plugins/utils";
-import { BOOLEAN_TYPE } from "../../enumerate";
+import { getElementId, randomCode, objAssign } from "../../plugins/utils";
+import { BOOLEAN_TYPE, ALPHABET } from "../../enumerate";
 
 const MODEL = {
   type: "FILL_QUESTION",
@@ -11,82 +11,115 @@ const MODEL = {
   sign: "objective",
   topicName: "",
   topicNo: null,
-  startNumber: 1,
-  questionsCount: 10,
-  optionCount: 4,
   questionCountPerGroup: 5,
-  groupPerLine: 4, // 小题纵向排列时,表示每行组数。小题横向排列时,表示每行小题数。
-  optionDirection: "horizontal",
-  questionDirection: "vertical",
   questionGap: 8,
   groupGap: 30,
   optionGap: 12,
+  questions: [], // question: questionNo,optionCount
+  questionGroup: [],
   isBoolean: false, // 是否是判断题
   booleanType: BOOLEAN_TYPE[0],
   isMultiply: false, // 是否是多选题
   isCovered: false,
-  fontSize: "14px",
 };
 
-const getModel = () => {
-  return {
-    id: getElementId(),
-    key: randomCode(),
-    ...MODEL,
-  };
+function getChoiceList(data) {
+  if (data.isBoolean) {
+    return data.booleanType.split(",");
+  } else {
+    return ALPHABET.slice(0, data.optionCount).split("");
+  }
+}
+
+const getModel = (presetData) => {
+  const model = objAssign(MODEL, presetData);
+  model.id = getElementId();
+  model.key = randomCode();
+  return model;
 };
 
 const getFullModel = (model, { pageSize, columnNumber }) => {
   const parent = { ...model };
   // 不同栏数,不同选项个数,每一行对应的组数
-  // 以一行4题,每题4选项为标准展示效果
-  const numberPerChildren = {
+  // 以一行4题,每题4选项为标准展示效果:选项序号占一个选项位置,即一行最多展示20个选项位置。
+  // 可扩展其他分栏的最佳展示效果
+  const lineMaxOptionCountConfig = {
     A3: {
-      2: [0, 0, 6, 5, 4, 3, 3, 2, 2, 2, 2, 1, 1],
-      3: [0, 0, 4, 3, 2, 2, 2, 1],
-      4: [0, 0, 3, 2, 2, 1],
+      2: 20,
     },
     A4: {
-      1: [0, 0, 6, 5, 4, 3, 3, 2, 2, 2, 2, 1, 1],
-      2: [0, 0, 3, 2, 2, 1],
+      1: 20,
     },
   };
-  // 以一行4题,每题5选项为标准展示效果
-  // const numberPerChildren = {
-  //   2: [0, 0, 7, 5, 4, 4, 3, 3, 2, 2, 2, 2, 1],
-  //   3: [0, 0, 4, 3, 2, 2, 2, 2, 1],
-  //   4: [0, 0, 3, 2, 2, 2, 1]
-  // };
-  const numList = numberPerChildren[pageSize][columnNumber];
-  const groupPerLine =
-    model.optionCount > numList.length
-      ? numList.pop()
-      : numList[model.optionCount];
-  const numPerLine = groupPerLine * model.questionCountPerGroup;
-  const total = Math.ceil(model.questionsCount / numPerLine);
+  const lineMaxOptionCount = lineMaxOptionCountConfig[pageSize][columnNumber];
+  let questionList = model.questions.map((item) => {
+    const choiceList = getChoiceList({
+      ...item,
+      isBoolean: model.isBoolean,
+      booleanType: model.booleanType,
+    });
+    return {
+      ...item,
+      choiceList,
+    };
+  });
+
+  const getNextGroupQuestions = () => {
+    let isEnough = false;
+    let groupOptionCount = 0;
+    let groups = [];
+    do {
+      const curGroup = questionList.splice(0, model.questionCountPerGroup);
+      if (curGroup.length) {
+        const curGroupMaxOptionCount = Math.max.apply(
+          null,
+          curGroup.map((item) => item.optionCount)
+        );
+        if (
+          curGroupMaxOptionCount + 1 + groupOptionCount <=
+          lineMaxOptionCount
+        ) {
+          groupOptionCount += curGroupMaxOptionCount + 1;
+          groups.push(curGroup);
+        } else {
+          isEnough = true;
+        }
+      } else {
+        isEnough = true;
+      }
+    } while (!isEnough);
+
+    return groups;
+  };
+
   let elements = [];
-  for (let i = 0; i < total; i++) {
+
+  while (questionList.length) {
+    const questionGroup = getNextGroupQuestions();
     let child = Object.assign({}, parent, {
       id: getElementId(),
       key: randomCode(),
-      groupPerLine,
-      startNumber: model.startNumber + i * numPerLine,
-      questionsCount:
-        i === total - 1 ? model.questionsCount - numPerLine * i : numPerLine,
       parent,
-      isLast: i === total - 1,
+      isFirst: !elements.length,
+      isLast: false,
+      questionGroup,
     });
-    const optionCount =
-      model.questionDirection === "vertical"
-        ? Math.min(child.questionsCount, model.questionCountPerGroup)
-        : Math.ceil(child.questionsCount / groupPerLine);
+    const maxOptionCountPerGroup =
+      questionGroup.length > 1
+        ? model.questionCountPerGroup
+        : questionGroup[0].length;
     const optionsHeight =
-      14 * optionCount + (optionCount - 1) * model.questionGap + 36;
-    child.h = i ? optionsHeight : optionsHeight + 34;
-    child.minHeight = child.h;
+      14 * maxOptionCountPerGroup +
+      (maxOptionCountPerGroup - 1) * model.questionGap +
+      36;
 
-    elements[i] = child;
+    child.h = elements.length ? optionsHeight : optionsHeight + 34;
+    child.minHeight = child.h;
+    elements.push(child);
   }
+
+  elements[elements.length - 1].isLast = true;
+
   return elements;
 };
 

+ 2 - 0
src/modules/card/enumerate.js

@@ -13,6 +13,8 @@ export const PAPER_TYPE = {
 
 export const BOOLEAN_TYPE = ["√,×", "是,否", "对,错"];
 
+export const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
 export const DIRECTION_TYPE = {
   horizontal: "横向",
   vertical: "纵向",

+ 48 - 0
src/modules/card/pageModel.js

@@ -0,0 +1,48 @@
+import { getElementModel } from "./elementModel";
+
+const templateDict = {
+  GRADUATE: {
+    elements: [
+      {
+        type: "CARD_HEAD",
+        modelType: "MODEL_ONE",
+      },
+    ],
+    side: [
+      {
+        type: "GUTTER",
+      },
+      {
+        type: "FILL_FIELD",
+        fields: [
+          {
+            name: "考生编号",
+            code: "",
+          },
+          {
+            name: "姓名",
+            code: "",
+          },
+          {
+            name: "考生确认签名",
+            code: "",
+          },
+        ],
+      },
+    ],
+  },
+};
+
+export const getPageInitElements = (templateInfo) => {
+  const temps = templateDict[templateInfo.templateType];
+
+  let pageInit = {};
+  Object.keys(temps).forEach((k) => {
+    pageInit[k] = temps[k].map((item) => {
+      const model = getElementModel(item.type, templateInfo);
+      return Object.assign(model, item);
+    });
+  });
+
+  return pageInit;
+};

+ 13 - 17
src/modules/card/router/index.js

@@ -1,9 +1,4 @@
-import Vue from "vue";
-import VueRouter from "vue-router";
-
-Vue.use(VueRouter);
-
-const routes = [
+export const menuRoutes = [
   {
     path: "/card/card-manage",
     name: "CardManage",
@@ -22,6 +17,18 @@ const routes = [
     component: () =>
       import(/* webpackChunkName: "card" */ "../views/CardHeadEdit.vue"),
   },
+  // {
+  //   path: "/about",
+  //   name: "About",
+  //   // route level code-splitting
+  //   // this generates a separate chunk (about.[hash].js) for this route
+  //   // which is lazy-loaded when the route is visited.
+  //   component: () =>
+  //     import(/* webpackChunkName: "about" */ "../views/About.vue")
+  // }
+];
+
+export const editRoutes = [
   {
     path: "/card/edit/:cardId?",
     name: "CardEdit",
@@ -35,15 +42,4 @@ const routes = [
     component: () =>
       import(/* webpackChunkName: "card" */ "../views/CardPreview.vue"),
   },
-  // {
-  //   path: "/about",
-  //   name: "About",
-  //   // route level code-splitting
-  //   // this generates a separate chunk (about.[hash].js) for this route
-  //   // which is lazy-loaded when the route is visited.
-  //   component: () =>
-  //     import(/* webpackChunkName: "about" */ "../views/About.vue")
-  // }
 ];
-
-export default routes;

+ 11 - 54
src/modules/card/views/CardEdit.vue

@@ -28,14 +28,8 @@ export default {
   },
   data() {
     return {
-      cardId: this.$route.params.cardId || this.$ls.get("cardId"),
-      prepareTcPCard: this.$ls.get("prepareTcPCard", {
-        examTaskId: "",
-        courseCode: "",
-        courseName: "",
-        makeMethod: "SELF",
-        cardRuleId: "",
-      }),
+      cardId: this.$route.params.cardId,
+      prepareTcPCard: { paperId: "" },
       cardContent: {},
       cardPreviewUrl: "",
       canSave: false,
@@ -48,16 +42,12 @@ export default {
     },
   },
   mounted() {
-    if (!this.prepareTcPCard.examTaskId && !this.isEdit) {
-      this.$message.error("找不到命题任务,请退出题卡制作!");
-      return;
-    }
     this.initCard();
     this.registWindowSubmit();
   },
   beforeDestroy() {
-    this.$ls.remove("cardId");
-    this.$ls.remove("prepareTcPCard");
+    // this.$ls.remove("cardId");
+    // this.$ls.remove("prepareTcPCard");
     delete window.submitCardTemp;
   },
   methods: {
@@ -70,22 +60,10 @@ export default {
         this.cardContent = {
           pages: [],
           cardConfig,
-          paperParams: {},
         };
       }
       this.dataReady = true;
     },
-    getCardTitle(titleRule) {
-      const fieldMap = {
-        courseCode: this.prepareTcPCard.courseCode,
-        courseName: this.prepareTcPCard.courseName,
-        schoolName: this.prepareTcPCard.schoolName,
-      };
-      Object.entries(fieldMap).forEach(([key, val]) => {
-        titleRule = titleRule.replace("${" + key + "}", val);
-      });
-      return titleRule;
-    },
     async getCardTempDetail() {
       const detData = await cardDetail(this.cardId);
       // 可能存在题卡内容没有记录的情况
@@ -93,50 +71,31 @@ export default {
         this.cardContent = JSON.parse(detData.content);
       } else {
         let cardConfig = await this.getCardConfig();
-        // 没有题卡内容时,直接创建新的内容
-        if (detData.makeMethod === "CUST") {
-          cardConfig.cardTitle = detData.title;
-        }
         this.cardContent = {
           pages: [],
           cardConfig,
-          paperParams: {},
         };
       }
     },
     async getCardConfig() {
-      const data = await cardConfigInfos(this.prepareTcPCard.cardRuleId);
+      const data = await cardConfigInfos();
       if (!data) {
-        this.$message.error("找不到题卡规则!");
+        this.$message.error("找不到题卡版头!");
         return;
       }
-      let config = {
+      const config = {
         ...data,
         ...{
           pageSize: "A3",
           columnNumber: 2,
           columnGap: 20,
           showForbidArea: true,
-          cardDesc: "",
-          makeMethod: this.prepareTcPCard.makeMethod,
+          showScorePan: false,
         },
       };
-      config.aOrB = true; // 默认开启A/B卷型
-      config.requiredFields = JSON.parse(config.requiredFields);
-      config.extendFields = JSON.parse(config.extendFields);
-      config.cardTitle = this.getCardTitle(config.titleRule);
       return config;
     },
     // 操作
-    getRequestConfig() {
-      return this.prepareTcPCard.makeMethod === "CUST"
-        ? {
-            headers: {
-              schoolId: this.prepareTcPCard.schoolId,
-            },
-          }
-        : {};
-    },
     getCardData(htmlContent = "", model = "") {
       let data = this.$refs.CardDesign.getCardData(htmlContent, model);
       data = {
@@ -162,15 +121,13 @@ export default {
     // save
     async toSave(datas) {
       datas.status = "STAGE";
-      const result = await saveCard(datas, this.getRequestConfig()).catch(
-        () => {}
-      );
+      const result = await saveCard(datas).catch(() => {});
 
       this.$refs.CardDesign.unloading();
       if (!result) return;
 
       this.cardId = result;
-      this.$ls.set("cardId", this.cardId);
+      // this.$ls.set("cardId", this.cardId);
       this.$message.success("保存成功!");
     },
     async toSubmit(cardData) {
@@ -200,7 +157,7 @@ export default {
         window.cardData = null;
         if (result) {
           this.cardId = result;
-          this.$ls.set("cardId", this.cardId);
+          // this.$ls.set("cardId", this.cardId);
           this.canSave = false;
           this.$message.success("提交成功!");
           this.goback();

+ 2 - 2
src/modules/card/views/CardHeadEdit.vue

@@ -87,7 +87,7 @@ const initModalForm = {
   attention: "",
   objectiveAttention: "",
   subjectiveAttention: "",
-  templateId: null,
+  templateType: null,
 };
 
 export default {
@@ -179,7 +179,7 @@ export default {
             trigger: "change",
           },
         ],
-        templateId: [
+        templateType: [
           {
             required: true,
             message: "请选择模板",

+ 3 - 2
src/modules/questions/routes/routes.js

@@ -33,7 +33,7 @@ import ExamPaperPendingTrial from "../views/ExamPaperPendingTrial.vue";
 import CheckDuplicateList from "../views/CheckDuplicateList.vue";
 import CheckDuplicateInfo from "../views/CheckDuplicateInfo.vue";
 
-import CardRoutes from "../../card/router";
+import { menuRoutes, editRoutes } from "../../card/router";
 
 export default [
   {
@@ -169,7 +169,7 @@ export default [
         path: "paper_storage/:isClear",
         component: PaperStorage,
       },
-      ...CardRoutes,
+      ...menuRoutes,
     ],
   },
   {
@@ -192,4 +192,5 @@ export default [
     path: "/view_paper/:id", //试卷查看
     component: ViewPaper,
   },
+  ...editRoutes,
 ];

+ 2 - 0
src/store/index.js

@@ -3,6 +3,7 @@ import Vuex from "vuex";
 import user from "../modules/portal/store/user";
 import currentPaths from "../modules/portal/store/currentPaths";
 import menuList from "../modules/portal/store/menuList";
+import { card } from "../modules/card/store";
 
 Vue.use(Vuex);
 
@@ -14,5 +15,6 @@ export default new Vuex.Store({
     user,
     currentPaths,
     menuList,
+    card,
   },
 });