| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- <template>
- <div
- ref="courseware"
- class="courseware"
- :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 v-show="computedRowVisibility(row.row_id)" :key="i" class="row">
- <el-checkbox
- v-if="
- isShowGroup &&
- groupRowList.find(({ row_id, is_pre_same_group }) => row.row_id === row_id && !is_pre_same_group)
- "
- v-model="rowCheckList[row.row_id]"
- :class="['row-checkbox', `${row.row_id}`]"
- />
- <!-- 列 -->
- <template v-for="(col, j) in row.col_list">
- <div :key="j" :class="['col', `col-${i}-${j}`]">
- <!-- 网格 -->
- <template v-for="(grid, k) in col.grid_list">
- <component
- :is="previewComponentList[grid.type]"
- :key="k"
- ref="preview"
- class="grid"
- :content="computedColContent(grid.id)"
- :data-row="i"
- :data-col="j"
- :data-grid="k"
- :data-view-order="computedGridViewOrder(grid.id)"
- :class="[grid.id]"
- :is-mobile="true"
- :data-id="grid.id"
- :style="{
- gridArea: grid.grid_area,
- }"
- />
- </template>
- </div>
- </template>
- </div>
- </template>
- </div>
- </template>
- <script>
- import { previewComponentList } from '@/views/book/courseware/data/bookType';
- export default {
- name: 'MobileCoursewarePreview',
- 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: () => ({}),
- },
- groupRowList: {
- type: Array,
- default: () => [],
- },
- isShowGroup: {
- type: Boolean,
- default: false,
- },
- groupShowAll: {
- type: Boolean,
- default: true,
- },
- },
- data() {
- return {
- previewComponentList,
- bookInfo: {
- theme_color: '',
- },
- showMenu: false,
- divPosition: {
- left: 0,
- top: 0,
- }, // courseware盒子原始距离页面顶部和左边的距离
- menuPosition: { x: 0, y: 0, select_node: '' }, // 用于存储菜单的位置
- componentId: '', // 添加批注的组件id
- rowCheckList: {},
- };
- },
- watch: {
- groupRowList: {
- handler(val) {
- if (!val) return;
- this.rowCheckList = val
- .filter(({ is_pre_same_group }) => !is_pre_same_group)
- .reduce((acc, row) => {
- acc[row.row_id] = false;
- return acc;
- }, {});
- },
- },
- },
- mounted() {
- const element = this.$refs.courseware;
- const rect = element.getBoundingClientRect();
- this.divPosition = {
- left: rect.left,
- top: rect.top,
- };
- },
- 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',
- };
- },
- /**
- * 计算行的可见性
- * @params {string} rowId 行的ID
- * @returns {boolean} 行是否可见
- */
- computedRowVisibility(rowId) {
- if (this.groupShowAll) return true;
- let { row_id, is_pre_same_group } = this.groupRowList.find(({ row_id }) => row_id === rowId);
- if (is_pre_same_group) {
- const index = this.groupRowList.findIndex(({ row_id }) => row_id === rowId);
- if (index === -1) return false;
- for (let i = index - 1; i >= 0; i--) {
- if (!this.groupRowList[i].is_pre_same_group) {
- return this.rowCheckList[this.groupRowList[i].row_id];
- }
- }
- return false;
- }
- return this.rowCheckList[row_id];
- },
- /**
- * 分割整数为多个 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 || [];
- if (!grid.length) {
- return {
- width: col.width,
- gridTemplateAreas: '',
- gridTemplateColumns: '',
- gridTemplateRows: '',
- };
- }
- // 一次遍历完成按行分组,并记录最大列数所在行
- let maxCol = 0;
- let curMaxRow = 0;
- const rowMap = new Map();
- grid.forEach((item) => {
- if (!rowMap.has(item.row)) {
- rowMap.set(item.row, []);
- }
- const rowItems = rowMap.get(item.row);
- rowItems.push(item);
- if (rowItems.length > maxCol) {
- maxCol = rowItems.length;
- curMaxRow = item.row;
- }
- });
- // 计算 grid_template_areas
- let gridTemplateAreas = '';
- const sortedRows = Array.from(rowMap.keys()).sort((a, b) => a - b);
- sortedRows.forEach((row) => {
- const rowItems = rowMap.get(row);
- const rowAreas = [];
- if (row === curMaxRow) {
- rowItems.forEach(({ grid_area }) => {
- rowAreas.push(`${grid_area}`);
- });
- } else {
- const needNum = maxCol - rowItems.length;
- const splitList = rowItems.length > 1 ? this.splitInteger(needNum, rowItems.length) : [];
- rowItems.forEach(({ grid_area }, index) => {
- if (rowItems.length === 1) {
- rowAreas.push(` ${grid_area} `.repeat(needNum + 1));
- return;
- }
- const repeatNum = splitList[index] + 1;
- rowAreas.push(splitList[index] === 0 ? ` ${grid_area} ` : ` ${grid_area} `.repeat(repeatNum));
- });
- }
- gridTemplateAreas += `'${rowAreas.join(' ')}' `;
- });
- // 计算 grid_template_columns
- const maxRowItems = rowMap.get(curMaxRow) || [];
- const gridTemplateColumns = maxRowItems.map(({ width }) => width).join(' ');
- // 移动端高度统一使用 auto
- const gridTemplateRows = `${sortedRows.map(() => 'auto').join(' ')} `;
- return {
- width: col.width,
- gridTemplateAreas,
- gridTemplateColumns,
- gridTemplateRows,
- };
- },
- /**
- * 查找子组件
- * @param {string} id 组件的唯一标识符
- * @returns {Promise<HTMLElement|null>} 返回找到的子组件或 null
- */
- async findChildComponentByKey(id) {
- await this.$nextTick();
- return this.$refs.preview.find((child) => child.dataset.id === id);
- },
- /**
- * 模拟回答
- * @param {boolean} isJudgingRightWrong 是否判断对错
- * @param {boolean} isShowRightAnswer 是否显示正确答案
- * @param {boolean} disabled 是否禁用
- */
- simulateAnswer(isJudgingRightWrong, isShowRightAnswer, disabled = true) {
- this.$refs.preview.forEach((item) => {
- item.showAnswer(isJudgingRightWrong, isShowRightAnswer, null, disabled);
- });
- },
- /**
- * 获取网格 id,是第几行第几列第几个网格,通过 data.row_list 计算
- * @param {string} id 网格 id
- * @returns {number} 网格的显示顺序
- */
- computedGridViewOrder(id) {
- 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;
- },
- },
- };
- </script>
- <style lang="scss">
- .courseware {
- position: relative;
- display: flex;
- flex-direction: column;
- row-gap: 6px;
- width: 100%;
- height: 100%;
- min-height: 500px;
- padding: 2%;
- background-color: #fff;
- background-repeat: no-repeat;
- border-bottom-right-radius: 12px;
- border-bottom-left-radius: 12px;
- .row {
- display: flex;
- flex-direction: column;
- gap: 16px;
- .col {
- display: flex;
- flex-direction: column;
- gap: 16px;
- overflow: hidden;
- .grid {
- width: 100%;
- }
- .main {
- .option-list {
- flex-direction: column !important;
- }
- }
- }
- .row-checkbox {
- position: absolute;
- left: 4px;
- }
- }
- .custom-context-menu,
- .remark-info {
- position: absolute;
- z-index: 999;
- display: flex;
- gap: 3px;
- align-items: center;
- font-size: 14px;
- cursor: pointer;
- }
- .custom-context-menu {
- padding-left: 30px;
- background: url('../../../../assets/icon-publish.png') left center no-repeat;
- background-size: 24px;
- }
- }
- </style>
|