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