| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037 | <template>  <main    ref="canvas"    class="canvas"    :style="[      {        backgroundImage: data.background_image_url ? `url(${data.background_image_url})` : '',        backgroundSize: data.background_image_url          ? `${data.background_position.width}% ${data.background_position.height}%`          : '',        backgroundPosition: data.background_image_url          ? `${data.background_position.left}% ${data.background_position.top}%`          : '',      },    ]"  >    <template v-if="isEdit">      <span class="drag-line" data-row="-1"></span>      <!-- 行 -->      <template v-for="(row, i) in data.row_list">        <div :key="i" class="row" :style="computedRowStyle(i)">          <!-- 列 -->          <template v-for="(col, j) in row.col_list">            <span              v-if="j === 0"              :key="`start-${i}-${j}`"              class="drag-vertical-line col-start"              :data-row="i"              :data-col="j"            ></span>            <div :key="j" :class="['col', `col-${i}-${j}`]" :style="computedColStyle(col)">              <!-- 网格 -->              <template v-for="(grid, k) in col.grid_list">                <span                  v-if="k === 0"                  :key="`start-${i}-${j}-${k}`"                  class="drag-line grid-line drag-row"                  :style="{ gridArea: 'grid-top' }"                  :data-row="i"                  :data-col="j"                  :data-grid="k"                  data-type="row"                ></span>                <span                  v-if="grid.row > 1 && grid.row !== col.grid_list[k - 1].row"                  :key="`middle-${i}-${j}-${k}`"                  :style="{ gridArea: `middle-${grid.grid_area}` }"                  :data-row="i"                  :data-col="j"                  :data-grid="k"                  data-type="col-middle"                  class="drag-line grid-line"                ></span>                <span                  :key="`left-${i}-${j}-${k}`"                  :style="{ gridArea: `left-${grid.grid_area}` }"                  :data-row="i"                  :data-col="j"                  :data-grid="k"                  data-type="col-left"                  class="drag-vertical-line grid-line grid-line-left"                ></span>                <component                  :is="componentList[grid.type]"                  :id="grid.id"                  ref="component"                  :key="`grid-${grid.id}`"                  :class="[grid.id]"                  :style="{ gridArea: grid.grid_area, height: grid.height, marginTop: grid.row !== 1 ? '16px' : '0' }"                  :delete-component="deleteComponent(i, j, k)"                  :component-move="componentMove(i, j, k)"                  @showSetting="showSetting"                  @changeData="changeData"                />                <span                  :key="`right-${i}-${j}-${k}`"                  :style="{ gridArea: `right-${grid.grid_area}` }"                  :data-row="i"                  :data-col="j"                  :data-grid="k + 1"                  data-type="col-right"                  class="drag-vertical-line grid-line grid-line-right"                ></span>                <span                  v-if="k === col.grid_list.length - 1"                  :key="`end-${i}-${j}-${k}`"                  class="drag-line grid-line drag-row"                  :style="{ gridArea: `grid-bottom` }"                  :data-row="i"                  :data-col="j"                  :data-grid="k + 1"                  data-type="row"                ></span>              </template>            </div>            <span :key="`end-${i}-${j}`" class="drag-vertical-line col-end" :data-row="i" :data-col="j + 1"></span>          </template>        </div>        <span v-if="i < data.row_list.length - 1" :key="`row-${i}`" class="drag-line" :data-row="i"></span>      </template>      <span class="drag-line" :data-row="data.row_list.length - 1"></span>    </template>    <PreviewEdit v-else :courseware-id="courseware_id" :row-list="data.row_list" @computedMoveData="computedMoveData" />  </main></template><script>import { getRandomNumber } from '@/utils/index';import { componentList } from '../../data/bookType';import { ContentSaveCoursewareContent, ContentGetCoursewareContent } from '@/api/book';import PreviewEdit from './PreviewEdit.vue';export default {  name: 'CreateCanvas',  components: {    PreviewEdit,  },  inject: ['getCurSettingId'],  props: {    isEdit: {      type: Boolean,      required: true,    },  },  data() {    const { book_id, chapter_id } = this.$route.query;    return {      courseware_id: this.$route.params.courseware_id,      book_id,      chapter_id,      data: {        background_image_url: '',        background_position: {          width: 100,          height: 100,          top: 0,          left: 0,        },        // 组件列表        row_list: [],      },      curType: 'divider',      componentList,      curRow: -2,      curCol: -1,      curGrid: -1,      gridInsertType: '', // 网格插入类型      enterCanvas: false, // 是否进入画布      // 拖拽状态      drag: {        clientX: 0,        clientY: 0,        dragging: false,      },    };  },  watch: {    drag: {      handler(val) {        if (val.dragging) {          const dragging = document.querySelector('.canvas-dragging');          dragging.style.left = `${val.clientX}px`;          dragging.style.top = `${val.clientY}px`;        }      },      deep: true,    },    enterCanvas: {      handler(val) {        if (val) return;        if (!this.isEdit) return;        const dragLineList = document.querySelectorAll('.drag-line');        dragLineList.forEach((item) => {          item.style.opacity = 0;        });        this.curRow = -2;        const dragVerticalLineList = document.querySelectorAll('.drag-vertical-line');        dragVerticalLineList.forEach((item) => {          item.style.opacity = 0;        });        this.curCol = -1;        this.curGrid = -1;        this.gridInsertType = '';      },    },    'data.row_list': {      handler(val) {        // row_list 更改时判断是否有当前设置 id 的组件        const curSettingId = this.getCurSettingId();        const find = val.find((row) => {          return row.col_list.find((col) => {            return col.grid_list.find((grid) => grid.id === curSettingId);          });        });        if (!find) {          this.$emit('showSettingEmpty');        }      },      immediate: true,    },  },  created() {    ContentGetCoursewareContent({ id: this.courseware_id }).then(({ content }) => {      if (content) {        this.data = JSON.parse(content);      }      this.$watch(        'data',        () => {          this.changeData();        },        {          deep: true,        },      );    });  },  mounted() {    document.addEventListener('mousemove', this.dragMove);    document.addEventListener('mouseup', this.dragEnd);  },  beforeDestroy() {    document.removeEventListener('mousemove', this.dragMove);    document.removeEventListener('mouseup', this.dragEnd);  },  methods: {    changeData() {      this.$emit('changeData');    },    /**     * 保存课件内容     * @param {string} type 类型     */    saveCoursewareContent(type) {      let component_id_list = this.data.row_list.flatMap((row) =>        row.col_list.flatMap((col) => col.grid_list.map((grid) => grid.id)),      );      this.$refs?.component.forEach((item) => {        item.saveCoursewareComponentContent();      });      const loading = this.$loading({        lock: true,        text: '保存中...',        spinner: 'el-icon-loading',        background: 'rgba(0, 0, 0, 0.7)',      });      ContentSaveCoursewareContent({        id: this.courseware_id,        category: 'NEW',        content: JSON.stringify(this.data),        component_id_list,      }).then(() => {        this.$message.success('保存成功');        loading.close();        if (type === 'quit') {          this.$emit('back');        }        if (type === 'edit') {          this.$emit('changeEditStatus');        }      });    },    setBackgroundImage(url, position) {      this.data.background_image_url = url;      this.data.background_position = position;      this.$message.success('设置背景图成功');    },    /**     * 显示设置     * @param {object} setting     * @param {string} type     * @param {string} id     * @param {object} params     */    showSetting(setting, type, id, params = {}) {      this.$emit('showSetting', setting, type, id, params);    },    /**     * 计算组件移动     * @param {number} i 行     * @param {number} j 列     * @param {number} k 格子     */    componentMove(i, j, k) {      return ({ type, offsetX, offsetY, id }) => {        this.computedMoveData({ i, j, k, type, offsetX, offsetY, id });      };    },    /**     * 计算移动数据     * @param {number} i 行     * @param {number} j 列     * @param {number} k 格子     * @param {string} type 移动类型     * @param {number} offsetX x 轴偏移量     * @param {number} offsetY y 轴偏移量     * @param {string} id 组件 id     */    computedMoveData({ i, j, k, type, offsetX, offsetY, id, min_width, min_height, row_width }) {      const row = this.data.row_list[i];      const col = row.col_list[j];      const grid = col.grid_list[k];      // 上下移动      if (['top', 'bottom'].includes(type)) {        this.handleVerticalMove(col, grid, offsetY, id, min_height);        return;      }      // 一行中有多个格子      let gridList = col.grid_list.filter((item) => item.row === grid.row);      if (gridList.length > 1) {        let find = gridList.findIndex((item) => item.id === id); // 当前 id 所在格子索引        // 移动类型为 left 且不是第一个格子        if (type === 'left' && find > 0) {          this.handleMultGridLeftMoveNotFirstGrid({ gridList, grid, col, find, k, offsetX, min_width, row_width });        }        // 移动类型为 right 且不是最后一个格子        if (type === 'right' && find < gridList.length - 1) {          this.handleMultGridRightMoveNotFirstGrid({ gridList, grid, col, find, k, offsetX, min_width, row_width });        }        return;      }      // 移动类型为 left 且不是第一个格子      if (type === 'left' && j > 0) {        this.handleLeftMoveNotFirstGrid(row, j, offsetX, min_width, row_width);      }      // 移动类型为 right 且不是最后一个格子      if (type === 'right' && j < row.col_list.length - 1) {        this.handleRightMoveNotLastGrid(row, j, offsetX, min_width, row_width);      }      this.$forceUpdate();    },    /**     * 处理垂直移动     * @param {number} col 列数据     * @param {object} grid 格子数据     * @param {number} offsetY y 轴偏移量     * @param {string} id 组件 id     * @param {number} min_height 最小高度     */    handleVerticalMove(col, grid, offsetY, id, min_height) {      // 高度为 auto 时      if (grid.height === 'auto') {        const gridHeight = document.querySelector(`.${id}`).offsetHeight;        const height = gridHeight + offsetY;        if (height < min_height) {          return;        }        grid.height = `${gridHeight + offsetY}px`;        col.grid_template_rows = `0 ${col.grid_list.map(({ height }) => height).join(' 16px ')} 0`;        return;      }      // 高度为数字时      const height = Number(grid.height.replace('px', ''));      const _h = height + offsetY;      if (_h < min_height) {        return;      }      if (_h >= 50) {        grid.height = `${_h}px`;        col.grid_template_rows = `0 ${col.grid_list.map(({ height }) => height).join(' 16px ')} 0`;      }    },    /**     * 处理左移且不是第一个格子     * @param {object} row 行数据     * @param {number} j 第几列     * @param {number} offsetX x 轴偏移量     * @param {number} min_width 最小宽度     * @param {number} row_width 行宽度     */    handleLeftMoveNotFirstGrid(row, j, offsetX, min_width, row_width) {      const prevGrid = row.width_list[j - 1];      const prevWidth = Number(prevGrid.replace('fr', ''));      const width = Number(row.width_list[j].replace('fr', ''));      const max = prevWidth + width - 10;      if (prevWidth + offsetX < 10 || prevWidth + offsetX > max || width - offsetX > max || width - offsetX < 10) {        return;      }      // 计算拖动的距离与总宽度的比例      const ratio = (offsetX / document.querySelector('.row').offsetWidth) * 100;      const _w = width - ratio;      if ((_w / 100) * row_width < min_width) {        return;      }      row.width_list[j - 1] = `${prevWidth + ratio}fr`;      row.width_list[j] = `${width - ratio}fr`;    },    /**     * 处理一行中有多个格子的左移且不是第一个格子     * @param {object} gridList 格子列表     * @param {object} grid 格子数据     * @param {object} col 列数据     * @param {number} find 当前 id 所在格子索引     * @param {number} k 格子的索引     * @param {number} offsetX x 轴偏移量     * @param {number} min_width 最小宽度     */    handleMultGridLeftMoveNotFirstGrid({ gridList, grid, col, find, k, offsetX, min_width, row_width }) {      const prevGrid = gridList[find - 1];      const prevWidth = Number(prevGrid.width.replace('fr', ''));      const width = Number(grid.width.replace('fr', ''));      const max = prevWidth + width - 10;      if (prevWidth + offsetX < 10 || prevWidth + offsetX > max || width - offsetX > max || width - offsetX < 10) {        return;      }      // 计算拖动的距离与总宽度的比例      const ratio = (offsetX / document.querySelector('.row').offsetWidth) * 100;      const _w = width - ratio;      if ((_w / 100) * row_width < min_width) {        return;      }      col.grid_list[k - 1].width = `${prevWidth + ratio}fr`;      grid.width = `${_w}fr`;    },    /**     * 处理右移且不是最后一个格子     * @param {object} row 行数据     * @param {number} j 第几列     * @param {number} offsetX x 轴偏移量     * @param {number} min_width 最小宽度     * @param {number} row_width 行宽度     */    handleRightMoveNotLastGrid(row, j, offsetX, min_width, row_width) {      let nextGrid = row.width_list[j + 1];      const nextWidth = Number(nextGrid.replace('fr', ''));      const width = Number(row.width_list[j].replace('fr', ''));      const max = nextWidth + width - 10;      if (nextWidth - offsetX < 10 || nextWidth - offsetX > max || width + offsetX > max || width + offsetX < 10) {        return;      }      const ratio = (offsetX / document.querySelector('.row').offsetWidth) * 100;      const _w = width + ratio;      if ((_w / 100) * row_width < min_width) {        return;      }      row.width_list[j + 1] = `${nextWidth - ratio}fr`;      row.width_list[j] = `${width + ratio}fr`;    },    /**     * 处理一行中有多个格子的右移且不是最后一个格子     * @param {object} gridList 格子列表     * @param {object} grid 格子数据     * @param {object} col 列数据     * @param {number} find 当前 id 所在格子索引     * @param {number} k 格子的索引     * @param {number} offsetX x 轴偏移量     * @param {number} min_width 最小宽度     */    handleMultGridRightMoveNotFirstGrid({ gridList, grid, col, find, k, offsetX, min_width, row_width }) {      let nextGrid = gridList[find + 1];      const nextWidth = Number(nextGrid.width.replace('fr', ''));      const width = Number(grid.width.replace('fr', ''));      const max = nextWidth + width - 10;      if (nextWidth - offsetX < 10 || nextWidth - offsetX > max || width + offsetX > max || width + offsetX < 10) {        return;      }      const ratio = (offsetX / document.querySelector('.row').offsetWidth) * 100;      const _w = width + ratio;      if ((_w / 100) * row_width < min_width) {        return;      }      col.grid_list[k + 1].width = `${nextWidth - ratio}fr`;      grid.width = `${width + ratio}fr`;    },    /**     * 重新计算格子宽度     * @param {number} i 行     * @param {number} j 列     */    recalculateGridWidth(i, j) {      let col = this.data.row_list[i].col_list[j];      let grid_template_columns = '0';      col.grid_list.forEach(({ width }, i) => {        const w = `${width}`;        if (i === col.grid_list.length - 1) {          grid_template_columns += ` ${w} 0`;        } else {          grid_template_columns += ` ${w} `;        }      });      col.grid_template_columns = grid_template_columns;    },    /**     * 删除组件     * @param {number} i 行     * @param {number} j 列     * @param {number} k 格子     */    deleteComponent(i, j, k) {      return () => {        const gridList = this.data.row_list[i].col_list[j].grid_list;        let delRow = gridList[k].row; // 删除的 grid 的 row        let delW = gridList[k].width; // 删除的 grid 的 width        gridList.splice(k, 1);        // 如果删除后没有 grid 了则删除列        const colList = this.data.row_list[i].col_list[j];        if (colList.grid_list.length === 0) {          this.data.row_list[i].col_list.splice(j, 1);          let width_list = this.data.row_list[i].width_list;          const delW = width_list[j];          width_list.splice(j, 1);          this.data.row_list[i].width_list = width_list.map((item) => {            return `${Number(item.replace('fr', '')) + Number(delW.replace('fr', '') / width_list.length)}fr`;          });        }        // 如果删除后没有列了则删除行        if (this.data.row_list[i].col_list.length === 0) {          this.data.row_list.splice(i, 1);        }        // 如果删除后还有 grid 则重新计算 grid 的 row 和 width        if (gridList?.length > 0) {          let delNum = gridList.filter(({ row }) => row === delRow).length;          let diff = Number(delW.replace('fr', '')) / delNum;          if (delNum === 0) {            // 删除 grid 后面的 row 都减 1            gridList.forEach((item) => {              if (item.row > delRow) {                item.row -= 1;              }            });          } else {            gridList.forEach((item) => {              if (item.row === delRow) {                item.width = `${Number(item.width.replace('fr', '')) + diff}fr`;              }            });          }        }      };    },    computedColStyle(col) {      let grid = col.grid_list;      let maxCol = 0; // 最大列数      let rowList = new Map();      grid.forEach(({ row }) => {        rowList.set(row, (rowList.get(row) || 0) + 1);      });      let curMaxRow = 0; // 当前列数最多的 row 的值      rowList.forEach((value, key) => {        if (value > maxCol) {          maxCol = value;          curMaxRow = key;        }      });      // 计算 grid_template_areas      let gridStr = '';      let gridArr = [];      let gridRowArr = []; // 存储不同 row 中间的 line      grid.forEach(({ grid_area, row }, i) => {        // 如果 gridArr[row - 1] 不存在则创建        if (!gridArr[row - 1]) {          gridArr[row - 1] = [];        }        if (row > 1 && grid[i - 1].row !== row) {          gridRowArr.push({            row: row - 1,            str: `middle-${grid_area} `.repeat(maxCol * 3),          });        }        // 如果当前 row 是最大列数的 row        if (curMaxRow === row) {          gridArr[row - 1].push(`left-${grid_area} ${grid_area} right-${grid_area}`);        } else {          let filter = grid.filter((item) => item.row === row);          let find = filter.findIndex((item) => item.grid_area === grid_area);          let needNum = (maxCol - filter.length) * 3; // 需要的数量          let str = '';          if (filter.length === 1) {            str = ` ${grid_area} `.repeat(needNum + 1);          } else {            let arr = this.splitInteger(needNum, filter.length);            str = arr[find] === 0 ? ` ${grid_area} ` : ` ${grid_area} `.repeat(arr[find] + 1);          }          gridArr[row - 1].push(`left-${grid_area} ${str} right-${grid_area}`);        }      });      gridArr.forEach((item, i) => {        let find = gridRowArr.find((row) => row.row === i);        if (find) {          gridStr += `'${find.str}' `;        }        gridStr += `'${item.join(' ')}' `;      });      // 计算 grid_template_columns      let gridTemCols = '';      let max = { row: 0, num: 0 };      grid.forEach(({ row }) => {        // 计算出 row 的哪个值最多        let len = grid.filter((item) => item.row === row).length;        if (max.num < len) {          max.num = len;          max.row = row;        }      });      grid.forEach((item) => {        if (item.row === max.row) {          gridTemCols += `${item.width} 8px 8px `;        }      });      // 计算 grid_template_rows      let gridTemplateRows = '';      // 将 grid 按照 row 分组      let gridMap = new Map();      grid.forEach((item) => {        if (!gridMap.has(item.row)) {          gridMap.set(item.row, []);        }        gridMap.get(item.row).push(item.height);      });      gridMap.forEach((value, i) => {        if (i > 1) {          gridTemplateRows += '4px ';        }        if (value.length === 1) {          gridTemplateRows += `${value[0]} `;        } else {          let isAllAuto = value.every((item) => item === 'auto'); // 是否全是 auto          gridTemplateRows += isAllAuto ? 'auto ' : `max(${value.join(', ')}) `;        }      });      return {        width: col.width,        gridTemplateAreas: `'${'grid-top '.repeat(maxCol * 3)}' ${gridStr} '${'grid-bottom '.repeat(maxCol * 3)}'`,        gridTemplateColumns: `0 ${gridTemCols.slice(0, gridTemCols.length - 8)} 0`,        gridTemplateRows: `0 ${gridTemplateRows} 0`,      };    },    /**     * 分割整数为多个 3 的倍数     * @param {number} num     * @param {number} parts     */    splitInteger(num, parts) {      let base = Math.floor(num / parts / 3) * 3;      let arr = Array(parts).fill(base);      let remainder = num - base * parts;      for (let i = 0; remainder > 0; i = (i + 1) % parts) {        arr[i] += 3;        remainder -= 3;      }      return arr;    },    /**     * 拖拽开始     * 用点击模拟拖拽     * @param {MouseEvent} event     * @param {string} type     */    dragStart(event, type) {      // 获取鼠标位置      const { clientX, clientY } = event;      document.body.style.userSelect = 'none'; // 禁止选中文本      this.drag.dragging = true;      this.curType = type;      // 在鼠标位置创建一个拖拽元素      const dragging = document.createElement('div');      dragging.className = 'canvas-dragging';      this.drag.clientX = clientX;      this.drag.clientY = clientY;      document.body.appendChild(dragging);    },    /**     * 鼠标移动     */    dragMove(event) {      if (!this.drag.dragging) return;      const { clientX, clientY } = event;      this.drag.clientX = clientX;      this.drag.clientY = clientY;      if (!this.isEdit) return; // 非编辑状态不允许显隐线      let { isInsideCanvas } = this.getMarginDifferences();      this.enterCanvas = isInsideCanvas;      if (!isInsideCanvas) return;      this.showRecentLine(clientX, clientY);    },    /**     * 显示最近的线     * @param {number} clientX     * @param {number} clientY     */    showRecentLine(clientX, clientY) {      const dragLineList = document.querySelectorAll('.drag-line'); // 获取所有的横线      const dragVerticalLineList = document.querySelectorAll('.drag-vertical-line'); // 获取所有的竖线      let minDistance = Infinity;      let minIndex = -1;      const list = [...dragLineList, ...dragVerticalLineList];      list.forEach((item, index) => {        const rect = item.getBoundingClientRect();        const distance = Math.sqrt(          Math.pow(clientX - rect.left - rect.width / 2, 2) + Math.pow(clientY - rect.top - rect.height / 2, 2),        );        if (distance < minDistance) {          minDistance = distance;          minIndex = index;        }      });      list.forEach((item, index) => {        if (index === minIndex) {          this.curRow = Number(item.getAttribute('data-row'));          this.curCol = Number(item.getAttribute('data-col') || -1);          this.curGrid = Number(item.getAttribute('data-grid') || -1);          this.gridInsertType = item.getAttribute('data-type') || '';          item.style.opacity = 1;        } else {          item.style.opacity = 0;        }      });    },    /**     * 鼠标松开     */    dragEnd() {      document.body.style.userSelect = 'auto';      const dragging = document.querySelector('.canvas-dragging');      if (dragging) {        document.body.removeChild(dragging);        this.drag.dragging = false;      }      if (!this.isEdit) return;      if (this.enterCanvas) {        if (this.curRow >= -1 && this.curCol <= -1) {          this.calculateRowInsertedObject();        }        if (this.curRow >= -1 && this.curCol > -1 && this.curGrid <= -1) {          this.calculateColObject();        }        if (this.curRow >= -1 && this.curCol > -1 && this.curGrid > -1) {          this.calculateGridObject();        }      }      this.enterCanvas = false;    },    computedRowStyle(i) {      let row = this.data.row_list[i];      let col = row.col_list;      if (col.length <= 1) {        return {          gridTemplateColumns: '0 100fr 0',        };      }      let str = row.width_list        .map((item) => {          return `${item}`;        })        .join(' 16px ');      let gridTemplateColumns = `0 ${str} 0`;      return {        gridAutoFlow: 'column',        gridTemplateColumns,        gridTemplateRows: 'auto',      };    },    /**     * 计算网格插入的对象     */    calculateGridObject() {      const id = `ID-${getRandomNumber(12, true)}`;      const letter = `L${getRandomNumber(6, true)}`;      let row = this.data.row_list[this.curRow];      let col = row.col_list[this.curCol];      let grid = col.grid_list;      let type = this.gridInsertType;      if (['row', 'col-middle'].includes(type)) {        let rowNum = this.curGrid === 0 ? 1 : grid[this.curGrid - 1].row + 1;        grid.splice(this.curGrid, 0, {          id,          grid_area: letter,          width: '100fr',          height: 'auto',          row: rowNum,          type: this.curType,        });        // 在新加入的 grid 后面的 row 都加 1        grid.forEach((item, i) => {          if (i > this.curGrid) {            item.row += 1;          }        });      }      if (['col-left', 'col-right'].includes(type)) {        let rowNum = grid[type === 'col-left' ? this.curGrid : this.curGrid - 1].row;        grid.splice(this.curGrid, 0, {          id,          grid_area: letter,          width: '100fr',          height: 'auto',          row: rowNum,          type: this.curType,        });        let allRowNum = grid.filter(({ row }) => row === rowNum).length;        let w = 0;        grid.forEach((item, i) => {          if (item.row === rowNum && i !== this.curGrid) {            let width = Number(item.width.replace('fr', ''));            let diff = width / allRowNum;            item.width = `${width - diff}fr`;            w += diff;          }        });        grid[this.curGrid].width = `${w}fr`;      }    },    /**     * 计算列插入的对象     */    calculateColObject() {      const id = `ID-${getRandomNumber(12, true)}`;      const letter = `L${getRandomNumber(6, true)}`;      let row = this.data.row_list[this.curRow];      let col = row.col_list;      let w = 0;      row.width_list.forEach((item, i) => {        let itemW = Number(item.replace('fr', ''));        let rowW = itemW / (row.width_list.length + 1);        w += rowW;        row.width_list[i] = `${itemW - rowW}fr`;      });      row.width_list.splice(this.curCol, 0, `${w}fr`);      col.splice(this.curCol, 0, {        width: '100fr',        height: 'auto',        grid_list: [          {            id,            grid_area: letter,            row: 1,            width: '100fr',            height: 'auto',            type: this.curType,          },        ],      });    },    /**     * 计算行插入的对象     */    calculateRowInsertedObject() {      const id = `ID-${getRandomNumber(12, true)}`;      const letter = `L${getRandomNumber(6, true)}`;      this.data.row_list.splice(this.curRow + 1, 0, {        width_list: ['100fr'],        col_list: [          {            width: '100fr',            height: 'auto',            grid_list: [              {                id,                grid_area: letter,                width: '100fr',                height: 'auto',                row: 1,                type: this.curType,              },            ],          },        ],      });    },    /**     * 获取拖拽元素和画布的边距差值     * @returns {object} { leftMarginDifference, topMarginDifference, isInsideCanvas }     * leftMarginDifference: 拖拽元素和画布左边距差值     * topMarginDifference: 拖拽元素和画布上边距差值     * isInsideCanvas: 是否在画布内     */    getMarginDifferences() {      const rect1 = document.querySelector('.canvas-dragging').getBoundingClientRect();      const rect2 = this.$refs.canvas.getBoundingClientRect();      const leftMarginDifference = rect1.left - rect2.left + 128;      const topMarginDifference = rect1.top - rect2.top + 72;      let isInsideCanvas =        leftMarginDifference > 0 &&        leftMarginDifference < rect2.width &&        topMarginDifference > 0 &&        topMarginDifference < rect2.height;      return { leftMarginDifference, topMarginDifference, isInsideCanvas };    },    // 获取子组件    findChildComponentByKey(key) {      return this.$children.find((child) => child.$vnode.key === key);    },  },};</script><style lang="scss" scoped>.canvas {  display: flex;  flex-direction: column;  row-gap: 6px;  width: $courseware-width;  min-height: calc(100% - 6px);  padding: 24px;  margin: 0 auto;  background-color: #fff;  background-repeat: no-repeat;  border-radius: 4px;  .row {    display: grid;    row-gap: 16px;    > .drag-vertical-line:not(:first-child, :last-child, .grid-line) {      left: 6px;    }    .drag-vertical-line.col-start {      left: -12px;    }    .drag-vertical-line.col-end {      right: -8px;    }    .col {      display: grid;      .drag-vertical-line:first-child {        left: -8px;      }      .drag-vertical-line:not(:first-child, :last-child, .grid-line) {        left: 6px;      }      .drag-vertical-line:last-child {        right: -4px;      }    }  }  .drag-line {    z-index: 2;    width: calc(100% - 16px);    height: 4px;    margin: 0 8px;    background-color: #379fff;    border-radius: 4px;    opacity: 0;    &.drag-row {      background-color: $right-color;    }    &.grid-line:not(:first-child, :last-child) {      position: relative;      top: 6px;    }    &.grid-line {      background-color: #f43;    }  }  .drag-vertical-line {    position: relative;    z-index: 2;    width: 4px;    height: 100%;    background-color: #379fff;    border-radius: 4px;    opacity: 0;    &.grid-line {      background-color: #f43;    }  }}</style><style lang="scss">.canvas-dragging {  position: fixed;  z-index: 999;  width: 320px;  height: 180px;  background-color: #eaf5ff;  border: 1px solid #b5dbff;  border-radius: 4px;  opacity: 0.5;  transform: translate(-40%, -40%);}</style>
 |