阿凡驴的开胃小菜之如何手撸一个简约的表单设计器

116 阅读13分钟

⚠ 带有上下 drag 的表单设计器 先看效果

功能依赖:vue2 + element-ui + draggable

1.文字类功能操作 字体风格、字号、颜色、粗体、显示位置 form.png 2.分割线操作功能 分割线颜色、分割线类型(单/双分割线)、上/下分割线宽、分割线间隔 image.png 3.中间列部分每个元素drag功能,同步左侧预览区域上下移动 image.png

<template>
  <div class="report-list-container">
    <div class="page-title">
      面包屑
    </div>
    <div class="content-mode">
      <!-- 左侧预览区域 -->
      <div class="left-preview">
        <div>
          // myArray 中的每一个对象都是一个功能(标题、下划线等)
          // v-html渲染内容 buildLabel方法传入当前项根据不同处理在函数内返回不同渲染内容 
          <div
            v-for="item in myArray"
            :key="item.id"
            v-html="buildLabel(item)"
            :style="{
              fontSize: `${item.modelFontSize}px`,
              textAlign: item.textAlign,
              color: item.color || 'black',
              marginTop: `${item.marginTop}px`,
              marginBottom: `${item.marginBottom}px`,
              fontFamily: item.fontFamily || 'Microsoft YaHei',
              display: item.displayLeft || 'block',
              backgroundColor: item.lineColor || '',
              fontWeight: item.fontWeight ? item.fontWeight : 'normal',
              height: item.lineWidth ? `${item.lineWidth}px` : '',
              display: item.display || 'block',
              justifyContent: item.justifyContent || '',
            }"
            :class="item.class || ''"
          ></div>
        </div>
      </div>
      <!--右侧编辑区域-->
      <div class="right-edit">
        <div class="drag">
          <p class="edit-title">红头</p>
          <draggable
            v-model="myArray"
            chosenClass="chosen"
            forceFallback="true"
            group="people"
            animation="1000"
          >
            <transition-group>
              <div
                class="item"
                v-for="element in myArray"
                :style="{ display: element.displayRight || 'block' }"
                :key="element.id"
                :class="{ highlight: currentHighlightId === element.id }"
              >
                <div @click="setCurrentHighlightId(element)">
                  {{ element.text }}
                </div>
              </div>
            </transition-group>
          </draggable>
          <div style="text-align: center; margin-top: 20px">
            <el-button type="primary" @click="addDom">添加</el-button>
          </div>
        </div>
        <div class="operation">
          <div
            style="
              text-align: right;
              height: 50px;
              line-height: 50px;
              background-color: #f9f9f9;
            "
          >
            <span style="margin-right: 10px">显示状态</span>
            <el-switch
              v-model="conceal"
              active-color="#13ce66"
              inactive-color="#ff4949"
              @change="displayStatusChange()"
              style="margin-right: 10px"
            >
            </el-switch>
          </div>
          <div class="operating-area">
            <div v-for="item in myArray" :key="item.id">
              <div
                v-if="item.id === currentHighlightId && item.conceal === true"
              >
                <!-- 字体 -->
                <div class="font" v-if="item.fontFamily">
                  <label class="label" for="fontSize">字体</label>
                  <span>{{ item.fontFamily }}</span>
                  <el-select
                    class="oneself-right"
                    size="small"
                    v-model="item.fontFamily"
                    placeholder="字体"
                  >
                    <el-option
                      v-for="element in fontFamilyOptions"
                      :key="element.value"
                      :label="element.label"
                      :value="element.value"
                    >
                    </el-option>
                  </el-select>
                </div>
                <!-- 调整字号 -->
                <div class="font-size" v-if="item.fontSize">
                  <label class="label" for="fontSize">字号</label>
                  <span>{{ item.wordSizeValue }}</span>
                  <div style="display: flex" class="oneself-right">
                    <div class="size-mark">
                      <el-select
                        size="small"
                        v-model="item.fontSize"
                        placeholder="字号"
                        @change="wordSizeChange(item.fontSize, item)"
                      >
                        <el-option
                          v-for="element in fontSizeOptions"
                          :key="element.value"
                          :label="element.label"
                          :value="element.value"
                        >
                        </el-option>
                      </el-select>
                    </div>
                    <div class="size-pound">
                      <el-select
                        popper-class="select-pound"
                        size="small"
                        v-model="item.poundOrPixel"
                        placeholder="px"
                        @change="pondChange(item)"
                        @focus="poundOrPixelFocus(item)"
                      >
                        <el-option
                          v-for="i in poundOptions"
                          :key="i.value"
                          :label="i.label"
                          :value="i.value"
                        >
                        </el-option>
                      </el-select>
                    </div>
                  </div>
                </div>
                <!-- 字体颜色 -->
                <div class="font-color" v-if="item.color || item.colorSelect">
                  <label class="label" for="fontSize">颜色</label>
                  <span>{{ item.color }}</span>
                  <div style="display: flex" class="oneself-right">
                    <div class="clear" @click="clearColor(item.id)">重置</div>
                    <div>
                      <el-color-picker
                        class="color-select"
                        v-model="item.color"
                      ></el-color-picker>
                    </div>
                  </div>
                </div>
                <!-- 粗体 -->
                <div class="font-width" v-if="item.fontWeight">
                  <label class="label" for="fontWidth">粗体</label>
                  <span>{{ item.fontWidthText }}</span>
                  <div style="display: flex" class="oneself-right">
                    <div>
                      <el-radio-group
                        @input="fontWidthRadioInput(item)"
                        v-model="item.fontWidthText"
                        size="small"
                      >
                        <el-radio-button label="正常"
                          ><i
                            size="10"
                            class="iconfont icon-a-VectorStroke-2"
                          ></i
                        ></el-radio-button>
                        <el-radio-button label="加粗体"
                          ><i size="10" class="iconfont icon-bold"></i
                        ></el-radio-button>
                      </el-radio-group>
                    </div>
                  </div>
                </div>
                <!-- 显示位置 -->
                <div
                  class="text-align align"
                  v-if="(item.text || item.subset) && item.nullText"
                >
                  <label class="label" for="fontWidth">显示位置</label>
                  <span>{{ item.textAlignText }}</span>
                  <div style="display: flex" class="oneself-right">
                    <div>
                      <el-radio-group
                        @input="TextAlignRadioInput(item)"
                        v-model="item.textAlign"
                        size="small"
                      >
                        <el-radio-button label="left"
                          ><i
                            style="font-size: 20px"
                            class="iconfont icon-duiqifangshi-zuoduiqi"
                          ></i
                        ></el-radio-button>
                        <el-radio-button label="center"
                          ><i
                            class="iconfont icon-duiqifangshi-juzhongduiqi"
                          ></i
                        ></el-radio-button>
                        <el-radio-button label="right"
                          ><i class="iconfont icon-duiqifangshi-youduiqi"></i
                        ></el-radio-button>
                        <el-radio-button label="justify"
                          ><i class="iconfont icon-dispersed"></i
                        ></el-radio-button>
                      </el-radio-group>
                    </div>
                  </div>
                </div>
                <!-- 分割线颜色 -->
                <div class="font-size" v-if="item.lineColorSelect">
                  <label class="label" for="fontSize">分割线颜色</label>
                  <span>{{ item.content[0].lineColor }}</span>
                  <div style="display: flex" class="oneself-right">
                    <div
                      class="size-mark"
                      style="
                        text-align: right;
                        display: flex;
                        align-items: center;
                        justify-content: right;
                      "
                    >
                      <div
                        class="clear"
                        @click="
                          () => {
                            (item.content[0].lineColor = '#FF0000'),
                              (item.content[1].lineColor = '#FF0000');
                          }
                        "
                      >
                        重置
                      </div>
                      <el-color-picker
                        @change="changeColor(item)"
                        v-model="item.content[0].lineColor"
                      ></el-color-picker>
                    </div>
                  </div>
                </div>
                <!-- 分割类型 -->
                <div class="font-size" v-if="item.lineType">
                  <label class="label" for="fontSize">分割类型</label>
                  <span>{{
                    item.lineType === "one" ? "单线分割" : "双线分割"
                  }}</span>
                  <div
                    style="display: flex; width: 100px"
                    class="oneself-right"
                  >
                    <div class="size-mark" style="text-align: right">
                      <el-radio-group
                        style="display: flex"
                        @input="radioLine(item)"
                        v-model="item.lineType"
                        size="small"
                      >
                        <el-radio-button label="one"
                          ><i class="iconfont icon-caidanfengexian"></i
                        ></el-radio-button>
                        <el-radio-button label="double"
                          ><i class="iconfont icon-fengexianfenge"></i
                        ></el-radio-button>
                      </el-radio-group>
                    </div>
                  </div>
                </div>
                <!-- 上分割线线宽度 -->
                <div
                  class="font-size upLineWidth"
                  v-if="item.lineWidth || item.FixedLineWidth"
                >
                  <label class="label" for="fontSize">上分割线宽</label>
                  <span
                    ><el-slider
                      :max="20"
                      :min="1"
                      @input="upLineSliderInput(item)"
                      v-model="item.content[0].inputWidth"
                    ></el-slider
                  ></span>

                  <div style="display: flex" class="oneself-right">
                    <div @click="upLineReset(item)" class="clear">重置</div>
                    <div class="size-mark" style="width: 65px">
                      <input
                        type="number"
                        style="
                          border: 1px solid #ccc;
                          border-right: 0;
                          width: 65px;
                          height: 36px;
                          box-sizing: border-box;
                          padding: 5px;
                        "
                        :value="item.content[0].inputWidth"
                        @input="upLineWidthInput($event, item)"
                      />
                    </div>
                    <div class="size-pound">
                      <el-select
                        style="height: 39px; border-radius: 0px"
                        popper-class="select-pound"
                        size="small"
                        v-model="item.content[0].poundOrPixel"
                        placeholder="px"
                        @change="upLineChange(item)"
                      >
                        <el-option
                          v-for="i in poundOptions"
                          :key="i.value"
                          :label="i.label"
                          :value="i.value"
                        >
                        </el-option>
                      </el-select>
                    </div>
                  </div>
                </div>
                <!-- 下分割线线宽度 -->
                <div
                  class="font-size upLineWidth"
                  v-if="
                    (item.lineWidth || item.FixedLineWidth) &&
                    item.content[1].display === 'block'
                  "
                >
                  <label class="label" for="fontSize">下分割线宽</label>
                  <span
                    ><el-slider
                      :max="20"
                      :min="1"
                      @input="downLineSliderInput(item)"
                      v-model="item.content[1].inputWidth"
                    ></el-slider
                  ></span>

                  <div style="display: flex" class="oneself-right">
                    <div @click="downLineReset(item)" class="clear">重置</div>
                    <div class="size-mark" style="width: 65px">
                      <input
                        type="number"
                        style="
                          border: 1px solid #ccc;
                          border-right: 0;
                          width: 65px;
                          height: 36px;
                          box-sizing: border-box;
                          padding: 5px;
                        "
                        :value="item.content[1].inputWidth"
                        @input="downLineWidthInput($event, item)"
                      />
                    </div>
                    <div class="size-pound">
                      <el-select
                        style="height: 39px; border-radius: 0px"
                        popper-class="select-pound"
                        size="small"
                        v-model="item.content[1].poundOrPixel"
                        placeholder="px"
                        @change="downLineChange(item)"
                      >
                        <el-option
                          v-for="i in poundOptions"
                          :key="i.value"
                          :label="i.label"
                          :value="i.value"
                        >
                        </el-option>
                      </el-select>
                    </div>
                  </div>
                </div>
                <!-- 分割线间隔 -->
                <div
                  class="font-size upLineWidth"
                  v-if="
                    (item.lineWidth || item.FixedLineWidth) &&
                    item.content[1].display === 'block'
                  "
                >
                  <label class="label" for="fontSize">分割线间隔</label>
                  <span
                    ><el-slider
                      :max="20"
                      :min="1"
                      @input="intervalLineMarginInput(item)"
                      v-model="item.content[0].interValMarginBottom"
                    ></el-slider
                  ></span>

                  <div style="display: flex" class="oneself-right">
                    <div @click="intervalReset(item)" class="clear">重置</div>
                    <div class="size-mark" style="width: 65px">
                      <input
                        type="number"
                        style="
                          border: 1px solid #ccc;
                          border-right: 0;
                          width: 65px;
                          height: 36px;
                          box-sizing: border-box;
                          padding: 5px;
                        "
                        :value="item.content[0].interValMarginBottom"
                        @input="intervalMarginInput($event, item)"
                      />
                    </div>
                    <div class="size-pound">
                      <el-select
                        style="height: 39px; border-radius: 0px"
                        popper-class="select-pound"
                        size="small"
                        v-model="item.content[0].lineInterval"
                        placeholder="px"
                        @change="intervalChange(item)"
                      >
                        <el-option
                          v-for="i in poundOptions"
                          :key="i.value"
                          :label="i.label"
                          :value="i.value"
                        >
                        </el-option>
                      </el-select>
                    </div>
                  </div>
                </div>
                <div
                  class="font-size upLineWidth"
                  v-if="item.height && !item.lineType && !item.fontSize"
                >
                  <label class="label" for="fontSize">占位符高度</label>
                  <span
                    ><el-slider
                      :max="100"
                      :min="1"
                      @input="placeholderSlider(item)"
                      v-model="item.virtualHeight"
                    ></el-slider
                  ></span>
                  <div style="display: flex" class="oneself-right">
                    <div @click="placeholderReset(item)" class="clear">
                      重置
                    </div>
                    <div class="size-mark" style="width: 65px">
                      <input
                        type="number"
                        style="
                          border: 1px solid #ccc;
                          border-right: 0;
                          width: 65px;
                          height: 36px;
                          box-sizing: border-box;
                          padding: 5px;
                        "
                        :value="item.virtualHeight"
                        @input="placeholderInput($event, item)"
                      />
                    </div>
                    <div class="size-pound">
                      <el-select
                        style="height: 39px; border-radius: 0px"
                        popper-class="select-pound"
                        size="small"
                        v-model="item.virtualUnit"
                        placeholder="px"
                        @change="placeholderChange(item)"
                      >
                        <el-option
                          v-for="i in poundOptions"
                          :key="i.value"
                          :label="i.label"
                          :value="i.value"
                        >
                        </el-option>
                      </el-select>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
