<template>
  <main ref="canvas" class="canvas">
    <div v-if="isEdit" class="edit">
      <div v-for="item in lineList" :key="item[0]" class="group-line" :style="computedGroupLine(item)"></div>
      <span class="drag-line" data-row="-1"></span>
      <!-- 行 -->
      <template v-for="(row, i) in data.row_list">
        <div :key="row.row_id" class="row" :style="computedRowStyle(i)">
          <el-checkbox
            v-if="row?.row_id"
            v-model="rowCheckList[row.row_id]"
            :class="['row-checkbox', `${row.row_id}`]"
          />

          <!-- 列 -->
          <template v-for="(col, j) in row.col_list">
            <span
              v-if="j === 0"
              :key="`start-${row.row_id}-${col.col_id}`"
              class="drag-vertical-line col-start"
              :data-row="i"
              :data-col="j"
            ></span>
            <div :key="col.col_id" :class="['col', `col-${i}-${j}`]" :style="computedColStyle(col)">
              <!-- 网格 -->
              <template v-for="(grid, k) in col.grid_list">
                <span
                  v-if="k === 0"
                  :key="`start-${grid.id}`"
                  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-${grid.id}`"
                  :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-${grid.id}`"
                  :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}`"
                  :old-id="grid?.oldId"
                  :class="[grid.id]"
                  :data-row="i"
                  :data-col="j"
                  :data-grid="k"
                  :data-view-order="computedGridViewOrder(grid.id)"
                  :border-color="computedBorderColor(row.row_id)"
                  :style="computedGridStyle(grid, row.row_id)"
                  :component-move="componentMove(i, j, k)"
                  @deleteComponent="deleteComponentConfirm"
                  @showSetting="showSetting"
                  @copyComponent="copyComponent"
                  @changeData="changeData"
                />
                <span
                  :key="`right-${grid.id}`"
                  :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-${grid.id}`"
                  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-${row.row_id}-${col.col_id}`"
              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-${row.row_id}`" class="drag-line" :data-row="i"></span>
      </template>
      <span class="drag-line" :data-row="data.row_list.length - 1"></span>
    </div>

    <PreviewEdit
      v-else
      ref="previewEdit"
      :courseware-id="courseware_id"
      :row-list="data.row_list"
      @computedMoveData="computedMoveData"
      @handleHeightChange="handleHeightChange"
    />
  </main>
</template>

<script>
import { getRandomNumber } from '@/utils/index';
import { componentList } from '../../data/bookType';
import {
  ContentSaveCoursewareContent,
  ContentGetCoursewareContent,
  GetBookUnifiedAttrib,
  ContentSaveCoursewareComponentContent,
} from '@/api/book';
import _ from 'lodash';
import { unified_attrib } from '@/common/data';

import PreviewEdit from './PreviewEdit.vue';

