123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- <template>
- <div class="describe-preview" :style="getAreaStyle()">
- <SerialNumberPosition v-if="isEnable(data.property.sn_display_mode)" :property="data.property" />
- <div class="main">
- <div ref="leftDiv" :style="{ width: data.note_list.length > 0 ? '' : '100%' }">
- <PinyinText
- v-if="isEnable(data.property.view_pinyin)"
- :paragraph-list="data.paragraph_list"
- :pinyin-position="data.property.pinyin_position"
- :is-preview="isPreview"
- />
- <span v-else class="rich-text" @click="handleRichFillClick" v-html="sanitizeHTML(data.content)"></span>
- </div>
- <div v-show="showLang" class="lang">
- {{ data.multilingual.find((item) => item.code === getLang())?.translation }}
- </div>
- </div>
- <el-dialog
- ref="optimizedDialog"
- title=""
- :visible.sync="noteDialogVisible"
- width="680px"
- :style="dialogStyle"
- :close-on-click-modal="false"
- destroy-on-close
- @close="noteDialogVisible = false"
- >
- <span v-html="sanitizeHTML(selectedNote)"></span>
- </el-dialog>
- </div>
- </template>
- <script>
- import { getRichTextData } from '@/views/book/courseware/data/richText';
- import PreviewMixin from '../common/PreviewMixin';
- import { isEnable } from '@/views/book/courseware/data/common';
- import PinyinText from '@/components/PinyinText.vue';
- export default {
- name: 'RichTextPreview',
- components: { PinyinText },
- mixins: [PreviewMixin],
- data() {
- return {
- isEnable,
- data: getRichTextData(),
- isPreview: true,
- divHeight: 'auto',
- observer: null,
- noteDialogVisible: false,
- selectedNote: '',
- dialogStyle: {
- position: 'fixed',
- top: '0',
- left: '0',
- margin: '0',
- },
- };
- },
- mounted() {
- this.observer = new ResizeObserver(() => {
- this.updateHeight();
- });
- this.observer.observe(this.$refs.leftDiv.closest('.describe-preview'));
- },
- beforeDestroy() {
- this.observer.disconnect();
- },
- methods: {
- handleRichFillClick(event) {
- // 检查点击的元素是否是 rich-fill 或者其子元素
- const richFillElement = event.target.closest('.rich-fill');
- if (richFillElement) {
- // 处理点击事件
- let selectedNoteId = richFillElement.dataset.annotationId;
- if (this.data.note_list.some((p) => p.id === selectedNoteId)) {
- this.noteDialogVisible = true;
- this.selectedNote = this.data.note_list.find((p) => p.id === selectedNoteId).note;
- }
- } else {
- this.selectedNote = '';
- this.noteDialogVisible = false;
- }
- this.$nextTick(() => {
- const dialogElement = this.$refs.optimizedDialog;
- // 确保对话框DOM已渲染
- if (!dialogElement) {
- return;
- }
- // 获取对话框内容区域的DOM元素
- const dialogContent = dialogElement.$el.querySelector('.el-dialog');
- if (!dialogContent) {
- return;
- }
- const dialogRect = dialogContent.getBoundingClientRect();
- const dialogWidth = dialogRect.width;
- const dialogHeight = dialogRect.height;
- const padding = 10; // 安全边距
- const clickX = event.clientX;
- const clickY = event.clientY;
- const windowWidth = window.innerWidth;
- const windowHeight = window.innerHeight;
- // 水平定位 - 中心对齐
- let left = clickX - dialogWidth / 2;
- // 边界检查
- left = Math.max(padding, Math.min(left, windowWidth - dialogWidth - padding));
- // 垂直定位 - 点击位置作为下边界中心
- let top = clickY - dialogHeight;
- // 上方空间不足时,改为向下展开
- if (top < padding) {
- top = clickY + padding;
- // 如果向下展开会超出屏幕,则贴底部显示
- if (top + dialogHeight > windowHeight - padding) {
- top = windowHeight - dialogHeight - padding;
- }
- }
- this.dialogStyle = {
- position: 'fixed',
- top: `${top - 20}px`,
- left: `${left}px`,
- margin: '0',
- transform: 'none',
- };
- });
- },
- updateHeight() {
- this.$nextTick(() => {
- const target = this.$refs.leftDiv;
- const parent = target.closest('.describe-preview');
- if (!parent) return;
- setTimeout(() => {
- this.divHeight = parent.offsetHeight - 16;
- }, 800);
- });
- },
- },
- };
- </script>
- <style lang="scss" scoped>
- @use '@/styles/mixin.scss' as *;
- .describe-preview {
- @include preview-base;
- :deep .el-dialog {
- position: fixed;
- margin: 0 !important;
- transition: all 0.2s; /* 添加平滑过渡效果 */
- .el-dialog__header {
- padding: 0 !important;
- .el-dialog__headerbtn {
- top: 6px !important;
- right: 6px !important;
- }
- }
- .el-dialog__body {
- padding: 10px !important;
- }
- }
- }
- </style>
|