Răsfoiți Sursa

页面布局调整

zhangjie 3 ani în urmă
părinte
comite
c855466ca3

+ 1 - 1
src/modules/card/api.js

@@ -68,7 +68,7 @@ export const cardConfigInfos = () => {
       "测试题卡规则1-注意事项\n测试题卡规则1-注意事项\n测试题卡规则1-注意事项",
     objectiveAttention: "测试题卡规则1-客观题-注意事项",
     subjectiveAttention: "测试题卡规则1-主观题-注意事项",
-    templateType: "MODEL_ONE",
+    templateType: "GRADUATE",
   });
 };
 export const cardDetail = () => {

+ 45 - 4
src/modules/card/assets/styles/card-preview.scss

@@ -89,10 +89,10 @@
       &-2 {
         .page-column-forbid-area {
           &::before {
-            transform: rotate(54.216deg);
+            transform: rotate(54.98deg);
           }
           &::after {
-            transform: rotate(-54.216deg);
+            transform: rotate(-54.98deg);
           }
         }
       }
@@ -158,8 +158,20 @@
   bottom: 86px;
   width: 120px;
   left: 40px;
-  z-index: auto;
-  background-color: #e0e0e0;
+  z-index: 10;
+  // background-color: #e0e0e0;
+
+  .free-topic-preview {
+    position: absolute;
+  }
+  .element-item-gutter {
+    position: absolute;
+    height: 100% !important;
+  }
+  .element-item-fill-field {
+    transform: rotate(-90deg);
+    transform-origin: 0 0;
+  }
 }
 // 分栏间距,默认20px
 // page-main-inner
@@ -1179,6 +1191,35 @@
   }
 }
 
