123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345 |
- <template>
- <div
- class="courserware"
- ref="courserware"
- :style="[
- {
- backgroundImage: background.background_image_url ? `url(${background.background_image_url})` : '',
- backgroundSize: background.background_image_url
- ? `${background.background_position.width}% ${background.background_position.height}%`
- : '',
- backgroundPosition: background.background_image_url
- ? `${background.background_position.left}% ${background.background_position.top}%`
- : '',
- },
- ]"
- >
- <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">
- <div :key="j" :class="['col', `col-${i}-${j}`]" :style="computedColStyle(col)">
- <!-- 网格 -->
- <template v-for="(grid, k) in col.grid_list">
- <div @contextmenu.prevent="handleContextMenu($event, grid.id)" :key="k">
- <component
- :is="previewComponentList[grid.type]"
- ref="preview"
- :content="computedColContent(grid.id)"
- :class="[grid.id]"
- :style="{
- gridArea: grid.grid_area,
- height: grid.height,
- }"
- />
- </div>
- <div
- v-if="showMenu && componentId === grid.id"
- class="custom-context-menu"
- :style="{ left: menuPosition.x + 'px', top: menuPosition.y + 'px' }"
- @click="handleMenuItemClick"
- :key="'menu' + grid.id + k"
- >
- <SvgIcon icon-class="icon-publish" size="24" />
- 添加批注
- </div>
- <div
- :key="'show' + grid.id + k"
- v-if="showRemark && Object.keys(componentRemarkObj).length !== 0 && componentRemarkObj[grid.id]"
- >
- <el-popover
- v-for="(items, indexs) in componentRemarkObj[grid.id]"
- :key="indexs"
- placement="bottom"
- width="200"
- trigger="click"
- >
- <div v-html="items.content"></div>
- <template #reference>
- <SvgIcon
- icon-class="icon-info"
- size="24"
- class="remark-info"
- slot="reference"
- :style="{ left: items.position_x - 12 + 'px', top: items.position_y - 12 + 'px' }"
- />
- </template>
- </el-popover>
- </div>
- </template>
- </div>
- </template>
- </div>
- </template>
- </div>
- </template>
- <script>
- import { previewComponentList } from '@/views/book/courseware/data/bookType';
- export default {
- name: 'CoursewarePreview',
- provide() {
- return {
- getDragStatus: () => false,
- bookInfo: this.bookInfo,
- };
- },
- props: {
- data: {
- type: Object,
- default: () => ({}),
- },
- background: {
- type: Object,
- default: () => ({}),
- },
- componentList: {
- type: Array,
- required: true,
- },
- canRemark: {
- type: Boolean,
- default: false,
- },
- showRemark: {
- type: Boolean,
- default: false,
- },
- componentRemarkObj: {
- type: Object,
- default: () => ({}),
- },
- },
- data() {
- return {
- previewComponentList,
- bookInfo: {
- theme_color: '',
- },
- showMenu: false,
- divPosition: {
- left: 0,
- top: 0,
- }, // courserware盒子原始距离页面顶部和左边的距离
- menuPosition: { x: 0, y: 0, select_node: '' }, // 用于存储菜单的位置
- componentId: '', // 添加批注的组件id
- };
- },
- mounted() {
- const element = this.$refs.courserware;
- const rect = element.getBoundingClientRect();
- this.divPosition = {
- left: rect.left,
- top: rect.top,
- };
- window.addEventListener('mousedown', this.handleMouseDown);
- },
- methods: {
- /**
- * 计算组件内容
- * @param {string} id 组件id
- * @returns {string} 组件内容
- */
- computedColContent(id) {
- if (!id) return '';
- return this.componentList.find((item) => item.component_id === id)?.content || '';
- },
- getMultipleColStyle(i) {
- let row = this.data.row_list[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;
- },
- computedColStyle(col) {
- const 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 gridTemplateAreas = '';
- let gridArr = [];
- grid.forEach(({ grid_area, row }) => {
- if (!gridArr[row - 1]) {
- gridArr[row - 1] = [];
- }
- if (curMaxRow === row) {
- gridArr[row - 1].push(`${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; // 需要的数量
- 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(`${str}`);
- }
- });
- gridArr.forEach((item) => {
- gridTemplateAreas += `'${item.join(' ')}' `;
- });
- // 计算 grid_template_columns
- let gridTemplateColumns = '';
- 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) {
- gridTemplateColumns += `${item.width} `;
- }
- });
- // 计算 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,
- gridTemplateColumns,
- gridTemplateRows,
- };
- },
- handleContextMenu(event, id) {
- if (this.canRemark) {
- event.preventDefault(); // 阻止默认的上下文菜单显示
- this.menuPosition = {
- x: event.clientX - this.divPosition.left,
- y: event.clientY - this.divPosition.top,
- }; // 设置菜单位置
- this.componentId = id;
- this.$emit('computeScroll');
- }
- },
- handleResult(top, left, select_node) {
- this.menuPosition = {
- x: this.menuPosition.x + left,
- y: this.menuPosition.y + top,
- select_node: select_node,
- }; // 设置菜单位置
- this.showMenu = true; // 显示菜单
- },
- handleMenuItemClick() {
- this.showMenu = false; // 隐藏菜单
- this.$emit(
- 'addRemark',
- this.menuPosition.select_node,
- this.menuPosition.x,
- this.menuPosition.y,
- this.componentId,
- );
- },
- handleMouseDown(event) {
- if (event.button === 0) {
- // 0 表示左键
- this.showMenu = false;
- }
- },
- },
- beforeDestroy() {
- window.removeEventListener('mousedown', this.handleMouseDown);
- },
- };
- </script>
- <style lang="scss" scoped>
- .courserware {
- position: relative;
- display: flex;
- flex-direction: column;
- row-gap: 6px;
- width: 100%;
- height: 100%;
- min-height: 500px;
- padding: 24px;
- background-color: #fff;
- background-repeat: no-repeat;
- border-bottom-right-radius: 12px;
- border-bottom-left-radius: 12px;
- .row {
- display: grid;
- gap: 16px;
- .col {
- display: grid;
- gap: 16px;
- overflow: hidden;
- }
- }
- .custom-context-menu,
- .remark-info {
- position: absolute;
- z-index: 999;
- display: flex;
- gap: 3px;
- align-items: center;
- font-size: 14px;
- cursor: pointer;
- }
- }
- </style>
|