|
@@ -14,79 +14,84 @@
|
|
|
},
|
|
|
]"
|
|
|
>
|
|
|
- <span class="drag-line" data-row="-1"></span>
|
|
|
- <!-- 行 -->
|
|
|
- <template v-for="(row, i) in data.row_list">
|
|
|
- <div :key="i" class="row" :style="getMultipleColStyle(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
|
|
|
- :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-${i}-${j}-${k}-${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"
|
|
|
- />
|
|
|
- <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 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="getMultipleColStyle(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
|
|
|
+ :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-${i}-${j}-${k}-${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>
|
|
|
- <span class="drag-line" :data-row="data.row_list.length - 1"></span>
|
|
|
+
|
|
|
+ <PreviewEdit v-else :courseware-id="courseware_id" :row-list="data.row_list" @computedMoveData="computedMoveData" />
|
|
|
</main>
|
|
|
</template>
|
|
|
|
|
@@ -95,8 +100,19 @@ import { getRandomNumber } from '@/utils/index';
|
|
|
import { componentList } from '../../data/bookType';
|
|
|
import { SaveCoursewareContent, GetCoursewareContent } from '@/api/book';
|
|
|
|
|
|
+import PreviewEdit from './PreviewEdit.vue';
|
|
|
+
|
|
|
export default {
|
|
|
name: 'CreateCanvas',
|
|
|
+ components: {
|
|
|
+ PreviewEdit,
|
|
|
+ },
|
|
|
+ props: {
|
|
|
+ isEdit: {
|
|
|
+ type: Boolean,
|
|
|
+ required: true,
|
|
|
+ },
|
|
|
+ },
|
|
|
data() {
|
|
|
const { book_id, chapter_id } = this.$route.query;
|
|
|
|
|
@@ -161,7 +177,18 @@ export default {
|
|
|
},
|
|
|
created() {
|
|
|
GetCoursewareContent({ id: this.courseware_id }).then(({ content }) => {
|
|
|
- if (content) this.data = JSON.parse(content);
|
|
|
+ if (content) {
|
|
|
+ this.data = JSON.parse(content);
|
|
|
+ }
|
|
|
+ this.$watch(
|
|
|
+ 'data',
|
|
|
+ () => {
|
|
|
+ this.changeData();
|
|
|
+ },
|
|
|
+ {
|
|
|
+ deep: true,
|
|
|
+ },
|
|
|
+ );
|
|
|
});
|
|
|
},
|
|
|
mounted() {
|
|
@@ -173,15 +200,28 @@ export default {
|
|
|
document.removeEventListener('mouseup', this.dragEnd);
|
|
|
},
|
|
|
methods: {
|
|
|
- saveCoursewareContent() {
|
|
|
+ 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) => {
|
|
|
+ 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)',
|
|
|
+ });
|
|
|
SaveCoursewareContent({
|
|
|
id: this.courseware_id,
|
|
|
category: 'NEW',
|
|
@@ -189,6 +229,13 @@ export default {
|
|
|
component_id_list,
|
|
|
}).then(() => {
|
|
|
this.$message.success('保存成功');
|
|
|
+ loading.close();
|
|
|
+ if (type === 'quit') {
|
|
|
+ this.$emit('back');
|
|
|
+ }
|
|
|
+ if (type === 'edit') {
|
|
|
+ this.$emit('changeEditStatus');
|
|
|
+ }
|
|
|
});
|
|
|
},
|
|
|
setBackgroundImage(url, position) {
|
|
@@ -205,54 +252,110 @@ export default {
|
|
|
showSetting(setting, type, id) {
|
|
|
this.$emit('showSetting', setting, type, id);
|
|
|
},
|
|
|
+ /**
|
|
|
+ * 计算组件移动
|
|
|
+ * @param {number} i 行
|
|
|
+ * @param {number} j 列
|
|
|
+ * @param {number} k 格子
|
|
|
+ */
|
|
|
componentMove(i, j, k) {
|
|
|
return ({ type, offsetX, offsetY, id }) => {
|
|
|
- const row = this.data.row_list[i];
|
|
|
- const col = row.col_list[j];
|
|
|
- const grid = col.grid_list[k];
|
|
|
-
|
|
|
- if (['top', 'bottom'].includes(type)) {
|
|
|
- if (grid.height === 'auto') {
|
|
|
- const gridHeight = document.querySelector(`.${id}`).offsetHeight;
|
|
|
- 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', ''));
|
|
|
- if (height + offsetY >= 50) {
|
|
|
- grid.height = `${height + offsetY}px`;
|
|
|
- col.grid_template_rows = `0 ${col.grid_list.map(({ height }) => height).join(' 16px ')} 0`;
|
|
|
- }
|
|
|
+ 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 }) {
|
|
|
+ const row = this.data.row_list[i];
|
|
|
+ const col = row.col_list[j];
|
|
|
+ const grid = col.grid_list[k];
|
|
|
+
|
|
|
+ if (['top', 'bottom'].includes(type)) {
|
|
|
+ if (grid.height === 'auto') {
|
|
|
+ const gridHeight = document.querySelector(`.${id}`).offsetHeight;
|
|
|
+ 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', ''));
|
|
|
+ if (height + offsetY >= 50) {
|
|
|
+ grid.height = `${height + offsetY}px`;
|
|
|
+ col.grid_template_rows = `0 ${col.grid_list.map(({ height }) => height).join(' 16px ')} 0`;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- if (type === 'left' && j > 0) {
|
|
|
- const prevGrid = row.width_list[j - 1];
|
|
|
- const prevWidth = Number(prevGrid.replace('fr', ''));
|
|
|
- const width = Number(row.width_list[j].replace('fr', ''));
|
|
|
+ let gridList = col.grid_list.filter((item) => item.row === grid.row);
|
|
|
+ // 一行中有多个格子
|
|
|
+ if (gridList.length > 1) {
|
|
|
+ let find = gridList.findIndex((item) => item.id === id);
|
|
|
+ if (type === 'left' && find > 0) {
|
|
|
+ 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;
|
|
|
- row.width_list[j + 1] = `${prevWidth + ratio}fr`;
|
|
|
- row.width_list[j] = `${width - ratio}fr`;
|
|
|
- this.$forceUpdate();
|
|
|
+
|
|
|
+ col.grid_list[k - 1].width = `${prevWidth + ratio}fr`;
|
|
|
+ grid.width = `${width - ratio}fr`;
|
|
|
}
|
|
|
- if (type === 'right' && j < row.col_list.length - 1) {
|
|
|
- let nextGrid = row.width_list[j + 1];
|
|
|
- const nextWidth = Number(nextGrid.replace('fr', ''));
|
|
|
- const width = Number(row.width_list[j].replace('fr', ''));
|
|
|
+
|
|
|
+ if (type === 'right' && find < gridList.length - 1) {
|
|
|
+ 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;
|
|
|
- row.width_list[j + 1] = `${nextWidth - ratio}fr`;
|
|
|
- row.width_list[j] = `${width + ratio}fr`;
|
|
|
- this.$forceUpdate();
|
|
|
+
|
|
|
+ col.grid_list[k + 1].width = `${nextWidth - ratio}fr`;
|
|
|
+ grid.width = `${width + ratio}fr`;
|
|
|
}
|
|
|
- };
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (type === 'left' && j > 0) {
|
|
|
+ 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;
|
|
|
+
|
|
|
+ row.width_list[j - 1] = `${prevWidth + ratio}fr`;
|
|
|
+ row.width_list[j] = `${width - ratio}fr`;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (type === 'right' && j < row.col_list.length - 1) {
|
|
|
+ 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;
|
|
|
+
|
|
|
+ row.width_list[j + 1] = `${nextWidth - ratio}fr`;
|
|
|
+ row.width_list[j] = `${width + ratio}fr`;
|
|
|
+ }
|
|
|
+ this.$forceUpdate();
|
|
|
},
|
|
|
/**
|
|
|
* 重新计算格子宽度
|
|
@@ -368,17 +471,45 @@ export default {
|
|
|
|
|
|
// 计算 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 === 1) {
|
|
|
+ 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) => {
|
|
|
+ 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 ${grid.map(({ height }) => height).join(' ')} 0`,
|
|
|
+ gridTemplateRows: `0 ${gridTemplateRows} 0`,
|
|
|
};
|
|
|
},
|
|
|
/**
|