+// elem-gutter
+.elem-gutter {
+  position: relative;
+  width: 30px;
+  height: 100%;
+
+  &-left {
+    border-right: 1px solid #000;
+  }
+  &-right {
+    border-left: 1px solid #000;
+  }
+
+  &-content {
+    position: absolute;
+    width: 100%;
+    top: 50%;
+    left: 0;
+    transform: translateY(-50%);
+
+    > span {
+      display: block;
+      padding: 5px;
+      line-height: 20px;
+      transform: rotate(-90deg);
+    }
+  }
+}
+
 // card-free-preview
 .card-free-preview {
   &:not(.card-print) {

+ 17 - 18
src/modules/card/components/CardDesign.vue

@@ -160,7 +160,14 @@
             </div>
           </div>
           <!-- side edit erea -->
-          <div class="page-main-side"></div>
+          <div class="page-main-side">
+            <free-element-preview
+              v-for="element in curPage.sides"
+              :key="element.id"
+              class="page-side-element"
+              :data="element"
+            ></free-element-preview>
+          </div>
           <!-- outer edit area -->
           <div class="page-main-outer">
             <page-number
@@ -187,12 +194,6 @@
             >
               <div id="topic-column" class="page-column-main">
                 <div class="page-column-body">
-                  <!-- card-head-sample -->
-                  <card-head-sample
-                    v-if="topics.length && cardHeadSampleData"
-                    id="simple-card-head"
-                    :data="cardHeadSampleData"
-                  ></card-head-sample>
                   <!-- topic element -->
                   <topic-element-preview
                     v-for="element in topics"
@@ -233,12 +234,13 @@ import { CARD_VERSION } from "../enumerate";
 // import CardConfigPropEdit from "../components/CardConfigPropEdit";
 import TopicElementEdit from "../components/TopicElementEdit";
 import TopicElementPreview from "../components/TopicElementPreview";
+import FreeElementPreview from "../components/FreeElementPreview";
 import PagePropEdit from "../components/PagePropEdit";
 import ElementPropEdit from "../components/ElementPropEdit";
 import RightClickMenu from "../components/RightClickMenu";
 import PageNumber from "../components/PageNumber";
-import CardHeadSample from "../elements/card-head/CardHead";
 import TopicSelectDialog from "../components/TopicSelectDialog";
+import { getPageInitElements } from "../pageModel";
 
 export default {
   name: "CardDesign",
@@ -246,10 +248,10 @@ export default {
     // CardConfigPropEdit,
     TopicElementEdit,
     TopicElementPreview,
+    FreeElementPreview,
     PagePropEdit,
     ElementPropEdit,
     RightClickMenu,
-    CardHeadSample,
     PageNumber,
     TopicSelectDialog,
   },
@@ -282,6 +284,7 @@ export default {
   computed: {
     ...mapState("card", [
       "cardConfig",
+      "pageDefaultElems",
       "topics",
       "pages",
       "paperParams",
@@ -289,12 +292,6 @@ export default {
       "curPage",
       "curPageNo",
     ]),
-    cardHeadSampleData() {
-      if (!this.cardConfig["pageSize"]) return;
-      const data = getCardHeadModel(this.cardConfig);
-      data.isSimple = true;
-      return data;
-    },
   },
   mounted() {
     this.initCard();
@@ -311,7 +308,9 @@ export default {
       "setOpenElementEditDialog",
       "setCurDragElement",
       "setPages",
+      "setTopics",
       "setInsetTarget",
+      "setPageDefaultElems",
       "initState",
     ]),
     ...mapActions("card", [
@@ -326,6 +325,8 @@ export default {
     async initCard() {
       const { cardConfig, pages } = this.content;
       this.setCardConfig(cardConfig);
+      const pageDefaultElems = getPageInitElements(cardConfig);
+      this.setPageDefaultElems(pageDefaultElems);
 
       if (pages && pages.length) {
         this.setPages(pages);
@@ -338,9 +339,7 @@ export default {
       this.addWatch();
     },
     initPageData() {
-      this.modifyCardHead({
-        ...getCardHeadModel(this.cardConfig),
-      });
+      this.setTopics(this.pageDefaultElems.elements || []);
       this.$nextTick(() => {
         this.rebuildPages();
         this.setCurPage(0);

+ 68 - 0
src/modules/card/components/FreeElementPreview.vue

@@ -0,0 +1,68 @@
+<template>
+  <div
+    :id="`preview-${data.id}`"
+    :class="classes"
+    :data-type="data.type"
+    :style="styles"
+  >
+    <component :is="compName" :data="data" preview></component>
+  </div>
+</template>
+
+<script>
+import PreviewText from "../elements/text/ElemText";
+import PreviewImage from "../elements/image/ElemImage";
+import PreviewLine from "../elements/line/ElemLine";
+import PreviewLines from "../elements/lines/ElemLines";
+import PreviewGrids from "../elements/grids/ElemGrids";
+import PreviewPane from "../elements/pane/ElemPane";
+import PreviewFillField from "../elements/fill-field/ElemFillField";
+import PreviewGutter from "../elements/gutter/ElemGutter";
+
+export default {
+  name: "FreeElementPreview",
+  components: {
+    PreviewFillField,
+    PreviewText,
+    PreviewImage,
+    PreviewLine,
+    PreviewLines,
+    PreviewGrids,
+    PreviewPane,
+    PreviewGutter,
+  },
+  props: {
+    data: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {};
+  },
+  computed: {
+    elementName() {
+      const name = this.data.type.toLowerCase().replace("_", "-");
+      return name.includes("line-") ? "line" : name;
+    },
+    compName() {
+      return `preview-${this.elementName}`;
+    },
+    classes() {
+      return ["free-topic-preview", `element-item-${this.elementName}`];
+    },
+    styles() {
+      return {
+        left: this.data.x + "px",
+        top: this.data.y + "px",
+        width: this.data.w + "px",
+        height: this.data.h + "px",
+        zIndex: this.data.zindex,
+      };
+    },
+  },
+  methods: {},
+};
+</script>

+ 11 - 1
src/modules/card/elementModel.js

@@ -8,6 +8,8 @@ import { getModel as createLine } from "./elements/line/model";
 import { getModel as createText } from "./elements/text/model";
 import { getModel as createImage } from "./elements/image/model";
 import { getModel as createGrids } from "./elements/grids/model";
+import { getModel as createGutter } from "./elements/gutter/model";
+import { getModel as createFillField } from "./elements/fill-field/model";
 import {
   getModel as createComposition,
   getFullModel as getCompositionElements,
@@ -31,7 +33,7 @@ const PAGE = {
   type: "PAGE",
   columnGap: 20,
   locators: [],
-  globals: [],
+  sides: [],
   columns: [],
 };
 // 可编辑栏
@@ -94,6 +96,10 @@ const ELEMENT_INFOS = {
     name: "网格",
     getModel: createGrids,
   },
+  GUTTER: {
+    name: "装订线",
+    getModel: createGutter,
+  },
   FILL_QUESTION: {
     name: "选择题",
     getModel: createFillQuestion,
@@ -110,6 +116,10 @@ const ELEMENT_INFOS = {
     name: "作文题",
     getModel: createComposition,
   },
+  FILL_FIELD: {
+    name: "填词块",
+    getModel: createFillField,
+  },
 };
 
 const ELEMENT_LIST = EDITABLE_ELEMENT.map((type) => {

+ 2 - 2
src/modules/card/elements/card-head/model.js

@@ -14,8 +14,8 @@ const MODEL = {
   sign: "head",
 };
 
-const getModel = (cardConfig) => {
-  const model = objAssign(MODEL, cardConfig);
+const getModel = (presetData) => {
+  const model = objAssign(MODEL, presetData);
   model.id = getElementId();
   model.key = randomCode();
 

+ 8 - 52
src/modules/card/elements/explain/EditExplain.vue

@@ -17,27 +17,6 @@
           clearable
         ></el-input>
       </el-form-item>
-      <el-form-item prop="startEnd" 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="0"
-          :max="999"
-          :step="1"
-          step-strictly
-          :controls="false"
-        ></el-input-number>
-      </el-form-item>
     </el-form>
   </div>
 </template>
@@ -46,9 +25,6 @@
 const initModalForm = {
   id: "",
   topicName: "",
-  startNumber: 1,
-  endNumber: 4,
-  questionsCount: 4,
 };
 
 export default {
@@ -62,16 +38,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 },
       rules: {
@@ -82,18 +48,6 @@ export default {
             trigger: "change",
           },
         ],
-        endNumber: [
-          {
-            required: true,
-            message: "请输入起止题号",
-            trigger: "change",
-          },
-          {
-            type: "number",
-            validator: numberRangeValidater,
-            trigger: "change",
-          },
-        ],
       },
     };
   },
@@ -103,18 +57,20 @@ export default {
   methods: {
     initData(val) {
       const valInfo = val.parent || val;
-      this.modalForm = { ...valInfo };
-      this.modalForm.endNumber =
-        this.modalForm.startNumber + this.modalForm.questionsCount - 1;
+      this.modalForm = this.$objAssign(initModalForm, valInfo);
     },
     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.topicName = this.modalForm.topicName.trim();
-      this.$emit("modified", this.modalForm);
+      let data = {};
+      if (this.instance.parent) {
+        data = this.$objAssign(this.instance.parent, this.modalForm);
+      } else {
+        data = this.$objAssign(this.instance, this.modalForm);
+      }
+      this.$emit("modified", data);
     },
   },
 };

+ 2 - 2
src/modules/card/elements/explain/ElemExplain.vue

@@ -5,10 +5,10 @@
     </div>
     <div ref="ElemBody" class="elem-body">
       <div
-        v-if="data.parent.questionsCount > 1 && !data.isExtend"
+        v-if="data.parent.questions.length > 1 && !data.isExtend"
         class="elem-explain-no"
       >
-        {{ data.serialNumber }}、
+        {{ data.questionNo }}、
       </div>
       <!-- 解答题子元件区域 -->
       <div class="elem-explain-elements">

+ 2 - 2
src/modules/card/elements/explain/ElemExplainEdit.vue

@@ -5,10 +5,10 @@
     </div>
     <div class="elem-body" :style="bodyStyle">
       <div
-        v-if="data.parent.questionsCount > 1 && !data.isExtend"
+        v-if="data.parent.questions.length > 1 && !data.isExtend"
         class="elem-explain-no"
       >
-        {{ data.serialNumber }}、
+        {{ data.questionNo }}、
       </div>
       <!-- 解答题子元件编辑区域 -->
       <div

+ 15 - 18
src/modules/card/elements/explain/model.js

@@ -1,12 +1,11 @@
-import { getElementId, randomCode, deepCopy } from "../../plugins/utils";
+import { getElementId, randomCode, objAssign } from "../../plugins/utils";
 
 const EXPLAIN_PROP = {
   type: "EXPLAIN",
   sign: "subjective",
   topicNo: null,
   topicName: "",
-  startNumber: 1,
-  questionsCount: 1,
+  questions: [],
 };
 // 解答题-小题
 const MODEL = {
@@ -18,6 +17,7 @@ const MODEL = {
   minHeight: 60,
   sign: "subjective",
   topicNo: null,
+  questionNo: "",
   isCovered: false,
   // 是否是小题的最后一个答题区,初始每个小题只有一个答题区,默认为true
   isLast: true,
@@ -25,8 +25,6 @@ const MODEL = {
   isExtend: false,
   // 是否展示解答题题目内容,解答题第1小题的第1个答题区需要显示解答题题目内容
   showTitle: false,
-  // 小题序号
-  serialNumber: 0,
   // 每一个解答题小题都可以包含其他基础元件,这些基础元件都用绝对定位
   elements: [],
   // 解答题整体信息,EXPLAIN_PROP
@@ -34,32 +32,31 @@ const MODEL = {
 };
 // tip属性存在时的条件:parent:大题的小题,container:题目内的子元素
 
-const getModel = () => {
-  return {
-    id: getElementId(),
-    key: randomCode(),
-    ...EXPLAIN_PROP,
-  };
+const getModel = (presetData) => {
+  const model = objAssign(EXPLAIN_PROP, presetData);
+  model.id = getElementId();
+  model.key = randomCode();
+
+  return model;
 };
 
 const getFullModel = (explainProp) => {
   const parent = { ...explainProp };
 
   let elements = [];
-
-  for (let i = 0; i < explainProp.questionsCount; i++) {
-    let child = Object.assign({}, deepCopy(MODEL), {
+  explainProp.questions.forEach((item, index) => {
+    let child = Object.assign({}, MODEL, {
       id: getElementId(),
       key: randomCode(),
       w: parent.w,
       topicNo: parent.topicNo,
-      serialNumber: i + explainProp.startNumber,
+      questionNo: item,
+      showTitle: !index,
       parent,
     });
 
-    elements[i] = child;
-  }
-  elements[0].showTitle = true;
+    elements[index] = child;
+  });
 
   return elements;
 };

+ 7 - 7
src/modules/card/elements/fill-field/model.js

@@ -1,4 +1,4 @@
-import { getElementId, randomCode, deepCopy } from "../../plugins/utils";
+import { getElementId, randomCode, objAssign } from "../../plugins/utils";
 
 const MODEL = {
   type: "FILL_FIELD",
@@ -14,12 +14,12 @@ const MODEL = {
   fieldInfos: {},
 };
 
-const getModel = () => {
-  return {
-    id: getElementId(),
-    key: randomCode(),
-    ...deepCopy(MODEL),
-  };
+const getModel = (presetData) => {
+  const model = objAssign(MODEL, presetData);
+  model.id = getElementId();
+  model.key = randomCode();
+
+  return model;
 };
 
 export { MODEL, getModel };

+ 28 - 2
src/modules/card/elements/fill-question/EditFillQuestion.vue

@@ -17,6 +17,17 @@
           clearable
         ></el-input>
       </el-form-item>
+      <el-form-item prop="questionCountPerGroup" label="单列小题数:">
+        <el-input-number
+          v-model="modalForm.questionCountPerGroup"
+          style="width: 125px"
+          :min="1"
+          :max="5"
+          :step="1"
+          step-strictly
+          :controls="false"
+        ></el-input-number>
+      </el-form-item>
       <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>
@@ -68,6 +79,7 @@ import { BOOLEAN_TYPE } from "../../enumerate";
 const initModalForm = {
   id: "",
   topicName: "",
+  questionCountPerGroup: 5,
   isBoolean: false,
   booleanType: BOOLEAN_TYPE[0],
   isMultiply: false,
@@ -116,6 +128,14 @@ export default {
             trigger: "change",
           },
         ],
+        questionCountPerGroup: [
+          {
+            required: true,
+            type: "number",
+            message: "请输入单列小题数",
+            trigger: "change",
+          },
+        ],
         booleanType: [
           {
             required: true,
@@ -132,7 +152,7 @@ export default {
   methods: {
     initData(val) {
       const valInfo = val.parent || val;
-      this.modalForm = Object.assign({}, this.initModalForm, valInfo);
+      this.modalForm = this.$objAssign(initModalForm, valInfo);
       this.booleanTypeChange();
     },
     selectTypeChange(val) {
@@ -155,7 +175,13 @@ export default {
         this.booleanTypes.no,
       ].join();
       this.modalForm.topicName = this.modalForm.topicName.trim();
-      this.$emit("modified", this.modalForm);
+      let data = {};
+      if (this.instance.parent) {
+        data = this.$objAssign(this.instance.parent, this.modalForm);
+      } else {
+        data = this.$objAssign(this.instance, this.modalForm);
+      }
+      this.$emit("modified", data);
     },
   },
 };

+ 67 - 0
src/modules/card/elements/gutter/EditGutter.vue

@@ -0,0 +1,67 @@
+<template>
+  <div class="edit-gutter">
+    <el-form
+      ref="modalFormComp"
+      :key="modalForm.id"
+      :model="modalForm"
+      :rules="rules"
+      label-width="120px"
+    >
+      <el-form-item prop="content" label="描述内容:">
+        <el-input
+          v-model="modalForm.content"
+          placeholder="请输入描述内容"
+          clearable
+        ></el-input>
+      </el-form-item>
+    </el-form>
+  </div>
+</template>
+
+<script>
+const initModalForm = {
+  id: "",
+  content: "",
+};
+
+export default {
+  name: "EditGutter",
+  props: {
+    instance: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {
+      modalForm: { ...initModalForm },
+      rules: {
+        content: [
+          {
+            required: true,
+            message: "请输入描述内容",
+            trigger: "change",
+          },
+        ],
+      },
+    };
+  },
+  mounted() {
+    this.initData(this.instance);
+  },
+  methods: {
+    initData(val) {
+      this.modalForm = this.$objAssign(initModalForm, val);
+    },
+    async submit() {
+      const valid = await this.$refs.modalFormComp.validate().catch(() => {});
+      if (!valid) return;
+
+      const data = this.$objAssign(this.instance, this.modalForm);
+      this.$emit("modified", data);
+    },
+  },
+};
+</script>

+ 35 - 0
src/modules/card/elements/gutter/ElemGutter.vue

@@ -0,0 +1,35 @@
+<template>
+  <div :class="classes">
+    <p class="elem-gutter-content">
+      <span v-for="(cont, index) in conts" :key="index">{{ cont }}</span>
+    </p>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "ElemGutter",
+  props: {
+    data: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
+  data() {
+    return {
+      conts: [],
+    };
+  },
+  computed: {
+    classes() {
+      return ["elem-gutter", `elem-gutter-${this.data.direction}`];
+    },
+  },
+  created() {
+    this.conts = this.data.content.split("");
+  },
+  methods: {},
+};
+</script>

+ 21 - 0
src/modules/card/elements/gutter/model.js

@@ -0,0 +1,21 @@
+import { getElementId, randomCode, objAssign } from "../../plugins/utils";
+
+const MODEL = {
+  type: "GUTTER",
+  x: 0,
+  y: 0,
+  w: 0,
+  h: 0,
+  content: "装订线",
+  direction: "left",
+};
+
+const getModel = (presetData) => {
+  const model = objAssign(MODEL, presetData);
+  model.id = getElementId();
+  model.key = randomCode();
+
+  return model;
+};
+
+export { MODEL, getModel };

+ 26 - 1
src/modules/card/pageModel.js

@@ -8,12 +8,20 @@ const templateDict = {
         modelType: "MODEL_ONE",
       },
     ],
-    side: [
+    sideLeft: [
       {
         type: "GUTTER",
+        direction: "left",
+        x: 60,
+        w: 30,
       },
       {
         type: "FILL_FIELD",
+        fieldCountPerLine: 2,
+        w: 440,
+        h: 40,
+        y: 980,
+        x: 26,
         fields: [
           {
             name: "考生编号",
@@ -23,6 +31,15 @@ const templateDict = {
             name: "姓名",
             code: "",
           },
+        ],
+      },
+      {
+        type: "FILL_FIELD",
+        w: 220,
+        h: 40,
+        y: 210,
+        x: 26,
+        fields: [
           {
             name: "考生确认签名",
             code: "",
@@ -30,6 +47,14 @@ const templateDict = {
         ],
       },
     ],
+    sideRight: [
+      {
+        type: "GUTTER",
+        direction: "right",
+        x: 30,
+        w: 30,
+      },
+    ],
   },
 };
 

+ 5 - 0
src/modules/card/plugins/utils.js

@@ -128,6 +128,10 @@ function getElementId() {
   return `element-${randomCode()}`;
 }
 
+function numberIsOdd(num) {
+  return !!(num % 2);
+}
+
 export {
   objTypeOf,
   deepCopy,
@@ -140,4 +144,5 @@ export {
   calcSum,
   isEmptyObject,
   getElementId,
+  numberIsOdd,
 };

+ 28 - 24
src/modules/card/store/card.js

@@ -6,7 +6,13 @@ import {
   getNewPage,
   getTopicHeadModel,
 } from "../elementModel";
-import { objAssign, deepCopy, calcSum, getElementId } from "../plugins/utils";
+import {
+  objAssign,
+  deepCopy,
+  calcSum,
+  getElementId,
+  numberIsOdd,
+} from "../plugins/utils";
 
 const state = {
   cardConfig: {},
@@ -15,6 +21,11 @@ const state = {
   curDragElement: {},
   curPage: {},
   curPageNo: 0,
+  pageDefaultElems: {
+    elements: [],
+    sideLeft: [],
+    sideRight: [],
+  },
   pages: [],
   topics: [],
   topicSeries: [], // 大题顺序号
@@ -46,6 +57,9 @@ const mutations = {
   setPages(state, pages) {
     state.pages = pages;
   },
+  setPageDefaultElems(state, pageDefaultElems) {
+    state.pageDefaultElems = pageDefaultElems;
+  },
   setTopics(state, topics) {
     state.topics = topics;
   },
@@ -572,7 +586,6 @@ const actions = {
     const pageSize = state.cardConfig.pageSize;
     // 更新元件最新的高度信息
     // 整理所有元件
-    const cardHeadElement = state.topics[0];
     state.topics.forEach((element) => {
       const elementDom = document.getElementById(`preview-${element.id}`);
 
@@ -590,8 +603,6 @@ const actions = {
 
     // 动态计算每列可以分配的元件
     const columnHeight = document.getElementById("topic-column").offsetHeight;
-    const simpleCardHeadHeight =
-      document.getElementById("simple-card-head").offsetHeight;
     let pages = [];
     let page = {};
     let columns = [];
@@ -601,26 +612,11 @@ const actions = {
     const initCurColumnElements = () => {
       curColumnElements = [];
       curColumnHeight = 0;
-      const groupColumnNumber = columnNumber * 2;
-      // 奇数页第一栏;
-      if (!(columns.length % groupColumnNumber)) {
-        // 非第一页奇数页第一栏
-        if (columns.length) {
-          const cardHeadSimpleElement = Object.assign({}, cardHeadElement, {
-            id: getElementId(),
-            isSimple: true,
-            h: simpleCardHeadHeight,
-          });
-          curColumnElements.push(cardHeadSimpleElement);
-          curColumnHeight += simpleCardHeadHeight;
-        } else {
-          curColumnElements.push(cardHeadElement);
-          curColumnHeight += cardHeadElement.h;
-        }
-      }
     };
 
     const checkElementIsCurColumnFirstType = (element) => {
+      if (element.type === "CARD_HEAD") return;
+
       const topicHeadIndex = curColumnElements.findIndex(
         (elem) => elem.type === "TOPIC_HEAD" && elem.sign === element.sign
       );
@@ -663,7 +659,7 @@ const actions = {
 
     // 批量添加所有元素。
     initCurColumnElements();
-    state.topics.slice(1).forEach((element, eindex) => {
+    state.topics.forEach((element, eindex) => {
       element.elementSerialNo = eindex;
       pushElement(element);
     });
@@ -679,14 +675,22 @@ const actions = {
       page.columns[columnNo].elements = column;
 
       if (columnNo + 1 === columnNumber || cindex === columns.length - 1) {
-        pages.push(deepCopy(page));
+        pages.push(page);
       }
     });
     // 保证页面总是偶数页
-    if (pages.length % 2) {
+    if (numberIsOdd(pages.length)) {
       pages.push(getNewPage(pages.length, { pageSize, columnNumber }));
     }
 
+    pages.forEach((page, pindex) => {
+      if (numberIsOdd(pindex + 1)) {
+        page.sides = deepCopy(state.pageDefaultElems.sideLeft);
+      } else {
+        page.sides = deepCopy(state.pageDefaultElems.sideRight);
+      }
+    });
+
     commit("setPages", pages);
     commit("setCurPage", state.curPageNo);
   },