export default {
  name: 'CreateCanvas',
  components: {
    PreviewEdit,
  },
  inject: ['getCurSettingId'],
  provide() {
    return {
      getBookUnifiedAttr: () => this.book_unified_attrib,
      getTitleList: () => this.title_list,
      getProjectResourcePopedom: () => this.projectResourcePopedom,
      moveComponentDragStart: this.dragStart,
    };
  },
  props: {
    isEdit: {
      type: Boolean,
      required: true,
    },
  },
  data() {
    const { project_id } = this.$route.query;

    return {
      courseware_id: this.$route.params.courseware_id,
      project_id,
      data: {
        // 组件列表
        row_list: [],
        // 样式调整
        unified_attrib,
      },
      visibleBackground: false, // 背景设置弹窗
      rowCheckList: {}, // 行复选框列表
      content_group_row_list: [], // 行分组id列表
      gridBorderColorList: ['#3fc7cc', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#d863ff', '#724fff'], // 网格边框颜色列表
      curType: 'divider',
      curParams: {}, // 当前组件参数
      scrollInterval: null, // 滚动定时器
      componentList,
      curRow: -2,
      curCol: -1,
      curGrid: -1,
      gridInsertType: '', // 网格插入类型
      enterCanvas: false, // 是否进入画布
      // 拖拽状态
      drag: {
        clientX: 0,
        clientY: 0,
        dragging: false,
      },
      visibleFullTextSettings: false,
      book_unified_attrib: unified_attrib,
      title_list: [],
      curComponentId: '', // 当前选中组件 id
      projectResourcePopedom: [], // 当前编辑人员的项目资源权限
    };
  },
  computed: {
    lineList() {
      let arr = [];
      this.content_group_row_list.forEach(({ row_id, is_pre_same_group }, i) => {
        if (is_pre_same_group) {
          arr.push([this.content_group_row_list[i - 1].row_id, row_id]);
        }
      });
      return arr;
    },
  },
  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');
        }

        this.rowCheckList = Object.fromEntries(val.filter((row) => row?.row_id).map((row) => [row.row_id, false]));

        // 增加新添的行
        val.forEach(({ row_id }, i) => {
          let isHas = this.content_group_row_list.some((group) => group.row_id === row_id);
          if (!isHas) {
            this.content_group_row_list.splice(i, 0, { row_id, is_pre_same_group: false });

            [i - 1, i + 1].forEach((start) => {
              let step = start < i ? -1 : 1;
              for (let j = start; j >= 0 && j < this.content_group_row_list.length; j += step) {
                if (this.content_group_row_list[j].is_pre_same_group) {
                  this.content_group_row_list[j].is_pre_same_group = false;
                } else {
                  break;
                }
              }
            });
          }
        });

        // 过滤掉已经不存在的行，并将第一行的 is_pre_same_group 设置为 false
        this.content_group_row_list = this.content_group_row_list.filter((group) =>
          val.find((row) => row.row_id === group.row_id),
        );
        if (this.content_group_row_list[0]) {
          this.content_group_row_list[0].is_pre_same_group = false;
        }
      },
      immediate: true,
    },
    rowCheckList: {
      handler(val) {
        // 如果同时有两个选中，将选中的挑选出来，并将它们的状态变为 false
        let selectedRowID = [];
        Object.keys(val).forEach((key) => {
          if (val[key]) {
            selectedRowID.push(key);
          }
        });

        if (selectedRowID.length > 1) {
          selectedRowID.forEach((id) => {
            this.rowCheckList[id] = false;
          });
        } else {
          return false;
        }

        let selectIndex = selectedRowID
          .map((id) => this.content_group_row_list.findIndex(({ row_id }) => row_id === id))
          .sort((a, b) => a - b);

        // 只处理选中两行的情况
        if (selectIndex[1] - selectIndex[0] === 1) {
          // 相邻两行，切换分组状态
          this.content_group_row_list[selectIndex[1]].is_pre_same_group =
            !this.content_group_row_list[selectIndex[1]].is_pre_same_group;
        } else {
          // 非相邻，判断中间行是否都已分组
          const allGrouped = this.content_group_row_list
            .slice(selectIndex[0] + 1, selectIndex[1] + 1)
            .every((group) => group.is_pre_same_group);

          for (let i = selectIndex[0] + 1; i <= selectIndex[1]; i++) {
            this.content_group_row_list[i].is_pre_same_group = !allGrouped;
          }
        }
      },
      deep: true,
    },
  },
  created() {
    const loading = this.$loading({
      lock: true,
      text: '组件加载中...',
      spinner: 'el-icon-loading',
    });
    ContentGetCoursewareContent({ id: this.courseware_id })
      .then(({ content, content_group_row_list, title_list, my_project_resource_popedom }) => {
        if (content) {
          let parsedContent = JSON.parse(content);
          if (
            parsedContent &&
            Array.isArray(parsedContent.row_list) &&
            parsedContent.row_list.length > 0 &&
            !parsedContent.row_list[0]?.row_id
          ) {
            this.data = this.normalizeRowColIds(parsedContent);
          } else {
            this.data = parsedContent;
          }
          loading.close();
        }
        if (content_group_row_list) this.content_group_row_list = JSON.parse(content_group_row_list);

        if (title_list) this.title_list = title_list || [];

        this.$watch(
          'data',
          () => {
            this.changeData();
          },
          {
            deep: true,
          },
        );

        this.projectResourcePopedom = my_project_resource_popedom || [];
      })
      .finally(() => {
        loading.close();
      });
    this.getBookUnifiedAttr();
  },
  mounted() {
    document.addEventListener('mousemove', this.dragMove);
    document.addEventListener('mouseup', this.dragEnd);
  },
  beforeDestroy() {
    document.removeEventListener('mousemove', this.dragMove);
    document.removeEventListener('mouseup', this.dragEnd);
  },
  methods: {
    handleHeightChange(id, newHeight) {
      this.data.row_list.forEach((row) => {
        row.col_list.forEach((col) => {
          col.grid_list.forEach((grid) => {
            if (grid.id === id) {
              grid.height = newHeight;
            }
          });
        });
      });
    },
    changeData() {
      this.$emit('changeData');
    },
    fullTextSettings(data) {
      this.$refs.component.forEach((item) => {
        item.updateProperty('view_pinyin', data.view_pinyin);
        item.updateProperty('pinyin_position', data.pinyin_position);
        item.updateRichTextProperty('fontFamily', data.font);
        item.updateRichTextProperty('fontSize', data.font_size);
        item.updateRichTextProperty('lineHeight', data.line_height);
        item.updateRichTextProperty('color', data.text_color);
        item.updateRichTextProperty('align', data.align);

        item.setUnifiedAttr(data);
      });
      this.data.unified_attrib = data;
    },
    applyToSelectedComponents(data) {
      this.$refs.component.forEach((item) => {
        if (item.$refs.base.checked) {
          item.updateProperty('view_pinyin', data.view_pinyin);
          item.updateProperty('pinyin_position', data.pinyin_position);
          item.updateRichTextProperty('fontFamily', data.font);
          item.updateRichTextProperty('fontSize', data.font_size);
          item.updateRichTextProperty('lineHeight', data.line_height);
          item.updateRichTextProperty('color', data.text_color);
          item.updateRichTextProperty('align', data.align);

          item.setUnifiedAttr(data);
        }
      });
    },
    getBookUnifiedAttr() {
      GetBookUnifiedAttrib({ book_id: this.project_id }).then(({ content }) => {
        if (content) {
          this.book_unified_attrib = JSON.parse(content);
        }
      });
    },
    /**
     * 保存课件内容
     * @param {string} type 类型
     */
    async saveCoursewareContent(type) {
      let isAllLoader = false;

      if (this.$refs?.component === undefined || this.$refs?.component.length === 0) {
        isAllLoader = true;
      } else if (this.isEdit) {
        isAllLoader = this.$refs?.component?.every((item) => item.property.isGetContent);
      } else {
        isAllLoader = this.$refs?.previewEdit?.$refs?.preview?.every((item) => item.loader);
      }
      if (!isAllLoader) {
        this.$message.warning('有组件内容未加载完成，请稍后再试');
        return false;
      }

      const loading = this.$loading({
        lock: true,
        text: '保存中...',
        spinner: 'el-icon-loading',
        background: 'rgba(0, 0, 0, 0.7)',
      });

      try {
        // 先等待所有子组件内容保存完成
        if (this.isEdit) {
          const comps = Array.isArray(this.$refs?.component)
            ? this.$refs.component
            : [this.$refs?.component].filter(Boolean);
          await Promise.all(comps.map((item) => item.saveCoursewareComponentContent()));
        }

        // 再收集并保存页面结构
        let component_id_list = this.data.row_list.flatMap((row) =>
          row.col_list.flatMap((col) => col.grid_list.map((grid) => grid.id)),
        );

        let groupIdList = _.cloneDeep(this.content_group_row_list);
        let groupList = [];
        // 通过判断 is_pre_same_group 将组合并
        for (let i = 0; i < groupIdList.length; i++) {
          if (groupIdList[i].is_pre_same_group) {
            groupList[groupList.length - 1].row_id_list.push(groupIdList[i].row_id);
          } else {
            groupList.push({
              name: '',
              row_id_list: [groupIdList[i].row_id],
              component_id_list: [],
            });
          }
        }

        // 通过合并后的分组，获取对应的组件 id 和分组名称
        groupList.forEach(({ row_id_list, component_id_list }, i) => {
          row_id_list.forEach((row_id, j) => {
            let row = this.data.row_list.find((row) => {
              return row.row_id === row_id;
            });
            // 当前行所有组件id列表
            let gridIdList = row.col_list.map((col) => col.grid_list.map((grid) => grid.id)).flat();
            component_id_list.push(...gridIdList);
            // 查找每组第一行中第一个包含 describe、label 或 stem 的组件
            if (j === 0) {
              let findKey = '';
              let findType = '';
              row.col_list.some((col) => {
                const findItem = col.grid_list.find(({ type }) => {
                  return ['describe', 'label', 'stem'].includes(type);
                });
                if (findItem) {
                  findKey = findItem.id;
                  findType = findItem.type;
                  return true;
                }
              });
              let groupName = `组${i + 1}`;

              // 如果有标签类组件，获取对应名称
              if (findKey) {
                let item = this.isEdit
                  ? this.findChildComponentByKey(`grid-${findKey}`)
                  : this.$refs.previewEdit.findChildComponentByKey(`preview-${findKey}`);
                if (['describe', 'stem'].includes(findType)) {
                  groupName = item.data.content.replace(/<[^>]+>/g, '');
                } else if (findType === 'label') {
                  groupName = item.data.dynamicTags.map((tag) => tag.text).join(', ');
                }
              }

              groupList[i].name = groupName;
            }
          });
        });

        await ContentSaveCoursewareContent({
          id: this.courseware_id,
          category: 'NEW',
          content: JSON.stringify(this.data),
          component_id_list,
          content_group_component_list: groupList,
          content_group_row_list: this.content_group_row_list,
        });

        this.$message.success('保存成功');
        if (type === 'quit') {
          this.$emit('back');
        }
        if (type === 'edit') {
          this.$emit('changeEditStatus');
        }
      } finally {
        loading.close();
      }
    },
    /**
     * 显示设置
     * @param {object} setting 组件设置数据
     * @param {string} type 组件类型
     * @param {string} id 组件 id
     * @param {object} params 组件参数
     */
    showSetting(setting, type, id, params = {}) {
      this.curComponentId = id;
      this.$emit('showSetting', setting, type, id, params);
    },
    /**
     * 组件中添加答案或解析
     * @param {'answer'|'analysis'} type 类型
     */
    addAnswerAndAnalysis(type) {
      this.findChildComponentByKey(`grid-${this.curComponentId}`)?.addAnswerAndAnalysis(type);
    },
    /**
     * 计算组件移动
     * @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 = 0) {
      let height = 0;
      const _h = this.isEdit ? grid?.edit_height : grid.height;

      // 高度为 auto 时
      if (_h === 'auto' || _h === undefined) {
        const gridHeight = document.querySelector(`.${id}`).offsetHeight;

        height = gridHeight + offsetY;
      } else {
        // 高度为数字时
        const h = this.isEdit ? grid?.edit_height : grid.height;

        const gridHeight = Number(h?.replace('px', ''));
        height = gridHeight + offsetY;
      }
      // 当高度小于最小高度时，设置为最小高度
      height = Math.max(height, min_height, 50);

      if (this.isEdit) {
        grid.edit_height = `${height}px`;
      } else {
        grid.height = `${height}px`;
      }
    },

    /**
     * 处理左移且不是第一个格子
     * @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;
    },
    async deleteComponentConfirm(id) {
      try {
        await this.$confirm('确定要删除该组件吗？', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning',
        });
        this.deleteComponent(id);
      } catch (err) {
        if (err === 'cancel') {
          this.$message.info('已取消删除');
        }
      }
    },
    /**
     * 删除组件
     * @param {String} id 组件 id
     */
    deleteComponent(id) {
      const attrs = this.findChildComponentByKey(`grid-${id}`)?.$attrs;
      if (!attrs) return;
      const i = Number(attrs['data-row']);
      const j = Number(attrs['data-col']);
      const k = Number(attrs['data-grid']);
      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) {
      const grid = col.grid_list || [];

      // 只分组一次，后续复用，避免在循环中反复 filter/find。
      const rowMap = new Map();
      const rowOrder = [];
      let maxCol = 0;
      let curMaxRow = 0;

      grid.forEach((item) => {
        if (!rowMap.has(item.row)) {
          rowMap.set(item.row, []);
          rowOrder.push(item.row);
        }
        const rowItems = rowMap.get(item.row);
        rowItems.push(item);
        if (rowItems.length > maxCol) {
          maxCol = rowItems.length;
          curMaxRow = item.row;
        }
      });

      // 计算 grid_template_areas
      const areaLines = [];
      rowOrder.forEach((row, index) => {
        const rowItems = rowMap.get(row) || [];
        if (index > 0 && row > 1) {
          areaLines.push(`'${`middle-${rowItems[0].grid_area} `.repeat(maxCol * 3)}'`);
        }

        const rowAreas = [];
        if (row === curMaxRow) {
          rowItems.forEach(({ grid_area }) => {
            rowAreas.push(`left-${grid_area} ${grid_area} right-${grid_area}`);
          });
        } else {
          const needNum = (maxCol - rowItems.length) * 3;
          const spread = rowItems.length > 1 ? this.splitInteger(needNum, rowItems.length) : [];
          rowItems.forEach(({ grid_area }, i) => {
            const repeatNum = rowItems.length === 1 ? needNum + 1 : spread[i] + 1;
            const inner = ` ${grid_area} `.repeat(repeatNum);
            rowAreas.push(`left-${grid_area} ${inner} right-${grid_area}`);
          });
        }
        areaLines.push(`'${rowAreas.join(' ')}'`);
      });

      // 计算 grid_template_columns（使用列数最多的一行作为基准）
      const maxRowItems = rowMap.get(curMaxRow) || [];
      const gridTemCols = maxRowItems.map(({ width }) => width).join(' 4px 4px ');

      // 计算 grid_template_rows
      const rowHeights = rowOrder.map((row) => {
        const heights = (rowMap.get(row) || []).map((item) => item?.edit_height || item.height);
        if (heights.length <= 1) return heights[0] || 'auto';
        const isAllAuto = heights.every((item) => item === 'auto');
        return isAllAuto ? 'auto' : `max(${heights.join(', ')})`;
      });

      return {
        width: col.width,
        gridTemplateAreas: `'${'grid-top '.repeat(maxCol * 3)}' ${areaLines.join(' ')} '${'grid-bottom '.repeat(maxCol * 3)}'`,
        gridTemplateColumns: `0 ${gridTemCols} 0`,
        gridTemplateRows: `0 ${rowHeights.join(' 4px ')} 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 组件类型
     * @param {object} params 组件参数
     */
    dragStart(event, type, params = {}) {
      // 获取鼠标位置
      const { clientX, clientY } = event;
      document.body.style.userSelect = 'none'; // 禁止选中文本
      this.drag.dragging = true;
      this.curType = type;
      this.curParams = params;
      // 在鼠标位置创建一个拖拽元素
      const dragging = document.createElement('div');
      dragging.className = `canvas-dragging${type === 'component' ? ' component-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;

      // 当移动组件时，如果鼠标位置距离画布上下边距小于 20px，则自动滚动画布，做一个定时器每隔 100ms 判断一次，直到鼠标位置距离边距大于 20px 或者离开画布
      if (this.curType === 'component') {
        const middleDom = document.querySelector('.create-middle');
        // 获取 middleDom 相对于 event 的位置
        const middleRect = middleDom.getBoundingClientRect();
        const middleHeight = middleRect.height;
        const offsetTop = clientY - middleRect.top;
        const offsetBottom = middleRect.bottom - clientY;
        if (this.scrollInterval) {
          clearInterval(this.scrollInterval);
        }
        if (offsetTop < 20) {
          this.scrollInterval = setInterval(() => {
            middleDom.scrollTop = Math.max(0, middleDom.scrollTop - 10);
          }, 10);
        } else if (offsetBottom < 20) {
          this.scrollInterval = setInterval(() => {
            middleDom.scrollTop = Math.min(middleDom.scrollHeight - middleHeight, middleDom.scrollTop + 10);
          }, 10);
        } else {
          clearInterval(this.scrollInterval);
          this.scrollInterval = null;
        }
      }

      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.scrollInterval) {
        clearInterval(this.scrollInterval);
        this.scrollInterval = null;
      }

      if (!this.isEdit) return;

      if (this.enterCanvas) {
        if (this.curType === 'component') {
          this.handleComponentMove();
          return;
        }

        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.curType = '';
      this.curParams = {};
      this.enterCanvas = false;
    },
    /**
     * 处理组件移动
     * 组件拖拽移动后，先删除组件，再根据拖拽位置计算插入行、列、格子
     */
    async handleComponentMove() {
      const component = this.findChildComponentByKey(`grid-${this.curParams.id}`);
      // 获取组件内容，保存后再插入到新的位置
      const requestData = component.getComponentRequestContent();

      const data = component?.data;
      this.curType = data?.type || 'select';
      let col = component?.$attrs['data-col'];
      let row = component?.$attrs['data-row'];
      let grid = component?.$attrs['data-grid'];
      let rowNum = 0; // 当前行组件数量
      this.data.row_list[row].col_list.forEach((item) => {
        rowNum += item.grid_list.length;
      });

      this.curParams = {
        ...this.curParams,
        col,
        row,
        grid,
        rowNum,
      };
      this.deleteComponent(this.curParams.id);

      await ContentSaveCoursewareComponentContent(requestData);

      if (this.curRow >= -1 && this.curCol <= -1) {
        this.calculateRowInsertedObject();
      }

      if (this.curRow >= -1 && this.curCol > -1 && this.curGrid <= -1) {
        // 当拖拽组件和放置位置在同一列内，且行内组件数量为 1，将 curRow 减 1，才能正确插入
        if ('col' in this.curParams) {
          const { row, rowNum } = this.curParams;
          if (row === this.curRow && rowNum === 1) {
            this.curRow = Math.max(0, this.curRow - 1);
            this.calculateRowInsertedObject();
            return;
          }
        }
        this.calculateColObject();
      }

      if (this.curRow >= -1 && this.curCol > -1 && this.curGrid > -1) {
        if ('col' in this.curParams) {
          const { col, row, grid } = this.curParams;

          // 当拖拽组件和放置位置在同一列内，将 curGrid 减 1，对应上面的删除组件，才能正确插入
          if (col === this.curCol && row === this.curRow && grid < this.curGrid) {
            this.curGrid = Math.max(0, this.curGrid - 1);
          }

          // 当拖拽组件和放置位置在同一列内，且行内组件数量为 1，将 curRow 减 1，才能正确插入
          if (col === this.curCol && row === this.curRow && grid === this.curGrid) {
            this.curRow = Math.max(0, this.curRow - 1);
            this.calculateRowInsertedObject();
            return;
          }
        }

        this.calculateGridObject();
      }

      this.curType = '';
      this.curParams = {};
      this.enterCanvas = false;
    },
    /**
     * 计算行样式
     * @param {number} i 行索引
     */
    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(' 6px ');
      let gridTemplateColumns = `0 ${str} 0`;

      return {
        gridAutoFlow: 'column',
        gridTemplateColumns,
        gridTemplateRows: 'auto',
      };
    },
    /**
     * 计算网格插入的对象
     */
    calculateGridObject() {
      const id = this.curParams?.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',
          edit_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',
          edit_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 = this.curParams?.id || `ID-${getRandomNumber(12, true)}`;
      const letter = `L${getRandomNumber(6, true)}`;
      const col_id = `C${getRandomNumber(8, 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, {
        col_id,
        width: '100fr',
        height: 'auto',
        grid_list: [
          {
            id,
            grid_area: letter,
            row: 1,
            width: '100fr',
            height: 'auto',
            edit_height: 'auto',
            type: this.curType,
          },
        ],
      });
    },
    /**
     * 计算行插入的对象
     */
    calculateRowInsertedObject() {
      const id = this.curParams?.id || `ID-${getRandomNumber(12, true)}`;
      const letter = `L${getRandomNumber(6, true)}`;
      const row_id = `R${getRandomNumber(6, true)}`;
      const col_id = `C${getRandomNumber(8, true)}`;

      this.data.row_list.splice(this.curRow + 1, 0, {
        width_list: ['100fr'],
        row_id,
        col_list: [
          {
            col_id,
            width: '100fr',
            height: 'auto',
            grid_list: [
              {
                id,
                grid_area: letter,
                width: '100fr',
                height: 'auto',
                edit_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.$refs.component.find((child) => child.$vnode.key === key);
    },

    /**
     * 计算分组线样式
     * @param {Array} rowIdList 行 ID 列表
     * @returns {Object} 样式对象
     */
    computedGroupLine(rowIdList) {
      // 获取画布顶部位置
      const canvas = this.$refs.canvas;
      const firstCheckbox = this.$el.querySelector(`.row-checkbox.${rowIdList[0]}`);
      const secCheckbox = this.$el.querySelector(`.row-checkbox.${rowIdList[1]}`);
      // DOM 未渲染，返回默认样式或空对象
      if (!canvas || !firstCheckbox || !secCheckbox) {
        return {};
      }

      const canvasTop = canvas.getBoundingClientRect().top;
      const firstRowTop = firstCheckbox.getBoundingClientRect().top;
      const secRowTop = secCheckbox.getBoundingClientRect().top;

      return {
        top: `${firstRowTop - canvasTop + 22}px`,
        height: `${secRowTop - firstRowTop - 24}px`,
      };
    },
    computedBorderColor(rowId) {
      const groupList = [];
      this.content_group_row_list.forEach(({ row_id, is_pre_same_group }) => {
        if (is_pre_same_group && groupList.length) {
          groupList[groupList.length - 1].push(row_id);
        } else {
          groupList.push([row_id]);
        }
      });
      const index = groupList.findIndex((g) => g.includes(rowId));
      return this.gridBorderColorList[index % this.gridBorderColorList.length];
    },
    /**
     * 计算网格样式
     * @param {Object} grid 网格对象
     * @returns {Object} 样式对象
     */
    computedGridStyle(grid) {
      let marginTop = grid.row === 1 ? '0' : '6px';

      return {
        gridArea: grid.grid_area,
        height: grid?.edit_height || grid.height,
        marginTop,
      };
    },
    /**
     * 计算网格视图顺序
     * @param {String} id 网格 ID
     * @returns {Number} 顺序值
     */
    computedGridViewOrder(id) {
      // 获取网格 id，是第几行第几列第几个网格，通过 data.row_list 计算
      let rowIndex = -1;
      let colIndex = -1;
      let gridIndex = -1;

      this.data.row_list.forEach((row, i) => {
        row.col_list.forEach((col, j) => {
          col.grid_list.forEach((grid, k) => {
            if (grid.id === id) {
              rowIndex = i;
              colIndex = j;
              gridIndex = k;
            }
          });
        });
      });

      let order = 0;
      // 计算前几行的网格数量
      if (rowIndex > 0) {
        for (let i = 0; i < rowIndex; i++) {
          this.data.row_list[i].col_list.forEach((col) => {
            order += col.grid_list.length;
          });
        }
      }
      // 计算当前行前几列的网格数量
      if (colIndex > 0) {
        for (let j = 0; j < colIndex; j++) {
          order += this.data.row_list[rowIndex].col_list[j].grid_list.length;
        }
      }
      // 加上当前列前面的网格数量
      order += gridIndex + 1;
      return order;
    },
    /**
     * 归一化 row_list 中的 row_id / col_id（为后端旧数据补 id）
     */
    normalizeRowColIds(data) {
      if (!data || !Array.isArray(data.row_list)) return data;
      data.row_list = data.row_list.map((row) => {
        if (!row.row_id) row.row_id = `R${getRandomNumber(6, true)}`;
        if (!Array.isArray(row.col_list)) row.col_list = [];
        row.col_list = row.col_list.map((col) => {
          if (!col.col_id) col.col_id = `C${getRandomNumber(8, true)}`;
          return col;
        });
        return row;
      });
      return data;
    },
    /**
     * 加载模板数据
     * @param {Object} param
     * @param {Object} param.data - 模板数据
     * @param {Array} param.content_group_row_list - 内容分组行列表
     */
    loadTemplateData_CreateCanvas({ data, content_group_row_list }) {
      this.data = data || {
        row_list: [],
      };
      this.content_group_row_list = content_group_row_list || [];
    },

    /**
     * 插入模板数据
     * @param {Object} param
     * @param {Object} param.row_list - 模板组件列表
     * @param {Array} param.content_group_row_list - 内容分组行列表
     */
    insertTemplateData_CreateCanvas({ row_list = [], content_group_row_list = [] }) {
      let contentGroupRowList = JSON.parse(JSON.stringify(content_group_row_list));

      let rowList = row_list.map((row) => {
        const oldRowID = row.row_id;
        const newRowID = `R${getRandomNumber(6, true)}`;
        row.row_id = newRowID;
        // 更新内容分组行列表中的 row_id
        for (let idx = 0; idx < contentGroupRowList.length; idx++) {
          if (contentGroupRowList[idx].row_id === oldRowID) {
            contentGroupRowList[idx] = { ...contentGroupRowList[idx], row_id: newRowID };
          }
        }

        row.col_list = row.col_list.map((col) => {
          col.col_id = `C${getRandomNumber(8, true)}`;

          col.grid_list = col.grid_list.map((grid) => {
            grid.oldId = grid.id;
            grid.id = `ID-${getRandomNumber(12, true)}`;
            grid.grid_area = `L${getRandomNumber(6, true)}`;
            return grid;
          });

          return col;
        });
        return row;
      });

      const curId = this.getCurSettingId();
      // 如果无选中组件，则插入到最后
      if (!curId) {
        this.data.row_list.push(...rowList);
        this.content_group_row_list.push(...contentGroupRowList);
        return;
      }
      // 插入到当前选中组件后面
      const attrs = this.findChildComponentByKey(`grid-${curId}`)?.$attrs;
      if (!attrs) return;
      const i = Number(attrs['data-row']);
      this.data.row_list.splice(i + 1, 0, ...rowList);
      this.content_group_row_list.splice(i + 1, 0, ...contentGroupRowList);
    },
    /**
     * @description 复制组件
     * @param {object} data 组件数据
     */
    copyComponent(data) {
      this.$emit('copyComponent', data);
    },
    /**
     * @description 粘贴组件
     * @param {Object} componentData 组件数据
     * @param {String} position 放入位置类型
     */
    pasteComponent(componentData, position) {
      if (!componentData) return;
      let _data = JSON.parse(JSON.stringify(componentData));
      const curId = this.getCurSettingId();
      const attrs = this.findChildComponentByKey(`grid-${curId}`)?.$attrs;
      if (!attrs) return;
      const i = Number(attrs['data-row']);
      const j = Number(attrs['data-col']);
      let k = Number(attrs['data-grid']);
      let type = _data.type;

      let row = this.data.row_list[i];
      let col = row.col_list[j];
      let grid = col.grid_list;

      const id = `ID-${getRandomNumber(12, true)}`;
      const letter = `L${getRandomNumber(6, true)}`;

      if (['top', 'bottom'].includes(position)) {
        let rowNum = k === 0 ? 1 : grid[k - 1].row + 1;
        if (position === 'bottom') {
          rowNum = grid[k].row + 1;
        }
        k = position === 'top' ? k : k + 1;
        grid.splice(k, 0, {
          id,
          grid_area: letter,
          width: '100fr',
          height: 'auto',
          edit_height: 'auto',
          row: rowNum,
          type,
        });
        grid.forEach((item, i) => {
          if (i > k) {
            item.row += 1;
          }
        });
      }

      if (['left', 'right'].includes(position)) {
        let rowNum = grid[k].row;
        let _k = position === 'left' ? k : k + 1;
        grid.splice(_k, 0, {
          id,
          grid_area: letter,
          width: '100fr',
          height: 'auto',
          edit_height: 'auto',
          row: rowNum,
          type,
        });

        let allRowNum = grid.filter(({ row }) => row === rowNum).length;
        let w = 0;
        grid.forEach((item, i) => {
          if (item.row === rowNum && i !== k) {
            let width = Number(item.width.replace('fr', ''));
            let diff = width / allRowNum;
            item.width = `${width - diff}fr`;
            w += diff;
          }
        });
        grid[_k].width = `${w}fr`;
      }
      this.$nextTick(() => {
        let newComponent = this.findChildComponentByKey(`grid-${id}`); // 获取新添加的组件实例
        newComponent?.setData(_data); // 设置新添加的组件数据
      });
    },
    // 交换组件位置
    switchComponent() {
      let checkedNum = 0;
      let components = [];
      this.$refs.component.forEach((child) => {
        if (child.$refs.base.checked) {
          checkedNum += 1;
          components.push(child.id);
        }
      });
      if (checkedNum !== 2) {
        this.$message.error('请选择两个组件进行交换');
        return;
      }
      this.$confirm('组件未保存的改动将丢失。确定要交换这两个组件的位置吗？', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      })
        .then(() => {
          this.switchComponentData(components);
        })
        .catch(() => {
          // 取消交换
        });
    },
    /**
     * 合并遍历 data.row_list，找到两个网格对象并直接交换它们的属性
     * @param {Array} components 组件 ID 列表
     */
    switchComponentData(components) {
      const [id1, id2] = components;
      let g1 = null;
      let g2 = null;

      for (const row of this.data.row_list) {
        for (const col of row.col_list) {
          for (const grid of col.grid_list) {
            if (grid.id === id1) g1 = grid;
            else if (grid.id === id2) g2 = grid;
            if (g1 && g2) break;
          }
          if (g1 && g2) break;
        }
        if (g1 && g2) break;
      }

      if (!g1 || !g2) return;

      const tmp = { grid_area: g1.grid_area, id: g1.id, type: g1.type }; // 临时存储 g1 的属性
      g1.grid_area = g2.grid_area;
      g1.type = g2.type;
      g1.id = g2.id;

      g2.grid_area = tmp.grid_area;
      g2.type = tmp.type;
      g2.id = tmp.id;
    },
    clearContent() {
      this.data.row_list = [];
      this.content_group_row_list = [];
    },
  },
};
</script>

<style lang="scss" scoped>
.canvas {
  .edit {
    position: relative;
    display: flex;
    flex-direction: column;
    row-gap: 8px;
    width: $courseware-width;
    min-height: calc(100vh - 240px);
    padding: 24px;
    margin: 0 auto;
    background-color: #fff;

    .group-line {
      position: absolute;
      left: 11px;
      width: 1px;
      background-color: #165dff;
    }

    .row {
      display: grid;
      row-gap: 16px;

      .row-checkbox {
        position: absolute;
        left: 4px;
      }

      > .drag-vertical-line:not(:first-child, :last-child, .grid-line) {
        left: 6px;
      }

      .drag-vertical-line.grid-line-right,
      .drag-vertical-line.grid-line-left {
        top: 25%;
        height: 50%;
      }

      .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 {
        width: 50%;
        margin-left: 25%;
        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: #f43;
      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%);

  &.component-dragging {
    background-color: #f9ffe0;
    border-color: #f9ffe0;
  }
}
</style>
