| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418 | 
							- <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">
 
-       <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 || `row-${i}`" 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 || 'r' + i}-${col.col_id || 'c' + j}`"
 
-               class="drag-vertical-line col-start"
 
-               :data-row="i"
 
-               :data-col="j"
 
-             ></span>
 
-             <div :key="col.col_id || `col-${i}-${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-${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}`"
 
-                   :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="deleteComponent"
 
-                   @showSetting="showSetting"
 
-                   @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 || 'r' + i}-${col.col_id || 'c' + 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-${row.row_id || 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
 
-       ref="previewEdit"
 
-       :courseware-id="courseware_id"
 
-       :row-list="data.row_list"
 
-       @computedMoveData="computedMoveData"
 
-     />
 
-     <FullTextSettings
 
-       :book-id="project_id"
 
-       :visible.sync="visibleFullTextSettings"
 
-       :settings="data.unified_attrib"
 
-       @fullTextSettings="fullTextSettings"
 
-     />
 
-   </main>
 
- </template>
 
- <script>
 
- import { getRandomNumber } from '@/utils/index';
 
- import { componentList } from '../../data/bookType';
 
- import { ContentSaveCoursewareContent, ContentGetCoursewareContent, GetBookUnifiedAttrib } from '@/api/book';
 
- import _ from 'lodash';
 
- import { unified_attrib } from '@/common/data';
 
- import PreviewEdit from './PreviewEdit.vue';
 
- import FullTextSettings from '../components/FullTextSettings.vue';
 
- export default {
 
-   name: 'CreateCanvas',
 
-   components: {
 
-     PreviewEdit,
 
-     FullTextSettings,
 
-   },
 
-   inject: ['getCurSettingId'],
 
-   provide() {
 
-     return {
 
-       getBookUnifiedAttr: () => this.book_unified_attrib,
 
-     };
 
-   },
 
-   props: {
 
-     isEdit: {
 
-       type: Boolean,
 
-       required: true,
 
-     },
 
-   },
 
-   data() {
 
-     const { project_id } = this.$route.query;
 
-     return {
 
-       courseware_id: this.$route.params.courseware_id,
 
-       project_id,
 
-       data: {
 
-         background_image_url: '',
 
-         background_position: {
 
-           width: 100,
 
-           height: 100,
 
-           top: 0,
 
-           left: 0,
 
-         },
 
-         // 组件列表
 
-         row_list: [],
 
-         // 全文设置
 
-         unified_attrib,
 
-       },
 
-       rowCheckList: {}, // 行复选框列表
 
-       content_group_row_list: [], // 行分组id列表
 
-       gridBorderColorList: ['#3fc7cc', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#d863ff', '#724fff'], // 网格边框颜色列表
 
-       curType: 'divider',
 
-       componentList,
 
-       curRow: -2,
 
-       curCol: -1,
 
-       curGrid: -1,
 
-       gridInsertType: '', // 网格插入类型
 
-       enterCanvas: false, // 是否进入画布
 
-       // 拖拽状态
 
-       drag: {
 
-         clientX: 0,
 
-         clientY: 0,
 
-         dragging: false,
 
-       },
 
-       visibleFullTextSettings: false,
 
-       book_unified_attrib: unified_attrib,
 
-     };
 
-   },
 
-   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() {
 
-     ContentGetCoursewareContent({ id: this.courseware_id }).then(({ content, content_group_row_list }) => {
 
-       if (content) {
 
-         this.data = JSON.parse(content);
 
-         // 为旧数据补充 row_id / col_id,保证 v-for 使用稳定 key
 
-         this.normalizeRowColIds();
 
-       }
 
-       if (content_group_row_list) this.content_group_row_list = JSON.parse(content_group_row_list);
 
-       this.$watch(
 
-         'data',
 
-         () => {
 
-           this.changeData();
 
-         },
 
-         {
 
-           deep: true,
 
-         },
 
-       );
 
-     });
 
-     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: {
 
-     changeData() {
 
-       this.$emit('changeData');
 
-     },
 
-     showFullTextSettings() {
 
-       this.visibleFullTextSettings = true;
 
-     },
 
-     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;
 
-     },
 
-     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.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();
 
-       }
 
-     },
 
-     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 = 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;
 
-     },
 
-     /**
 
-      * 删除组件
 
-      * @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) {
 
-       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} 4px 4px `;
 
-         }
 
-       });
 
-       // 计算 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?.edit_height || 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(' 6px ');
 
-       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',
 
-           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 = `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 = `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() {
 
-       if (!this.data || !Array.isArray(this.data.row_list)) return;
 
-       this.data.row_list = this.data.row_list.map((row, ri) => {
 
-         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, ci) => {
 
-           if (!col.col_id) col.col_id = `C${getRandomNumber(8, true)}`;
 
-           return col;
 
-         });
 
-         return row;
 
-       });
 
-     },
 
-   },
 
- };
 
- </script>
 
- <style lang="scss" scoped>
 
- .canvas {
 
-   position: relative;
 
-   display: flex;
 
-   flex-direction: column;
 
-   row-gap: 8px;
 
-   width: $courseware-width;
 
-   min-height: calc(100% - 6px);
 
-   padding: 24px;
 
-   margin: 0 auto;
 
-   background-color: #fff;
 
-   background-repeat: no-repeat;
 
-   border-radius: 4px;
 
-   .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%);
 
- }
 
- </style>
 
 
  |