// 需要 npm 安装 draggable
import draggable from "vuedraggable";
export default {
  components: {
    draggable,
  },
  watch: {},
  data() {
    return {
      conceal: true, // 显示状态中间值
      median: false, // true 表示当前选择的是分散对齐
      overall: "pixel", // 中间值承接 pixel 或 pound
      title: "红头标题",
      currentHighlightId: 0,
      // 字体选项
      fontFamilyOptions: [
        {
          value: "宋体",
          label: "宋体",
        },
        {
          value: "黑体",
          label: "黑体",
        },
        {
          value: "微软雅黑",
          label: "微软雅黑",
        },
        {
          value: "新宋体",
          label: "新宋体",
        },
        {
          value: "楷体",
          label: "楷体",
        },
        {
          value: "仿宋",
          label: "仿宋",
        },
        {
          value: "微软正黑体",
          label: "微软正黑体",
        },
      ],
      wordSizeValue: "小一", // 承接字号文本 
      // 字号选项
      fontSizeOptions: [
        {
          value: "56",
          label: "初号",
        },
        {
          value: "48",
          label: "小初",
        },
        {
          value: "34.7",
          label: "一号",
        },
        {
          value: "32",
          label: "小一",
        },
        {
          value: "29.3",
          label: "二号",
        },
        {
          value: "24",
          label: "小二",
        },
        {
          value: "21.3",
          label: "三号",
        },
        {
          value: "20",
          label: "小三",
        },
        {
          value: "18.7",
          label: "四号",
        },
        {
          value: "16",
          label: "小四",
        },
        {
          value: "14",
          label: "五号",
        },
        {
          value: "12",
          label: "小五",
        },
        {
          value: "10",
          label: "六号",
        },
        {
          value: "8.7",
          label: "小六",
        },

        {
          value: "7.3",
          label: "七号",
        },
        {
          value: "6.7",
          label: "八号",
        },
      ],
      // 磅选项
      poundOptions: [
        {
          value: "pixel",
          label: "px",
        },
        {
          value: "pound",
          label: "磅",
        },
      ],
      myArray: [
        {
          colorSelect: true, // 点击清空不要删除颜色选择器 只清空 color 值 带有颜色选择器的项此值必须为 true
          people: "cn",
          id: 0,
          name: "红头文件主标题",
          label: "h1",
          class: "h1",
          defaultColor: "#FF0000",
          text: "红头文件主标题",
          fontSize: "32",
          modelFontSize: "32",
          textAlign: "center",
          color: "#FF0000" || "black",
          marginTop: 0,
          marginBottom: 0,
          fontFamily: "微软雅黑",
          poundOrPixel: "pixel", // 磅或者px
          fontWeight: "normal",
          fontWidthText: "正常",
          textAlignText: "居中显示",
          display: "block",
          justifyContent: "space-between",
          nullText: true,
          median: false, // 是否为分散对齐切换为非分散对齐
          wordSizeValue: "小一",
          conceal: true, // 隐藏元素
        },
        {
          colorSelect: true, // 点击清空不要删除颜色选择器 只清空 color 值 带有颜色选择器的项此值必须为 true
          people: "cn",
          id: 1,
          name: "副标题",
          label: "div",
          class: "h2",
          defaultColor: "#FF0000",
          text: "副标题",
          fontSize: "24",
          modelFontSize: "24",
          textAlign: "center",
          color: "#FF0000" || "black",
          marginTop: 10,
          marginBottom: 0,
          fontFamily: "微软雅黑",
          poundOrPixel: "pixel", // 磅或者px
          fontWeight: "normal",
          fontWidthText: "正常",
          textAlignText: "居中显示",
          display: "block",
          justifyContent: "space-between",
          nullText: true,
          median: false, // 是否为分散对齐切换为非分散对齐
          wordSizeValue: "小二",
          conceal: true, // 隐藏元素
        },
        {
          colorSelect: true, // 点击清空不要删除颜色选择器 只清空 color 值 带有颜色选择器的项此值必须为 true
          people: "cn",
          id: 2,
          name: "期刊编号",
          label: "div",
          class: "periodical",
          defaultColor: "#000000",
          text: "期刊编号",
          fontSize: "16",
          modelFontSize: "16",
          textAlign: "center",
          color: "#FF0000" || "black",
          marginTop: 10,
          marginBottom: 10,
          fontFamily: "微软雅黑",
          poundOrPixel: "pixel", // 磅或者px
          fontWeight: "normal",
          fontWidthText: "正常",
          textAlignText: "居中显示",
          display: "block",
          justifyContent: "space-between",
          nullText: true,
          median: false, // 是否为分散对齐切换为非分散对齐
          wordSizeValue: "小四",
          conceal: true, // 隐藏元素
        },
        {
          subset: true,
          colorSelect: true, // 点击清空不要删除颜色选择器 只清空 color 值 带有颜色选择器的项此值必须为 true
          people: "cn",
          id: 3,
          uniqueIdentification: true, // 唯一标识true的解决问题(在单行元素多列情况下,选择分散显示,操作显示状态按钮,不会调用 disperseAlign 方法),单行单列元素选择分散对齐后操作显示状态按钮重新调用 disperseAlign 方法包裹 span 标签
          name: "编制单位/日期",
          label: "div",
          class: "date-unit",
          defaultColor: "#000000",
          text: "编制单位/日期",
          fontSize: "16",
          modelFontSize: "16",
          textAlign: "justify",
          color: "#000000" || "black",
          marginTop: 10,
          marginBottom: 10,
          fontFamily: "微软雅黑",
          poundOrPixel: "pixel", // 磅或者px
          fontWeight: "normal",
          fontWidthText: "正常",
          textAlignText: "居中显示",
          display: "flex",
          displayRight: "block",
          justifyContent: "space-between",
          nullText: true,
          median: false, // 是否为分散对齐切换为非分散对齐
          wordSizeValue: "小四",
          conceal: true, // 隐藏元素
          content: [
            {
              colorSelect: true, // 点击清空不要删除颜色选择器 只清空 color 值 带有颜色选择器的项此值必须为 true
              people: "cn",
              id: 31,
              name: "编制单位说明",
              label: "div",
              class: "unit",
              defaultColor: "#000000",
              text: "编制单位说明",
              fontSize: "16",
              textAlign: "justify",
              color: "#000000" || "black",
              marginTop: 10,
              marginBottom: 0,
              marginRight: 10,
              fontFamily: "微软雅黑",
              poundOrPixel: "pixel", // 磅或者px
              fontWeight: "normal",
              fontWidthText: "正常",
              textAlignText: "居中显示",
              display: "block",
              justifyContent: "space-between",
              nullText: true,
              median: false, // 是否为分散对齐切换为非分散对齐
              value: "说明说明说明说明说明",
            },
            {
              colorSelect: true, // 点击清空不要删除颜色选择器 只清空 color 值 带有颜色选择器的项此值必须为 true
              people: "cn",
              id: 31,
              name: "编制日期",
              label: "div",
              class: "date",
              defaultColor: "#000000",
              text: "编制日期",
              fontSize: "16",
              textAlign: "justify",
              color: "#000000" || "black",
              marginTop: 10,
              marginBottom: 10,
              fontFamily: "微软雅黑",
              poundOrPixel: "pixel", // 磅或者px
              fontWeight: "normal",
              fontWidthText: "正常",
              textAlignText: "居中显示",
              display: "block",
              justifyContent: "space-between",
              nullText: true,
              median: false, // 是否为分散对齐切换为非分散对齐
              value: "2024年08月30日",
            },
          ],
        },
        {
          subset: true, // 有子集元素
          lineColorSelect: true,
          hidden: true,
          id: 4,
          name: "分割线",
          text: "分割线",
          label: "div",
          class: "hr upLine",
          textAlign: "left",
          marginTop: 10,
          interValMarginBottom: 5,
          lineType: "double", // 单双线
          FixedLineWidth: true, // input为空也不删除此线
          poundOrPixel: "pixel", // 磅或者px
          lineInterval: "pixel", // 分割线间隔单位
          conceal: true, // 隐藏元素
          content: [
            {
              hidden: "block",
              lineColorSelect: true,
              // 初始化上下分割线都显示
              display: "block",
              people: "cn",
              id: 41,
              name: "分割线",
              text: "",
              label: "div",
              class: "hr upLine",
              lineColor: "#FF0000",
              marginTop: 10,
              marginBottom: 5, // 真实下外边距
              interValMarginBottom: 5, // 绑定变量中间值
              inputWidth: 4, // 绑定输入框
              lineWidth: 4, // 真实宽度
              lineType: "double", // 单双线
              FixedLineWidth: true, // 就算input为空也不删除此线
              poundOrPixel: "pixel", // 磅或者px
              lineInterval: "pixel", // 分割线间隔单位
            },
            {
              lineColorSelect: true,
              hidden: "block",
              display: "block",
              people: "cn",
              id: 42,
              name: "分割线",
              label: "div",
              class: "hr downLine",
              text: "",
              lineColor: "#FF0000",
              marginTop: 0,
              interValMarginBottom: 0,
              marginBottom: 10,
              inputWidth: 2,
              lineWidth: 2,
              lineType: "double", // 单双线
              FixedLineWidth: true, // 就算input为空也不删除此线
              poundOrPixel: "pixel", // 磅或者px
              lineInterval: "pixel", // 分割线间隔单位
            },
          ],
        },
        {
          // colorSelect: true, // 点击清空不要删除颜色选择器 只清空 color 值 带有颜色选择器的项此值必须为 true
          people: "cn",
          subset: true,
          id: 5,
          name: "占位符",
          label: "div",
          class: "placeholder",
          sonClass: "son-placeholder",
          text: "占位符",
          lineType: "",
          fontSize: "",
          modelFontSize: "",
          poundOrPixel: "pixel", // 磅或者px
          virtualUnit: "pixel", // 承接单位
          conceal: true, // 隐藏元素
          virtualHeight: 20, // 承接高度
          height: 20,
        },
      ],
    };
  },
  computed: {},
  mounted() {},
  methods: {
    buildLabel(item) {
      if ((item.text || item.subset) && item.conceal !== false) {
        // 这里的功能是固定的,所以可以使用 name 或 id 固定值判断
        if (item.name === "分割线") {
          let str = "";
          // 构造标签 并将标签名样式绑在新构造的元素上 当前item.content有两项,分别为上分割线和下分割线
          item.content.forEach((i) => {
            str += `<div class="${i.class}"
            style="height:${i.lineWidth}px;width:100%;background-color:${i.lineColor};margin-bottom:${i.marginBottom}px;margin-top:${i.marginTop}px;display:${i.display}">
            </div>`;
          });
          return `${str}`;
        } else if (item.name === "编制单位/日期") {
          let str = "";
          item.content.forEach((i) => {
            str += `<div style='font-weight:${i.fontWeight};margin-right:${i.marginRight}px'>
                ${i.name}${i.value}
            </div>`;
          });
          return `${str}`;
        } else if (item.name === "占位符") {
          return `<div class="${item.sonClass}" style="height: ${item.height}px"></div>`;
        } else {
          return `${item.name}`;
        }
      }
    },
    // 获取当前选中的 id 和 title 以及 conceal 元素隐藏状态
    setCurrentHighlightId(element) {
      this.currentHighlightId = element.id;
      this.title = element.name;
      this.conceal = element.conceal;
    },
    // 切换显示状态
    displayStatusChange() {
      this.myArray.forEach((item) => {
        // 找到对应的 id 赋值
        if (item.id === this.currentHighlightId) {
          // 显示状态的 switch 状态 v-model 到 this.conceal
          // 切换当前项显示态
          item.conceal = this.conceal;
        }
        // 切换显示态时 找到当前id且显示状态为true时,且当前对其方式为分散对齐,且 uniqueIdentification 不全等于 true
        // uniqueIdentification: true 的作用:唯一标识true的解决问题(在单行元素多列情况下,选择分散显示,操作显示状态按钮,不会调用 disperseAlign 方法),单行单列元素选择分散对齐后操作显示状态按钮重新调用 disperseAlign 方法
        if (
          item.id === this.currentHighlightId &&
          item.conceal === true &&
          item.textAlign === "justify" && item.uniqueIdentification !== true
        ) {
          // 调用分散对齐方法
          this.disperseAlign(item);
        }
      });
    },
    // 在切换单位的 select 框获取焦点时 用 overall 承接一下单位
    poundOrPixelFocus(item) {
      this.overall = item.poundOrPixel;
    },
    // "px"和"磅"的change事件
    pondChange(item) {
      if (item.poundOrPixel !== this.overall && item.poundOrPixel === "pixel") {
        this.overall = "pixel";
        // modelFontSize 字体大小的承接值
        // 选择的是"px"情况下赋值当前项字体大小
        item.modelFontSize = item.fontSize;
      }
      if (item.poundOrPixel !== this.overall && item.poundOrPixel === "pound") {
        this.overall = "pound";
        // 选择的为"磅"单位,字体放大1.33倍
        item.modelFontSize = item.fontSize * 1.33;
      }
    },
    // 字号大小切换事件
    wordSizeChange(size, element) {
      this.fontSizeOptions.forEach((item) => {
        // 找到当前项的字体大小在选项值里对应的默认大小
        if (item.value === size) {
          // fontSizeOptions 选项里的字体大小赋值给当前传入项(element)的字体值
          element.wordSizeValue = item.label;
          // 如果后缀单位为"磅"则放大1.33倍
          if (element.poundOrPixel === "pound") {
            element.modelFontSize = item.value * 1.33;
          }
          // 如果后缀单位为"px"则放直接赋值
          if (element.poundOrPixel === "pixel") {
            element.modelFontSize = item.value;
          }
        }
      });
    },
    // 清除颜色事件
    clearColor(id) {
      this.myArray.forEach((item) => {
        if (item.id === id) {
          // 还原颜色值
          item.color = "#000000";
        }
      });
    },
    // 选择粗体事件
    fontWidthRadioInput(item) {
      if (item.fontWidthText === "加粗体") {
        // 如果选择的是粗体,并且当前文本对齐方式为分散对齐
        if (item.textAlign === "justify") {
          // 则需要根据当前 item 里的 class 名称获取当前项元素里到所有的 span 标签依次加粗
          const spanArr = document.querySelectorAll(`.${item.class} span`);
          spanArr.forEach((i) => {
            i.style.fontWeight = "bold";
          });
        }
        // 如果是在"编制单位/日期"里选择的加粗 则将当前项下的 content 里的两个元素都进行加粗
        if (item.name === "编制单位/日期") {
          item.content.forEach((element) => {
            element.fontWeight = "bold";
          });
        }
        // 排除以上两种情况 赋值"bold"值和"加粗体"
        item.fontWeight = "bold";
        item.fontWidthText = "加粗体";
      }
      if (item.fontWidthText === "正常") {
        if (item.textAlign === "justify") {
          const spanArr = document.querySelectorAll(`.${item.class} span`);
          spanArr.forEach((i) => {
            i.style.fontWeight = "normal";
          });
        }
        if (item.name === "编制单位/日期") {
          item.content.forEach((element) => {
            element.fontWeight = "normal";
          });
        }
        item.fontWeight = "normal";
        item.fontWidthText = "正常";
      }
    },
    // 选择分散对齐的事件
    disperseAlign(item) {
      item.median = true;
      item.textAlignText = "分散显示";
      setTimeout(() => {
        // 获取到对应文本 split 分割
        let domTextArr = document
          .querySelector(`.${item.class}`)
          .innerHTML.split("");
        let newDom = "";
        // 然后给分割后的每个独立的文字包裹 span 标签
        domTextArr.forEach((item) => {
          newDom += `<span>${item}</span>`;
        });
        document.querySelector(`.${item.class}`).innerHTML = newDom;
        // 如果当前项的字体宽度为粗体,那么包裹完 span 标签后进行依次加粗
        if (item.fontWeight === "bold") {
          const spanArr = document.querySelectorAll(`.${item.class} span`);
          spanArr.forEach((i) => {
            i.style.fontWeight = "bold";
          });
        }
        item.display = "flex";
      });
    },
    // 选择文本对齐方式事件
    TextAlignRadioInput(item) {
      // 单独处理单行多列情况
      if (item.name === "编制单位/日期") {
        if (item.textAlign !== "justify") {
          // 除分散对齐外正常使用当前项的对齐方式
          item.justifyContent = item.textAlign;
        } else {
          // 两端对齐
          item.justifyContent = "space-between";
        }
      }
      // median:代表是否为分散对齐切换为非分散对齐
      if (item.textAlign !== "justify" && item.median === true) {
        document.querySelector(`.${item.class}`).innerHTML = item.name;
        item.display = "block";
        item.median = false;
      }
      if (item.textAlign === "left") {
        item.textAlignText = "居左显示";
      } else if (item.textAlign === "center") {
        item.textAlignText = "居中显示";
      } else if (item.textAlign === "right") {
        item.textAlignText = "居右显示";
      } else if (
        item.textAlign === "justify" &&
        item.name !== "编制单位/日期"
      ) {
        // 调用分散对齐方法
        this.disperseAlign(item);
      }
    },
    // 选择单/双分割线事件
    radioLine(item) {
      if (item.lineType === "one") {
        // 隐藏下分割线
        item.content[1].display = "none";
        // 如果选择了单分割线则重置下分割线和分割线间隔样式
        this.downLineReset(item);
        this.intervalReset(item);
      } else if (item.lineType === "double") {
        // 打开下分割线
        item.content[1].display = "block";
      }
    },
    // 颜色选择器的change事件
    changeColor(item) {
      item.content[1].lineColor = item.content[0].lineColor;
    },
    // 1.上分割线 
    // inputWidth为v-model的值  lineWidth为最后真实渲染值
    // slider
    upLineSliderInput(item) {
      if (item.content[0].poundOrPixel === "pound") {
        item.content[0].lineWidth = item.content[0].inputWidth * 1.33;
      } else if (item.content[0].poundOrPixel === "pixel") {
        item.content[0].lineWidth = item.content[0].inputWidth;
      }
    },
    // input
    upLineWidthInput(e, item) {
      if (!e.target.value) {
        item.content[0].inputWidth = 0;
      }
      item.content[0].inputWidth = Number(e.target.value);
      if (item.content[0].poundOrPixel === "pixel") {
        item.content[0].lineWidth = Number(item.content[0].inputWidth);
      } else if (item.content[0].poundOrPixel === "pound") {
        item.content[0].lineWidth = Number(item.content[0].inputWidth * 1.33);
      }
    },
    // 切换单位
    upLineChange(item) {
      if (item.content[0].poundOrPixel === "pixel") {
        item.content[0].lineWidth = Number(item.content[0].lineWidth / 1.33);
      } else if (item.content[0].poundOrPixel === "pound") {
        item.content[0].lineWidth = Number(item.content[0].lineWidth * 1.33);
      }
    },
    // 重置上分割线宽
    upLineReset(item) {
      item.content[0].lineWidth = 2;
      item.content[0].inputWidth = 2;
      item.content[0].poundOrPixel = "pixel";
    },
    // 2.下分割线
    // slider
    downLineSliderInput(item) {
      if (item.content[1].poundOrPixel === "pound") {
        item.content[1].lineWidth = item.content[1].inputWidth * 1.33;
      } else if (item.content[1].poundOrPixel === "pixel") {
        item.content[1].lineWidth = item.content[1].inputWidth;
      }
    },
    // input
    downLineWidthInput(e, item) {
      if (!e.target.value) {
        item.content[1].inputWidth = 0;
      }
      item.content[1].inputWidth = Number(e.target.value);
      if (item.content[1].poundOrPixel === "pixel") {
        item.content[1].lineWidth = Number(item.content[1].inputWidth);
      } else if (item.content[1].poundOrPixel === "pound") {
        item.content[1].lineWidth = Number(item.content[1].inputWidth * 1.33);
      }
    },
    // 切换单位
    downLineChange(item) {
      if (item.content[1].poundOrPixel === "pixel") {
        item.content[1].lineWidth = Number(item.content[1].lineWidth / 1.33);
      } else if (item.content[1].poundOrPixel === "pound") {
        item.content[1].lineWidth = Number(item.content[1].lineWidth * 1.33);
      }
    },
    // 重置下分割线宽
    downLineReset(item) {
      item.content[1].lineWidth = 2;
      item.content[1].inputWidth = 2;
      item.content[1].poundOrPixel = "pixel";
    },
    // 3.分割线间隔
    // slider
    intervalLineMarginInput(item) {
      if (item.content[0].lineInterval === "pound") {
        item.content[0].marginBottom =
          item.content[0].interValMarginBottom * 1.33;
      } else if (item.content[0].lineInterval === "pixel") {
        item.content[0].marginBottom = item.content[0].interValMarginBottom;
      }
    },
    // input
    intervalMarginInput(e, item) {
      if (!e.target.value) {
        item.content[0].marginBottom = 0;
        item.content[0].interValMarginBottom = 0;
      }
      item.content[0].interValMarginBottom = Number(e.target.value);
      item.poundOrPixel = item.lineInterval;
      if (item.content[0].poundOrPixel === "pixel") {
        item.content[0].marginBottom = Number(
          item.content[0].interValMarginBottom
        );
      }
      if (item.content[0].poundOrPixel === "pound") {
        item.content[0].marginBottom = Number(
          item.content[0].interValMarginBottom * 1.33
        );
      }
    },
    // 切换单位
    intervalChange(item) {
      if (item.content[0].lineInterval === "pixel") {
        item.content[0].marginBottom = Number(
          item.content[0].interValMarginBottom
        );
      } else if (item.content[0].lineInterval === "pound") {
        item.content[0].marginBottom = Number(
          item.content[0].interValMarginBottom * 1.33
        );
      }
    },
    // 重置分割线间隔
    intervalReset(item) {
      item.content[0].marginBottom = 5;
      item.content[0].interValMarginBottom = 5;
      item.content[0].poundOrPixel = "pixel";
      item.content[0].lineInterval = "pixel";
    },
    // 4.占位符高度
    // slider
    placeholderSlider(item) {
      if (item.virtualUnit === "pound") {
        item.height = item.virtualHeight * 1.33;
      } else if (item.virtualUnit === "pixel") {
        item.height = item.virtualHeight;
      }
    },
    // input
    placeholderInput(e, item) {
      if (!e.target.value || e.target.value < 1) {
        item.height = 0;
        item.virtualHeight = 0;
      }
      item.virtualHeight = Number(e.target.value);
      item.poundOrPixel = item.virtualUnit;
      if (item.virtualUnit === "pixel") {
        item.height = Number(item.virtualHeight);
      }
      if (item.virtualUnit === "pound") {
        item.height = Number(item.virtualHeight * 1.33);
      }
    },
    // 切换单位
    placeholderChange(item) {
      if (item.virtualUnit === "pixel") {
        item.height = Number(item.virtualHeight);
      } else if (item.virtualUnit === "pound") {
        item.height = Number(item.virtualHeight * 1.33);
      }
    },
    // 重置占位符
    placeholderReset(item) {
      item.height = 20;
      item.virtualHeight = 20;
      item.virtualUnit = "pixel";
      item.poundOrPixel = "pixel";
    },
    // 添加元素事件
    addDom() {
      let copyData = null; // 初始化为null或undefined,根据需要选择
      this.myArray.forEach((item) => {
        if (item.id === this.currentHighlightId) {
          // 深拷贝数据
          copyData = JSON.parse(JSON.stringify(item));
        }
      });
      // 计算是同类别的第几个
      let domNowNumber = 0;
      this.myArray.forEach((item) => {
        if (item.name.includes(copyData.name)) {
          domNowNumber += 1;
        }
      });
      // 赋值id
      copyData.id = this.myArray.length;
      // 将需要拷贝的数据的text字split一下 
      let textArr = copyData.text.split(""); // 假如新增了三条分割线(到分割线3了)那得到["分","割","线","3"]
      let isNaN = textArr[textArr.length - 1] * 1; // 判断最后得到的该项是否为数字类型
      if (isNaN) {
        // 去掉最后的数字然后在后边继续追加 += 1  那么得到了"分割线4"的一个text(数据里的text字渲染在中间的drag列操作上,所以需要看到是第几个)
        copyData.text = `${copyData.text
          .split("")
          .slice(0, -1)
          .join("")}${domNowNumber}`;
        // 新增项的class名称也追加 1 这里为字符串的追加 例如 "分割线1"、"分割线11"、"分割线111"
        copyData.class = `${copyData.class}${domNowNumber}`;
      } else {
        copyData.text = `${copyData.text}${domNowNumber}`;
        copyData.class = `${copyData.class}${domNowNumber}`;
      }
      // 新元素的显示状态为true
      copyData.conceal = true;
      // 推入渲染数组
      this.myArray.push(copyData);
      // 如果显示状态是true显示,且对齐方式为分散对齐,且除了name里含"编制单位"的,调用分散对齐
      if (
        copyData.conceal === true &&
        copyData.textAlign === "justify" &&
        !copyData.name.includes("编制单位")
      ) {
        this.disperseAlign(copyData);
      }
    },
  },
};
</script>
<style lang="less" scoped>
.report-list-container {
  font-family: Microsoft YaHei;
  font-weight: 400;
  .page-title {
    height: 19px;
    font-size: 14px;
    font-family: Microsoft YaHei;
    font-weight: 400;
    line-height: 19px;
    color: #9ba0ad;
    margin-bottom: 20px;
    .self {
      color: #0c254c;
    }
  }
  .content-mode {
    display: flex;
    justify-content: center;
    height: calc(100% - 40px);
    box-sizing: border-box;
    background: #ffffff;
    .item {
      padding: 6px;
      box-sizing: border-box;
      background-color: #fdfdfd;
      border: solid 1px #eee;
      margin-bottom: 10px;
      cursor: move;
    }
    .chosen {
      border: solid 2px #3089dc !important;
    }
    .left-preview {
      margin-right: 15px;
      overflow-y: scroll;
      padding: 16px;
      width: 640px;
      border: 1px solid #ccc;
      box-sizing: border-box;
      .hr {
        width: 100%;
        // height: 1px;
      }
      h1 {
        > div {
          font-weight: 900;
        }
      }
      /deep/ .son-placeholder {
        border: 1px #ccc dashed;
        margin-bottom: 10px;
      }
    }
    .left-preview::-webkit-scrollbar {
      width: 5px;
      height: 5px;
    }
    .right-edit {
      .clear {
        color: #0079fe;
        cursor: pointer;
      }
      display: flex;
      width: 620px;
      .operation::-webkit-scrollbar {
        width: 5px;
        height: 5px;
      }
      .operation {
        > div > div > div {
          > div {
            margin-bottom: 10px;
            span {
              display: inline-block;
              width: 90px;
            }
          }
        }
        box-sizing: border-box;
        overflow: auto;
        .oneself-right {
          margin-left: auto;
          > div {
            margin-top: 0;
          }
          .clear {
            display: flex;
            margin-right: 20px;
            align-items: center;
          }
          .el-color-picker
            .el-color-picker__trigger
            .el-icon-arrow-down
            .el-color-picker__icon {
            font-size: 0;
          }
          .color-select::after {
            border: 1px solid red;
          }
        }
        .upLineWidth {
          .size-mark {
            /deep/ input {
              border-radius: 4px 0 0 4px;
              height: 36px;
              box-sizing: border-box;
            }
          }
          /deep/ input {
            border-radius: 0 4px 4px 0;
            height: 36px;
            box-sizing: border-box;
          }
        }
        .operating-area {
          width: 440px;
          padding: 16px;
          box-sizing: border-box;
        }
        .font,
        .font-size,
        .font-color,
        .font-width,
        .text-align,
        .hrColor {
          display: flex;
          align-items: center;
          span {
            font-size: 13px;
          }
        }
        /deep/ .text-align .el-radio-button--small .el-radio-button__inner {
          width: 46px;
          display: flex;
          justify-content: center;
          align-items: center;
          height: 36px;
          padding: 2px;
        }
        .text-align i::before {
          font-size: 28px;
        }
        .font {
          .el-select {
            width: 155px;
          }
        }
        .font-size {
          .size-mark {
            width: 95px;
          }
          .size-pound {
            width: 60px;
            .select-pound {
              padding-right: 0;
            }
          }
        }
        .label {
          width: 88px;
          color: #aaaaaa;
          font-size: 13px;
        }
      }
      .drag {
        width: 180px;
        font-size: 14px;
        overflow-y: scroll;
        > span > div {
          box-sizing: border-box;
          border-width: 1px;
          border-style: solid;
          border-color: rgba(233, 233, 233, 1);
          border-top: 0;
          border-bottom: 0;
        }
        .highlight {
          background: inherit;
          background-color: rgba(236, 245, 255, 1);
        }
      }
      .drag::-webkit-scrollbar {
        width: 5px;
        height: 5px;
      }
      .edit-title {
        box-sizing: border-box;
        height: 50px;
        line-height: 50px;
        padding-left: 20px;
        background-color: rgba(249, 249, 249, 1);
      }
      .item {
        margin: 0;
        height: 50px;
        line-height: 50px;
        background: none;
        border: none;
        border-width: 1px;
        border-style: solid;
        border-color: rgba(233, 233, 233, 1);
        border-top: 0;
        padding: 2px 2px 2px 40px;
        box-sizing: border-box;
      }
    }
  }
}
</style>