| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417 |
- <template>
- <div ref="previewRoot" class="preview">
- <div v-if="heightPrompt" class="height-prompt"></div>
- <template v-for="(row, i) in rowList">
- <!-- 行 -->
- <div :key="i" :class="['row', `row-${i}`]" :style="getMultipleColStyle(i)">
- <!-- 列 -->
- <template v-for="(col, j) in row.col_list">
- <div :key="j" :class="['col', `col-${i}-${j}`]" :style="computedColStyle(col)">
- <!-- 网格 -->
- <div
- v-for="(grid, k) in col.grid_list"
- :key="`grid-${i}-${j}-${k}`"
- :style="{ gridArea: grid.grid_area, height: grid.height }"
- :class="[!noMoveComponent.includes(grid.type) ? 'grid' : 'no-grid']"
- >
- <template v-for="{ type, cursor, lineClass } in moveLineList">
- <span
- v-if="!noMoveComponent.includes(grid.type)"
- :key="`${type}-${i}-${j}-${k}`"
- class="drag-line"
- :class="[type, ...lineClass]"
- :style="{ gridArea: type }"
- :data-type="type"
- @mousedown="dragStart($event, { cursor, type, i, j, k, id: grid.id })"
- ></span>
- </template>
- <component
- :is="previewComponentList[grid.type]"
- :id="grid.id"
- ref="preview"
- :key="`preview-${grid.id}`"
- :courseware-id="coursewareId"
- type="edit"
- :class="[grid.id]"
- :style="{
- gridArea: 'preview',
- height: grid.height,
- overflow: 'auto',
- }"
- @handleHeightChange="handleHeightChange"
- />
- </div>
- </div>
- </template>
- </div>
- </template>
- </div>
- </template>
- <script>
- import { previewComponentList } from '@/views/book/courseware/data/bookType';
- export default {
- name: 'PreviewEdit',
- provide() {
- return {
- getDragStatus: () => this.drag.dragging,
- bookInfo: this.bookInfo,
- getPermissionControl: () => this.permissionControl,
- };
- },
- props: {
- rowList: {
- type: Array,
- required: true,
- },
- coursewareId: {
- type: String,
- required: true,
- },
- },
- data() {
- return {
- previewComponentList,
- drag: {
- dragging: false,
- i: -1,
- j: -1,
- k: -1,
- startX: 0,
- startY: 0,
- type: '',
- id: '',
- },
- moveLineList: [
- {
- type: 'top',
- cursor: 'ns-resize',
- lineClass: ['drag-line'],
- },
- {
- type: 'left',
- cursor: 'ew-resize',
- lineClass: ['drag-vertical-line'],
- },
- {
- type: 'right',
- cursor: 'ew-resize',
- lineClass: ['drag-vertical-line'],
- },
- {
- type: 'bottom',
- cursor: 'ns-resize',
- lineClass: ['drag-line'],
- },
- ],
- dragElement: null, // 当前拖拽的组件实例
- // 不需要移动的组件
- noMoveComponent: ['divider', 'spacing'],
- bookInfo: {
- theme_color: '',
- },
- resizeObserver: null, // 用于监听高度变化
- heightPrompt: false, // 是否显示高度提示线
- permissionControl: {
- can_answer: false, // 可作答
- can_judge_correct: false, // 可判断对错(客观题)
- can_show_answer: false, // 可查看答案
- can_correct: false, // 可批改
- can_check_correct: false, // 可查看批改
- },
- };
- },
- created() {
- document.addEventListener('mousemove', this.dragMove);
- document.addEventListener('mouseup', this.dragEnd);
- },
- mounted() {
- const element = this.$refs.previewRoot;
- // 监听 courserware 高度变化,获取其高度
- this.resizeObserver = new ResizeObserver(() => {
- const rect = element.getBoundingClientRect();
- this.heightPrompt = rect.height > 1620;
- });
- this.resizeObserver.observe(element);
- },
- beforeDestroy() {
- document.removeEventListener('mousemove', this.dragMove);
- document.removeEventListener('mouseup', this.dragEnd);
- if (this.resizeObserver) {
- this.resizeObserver.disconnect();
- }
- },
- methods: {
- handleHeightChange(id, newHeight) {
- this.$emit('handleHeightChange', id, newHeight);
- },
- getMultipleColStyle(i) {
- let row = this.rowList[i];
- let col = row.col_list;
- if (col.length <= 1) {
- return {
- gridTemplateColumns: '100fr',
- };
- }
- let gridTemplateColumns = row.width_list.join(' ');
- return {
- gridAutoFlow: 'column',
- gridTemplateColumns,
- gridTemplateRows: 'auto',
- };
- },
- /**
- * 分割整数为多个 1的倍数
- * @param {number} num
- * @param {number} parts
- */
- splitInteger(num, parts) {
- let base = Math.floor(num / parts);
- let arr = Array(parts).fill(base);
- let remainder = num - base * parts;
- for (let i = 0; remainder > 0; i = (i + 1) % parts) {
- arr[i] += 1;
- remainder -= 1;
- }
- return arr;
- },
- /**
- * 计算列样式
- * @param {Object} col 列对象
- */
- computedColStyle(col) {
- const grid = col.grid_list;
- // 单次分组:后续 areas / columns / rows 统一复用 rowGroups。
- const rowGroups = new Map();
- grid.forEach((item) => {
- if (!rowGroups.has(item.row)) {
- rowGroups.set(item.row, []);
- }
- rowGroups.get(item.row).push(item);
- });
- let maxCol = 0; // 最大列数
- let curMaxRow = 0; // 当前数量最大 row 的值
- rowGroups.forEach((items, row) => {
- if (items.length > maxCol) {
- maxCol = items.length;
- curMaxRow = row;
- }
- });
- const sortedRows = Array.from(rowGroups.keys()).sort((a, b) => a - b);
- // 计算 grid_template_areas
- let gridTemplateAreas = '';
- sortedRows.forEach((row) => {
- const rowItems = rowGroups.get(row) || [];
- const needNum = maxCol - rowItems.length; // 需要补齐的数量
- const splitArr = rowItems.length > 1 ? this.splitInteger(needNum, rowItems.length) : [];
- const areaList = rowItems.map(({ grid_area }, index) => {
- if (curMaxRow === row) {
- return `${grid_area}`;
- }
- if (rowItems.length === 1) {
- return ` ${grid_area} `.repeat(needNum + 1);
- }
- return splitArr[index] === 0 ? ` ${grid_area} ` : ` ${grid_area} `.repeat(splitArr[index] + 1);
- });
- gridTemplateAreas += `'${areaList.join(' ')}' `;
- });
- // 计算 grid_template_columns
- let gridTemplateColumns = '';
- const maxRowItems = rowGroups.get(curMaxRow) || [];
- maxRowItems.forEach((item) => {
- gridTemplateColumns += `${item.width} `;
- });
- // 计算 grid_template_rows
- let gridTemplateRows = '';
- sortedRows.forEach((row) => {
- const heights = (rowGroups.get(row) || []).map((item) => item.height);
- if (heights.length === 1) {
- gridTemplateRows += `${heights[0]} `;
- } else {
- const isAllAuto = heights.every((item) => item === 'auto'); // 是否全是 auto
- gridTemplateRows += isAllAuto ? 'auto ' : `max(${heights.join(', ')}) `;
- }
- });
- return {
- width: col.width,
- gridTemplateAreas,
- gridTemplateColumns,
- gridTemplateRows,
- };
- },
- /**
- * 拖拽开始
- * @param {MouseEvent} event
- * @param {string} cursor
- * @param {string} type
- * @param {string} id
- * @param {number} i
- * @param {number} j
- * @param {number} k
- */
- dragStart(event, { cursor, type, id, i, j, k }) {
- const dragElement = this.findChildComponentByKey(`preview-${id}`);
- if (!dragElement) return;
- this.dragElement = dragElement;
- const { clientX, clientY } = event;
- this.drag = {
- dragging: true,
- startX: clientX,
- startY: clientY,
- type,
- id,
- i,
- j,
- k,
- };
- document.body.style.cursor = cursor;
- },
- /**
- * 拖拽移动
- * @param {MouseEvent} event
- */
- dragMove(event) {
- if (!this.drag.dragging) return;
- const { clientX, clientY } = event;
- const { i, j, k, id, startX, startY, type } = this.drag;
- const offsetX = clientX - startX;
- const offsetY = clientY - startY;
- let { min_height, min_width } = this.dragElement.data;
- const ROW_WIDTH = 1000;
- this.$emit('computedMoveData', {
- i,
- j,
- k,
- offsetX,
- offsetY,
- type,
- id,
- min_width,
- min_height,
- row_width: ROW_WIDTH,
- });
- this.drag.startX = clientX;
- this.drag.startY = clientY;
- this.$forceUpdate();
- },
- /**
- * 拖拽结束
- */
- dragEnd() {
- this.drag = {
- dragging: false,
- startX: 0,
- startY: 0,
- type: '',
- id: '',
- i: -1,
- j: -1,
- k: -1,
- };
- this.dragElement = null;
- document.body.style.cursor = 'auto';
- },
- // 获取子组件
- findChildComponentByKey(key) {
- return this.$children.find((child) => child.$vnode.key === key);
- },
- },
- };
- </script>
- <style lang="scss" scoped>
- .preview {
- position: relative;
- display: flex;
- flex-direction: column;
- row-gap: $component-spacing;
- width: calc($courseware-width + 200px);
- height: 100%;
- min-height: calc(100vh - 240px);
- padding: calc($courseware-top-padding + 15px) 100px calc($courseware-bottom-padding + 15px) 100px;
- margin: 0 auto;
- overflow: hidden;
- background-color: #fff;
- background-repeat: no-repeat;
- border-bottom-right-radius: 12px;
- border-bottom-left-radius: 12px;
- .height-prompt {
- position: absolute;
- top: 1620px;
- left: -200px;
- width: 1400px;
- border-top: 2px dashed #903ff8;
- }
- .row {
- display: grid;
- gap: $component-spacing;
- .col {
- display: grid;
- gap: $component-spacing;
- align-items: flex-start;
- .grid {
- display: grid;
- grid-template:
- 'top top top' 3px
- 'left preview right' 1fr
- 'bottom bottom bottom' 3px / 3px 1fr 3px;
- }
- .drag-line {
- z-index: 2;
- width: 100%;
- height: 6px;
- cursor: ns-resize;
- background: linear-gradient(to bottom, transparent, transparent 40%, #e5e6eb 40%, #e5e6eb 60%, transparent 60%);
- &.bottom {
- background: linear-gradient(
- to bottom,
- transparent,
- transparent 20%,
- #e5e6eb 20%,
- #e5e6eb 50%,
- transparent 50%
- );
- }
- }
- .drag-vertical-line {
- z-index: 2;
- width: 6px;
- height: 100%;
- cursor: ew-resize;
- background: linear-gradient(to right, transparent, transparent 40%, #e5e6eb 40%, #e5e6eb 60%, transparent 60%);
- &.left {
- background: linear-gradient(to left, transparent, transparent 60%, #e5e6eb 60%, #e5e6eb);
- }
- }
- }
- }
- }
- </style>
|