PinyinText.vue 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. <template>
  2. <div class="pinyin-area">
  3. <div v-for="(paragraph, i) in paragraphList" :key="i" class="pinyin-paragraph">
  4. <span
  5. v-for="(sentence, j) in paragraph"
  6. :key="j"
  7. class="pinyin-sentence"
  8. :style="{ 'align-items': pinyinPosition === 'top' ? 'flex-end' : 'flex-start' }"
  9. >
  10. <span v-for="(item, k) in sentence" :key="k" class="pinyin-text">
  11. <span
  12. :class="{ active: visible && word_index == k && paragraph_index === i && sentence_index === j }"
  13. :title="isPreview ? '' : '点击校对'"
  14. :style="{
  15. cursor: isPreview ? '' : 'pointer',
  16. 'align-items': k == 0 ? 'flex-start' : 'center',
  17. }"
  18. @click="correctPinyin(item, i, j, k)"
  19. >
  20. <span v-if="pinyinPosition === 'top'" class="pinyin">{{ item.pinyin.replace(/\s+/g, '') }}</span>
  21. <span class="py-char" :style="{ ...item.activeTextStyle }">{{ item.text }}</span>
  22. <span v-if="pinyinPosition !== 'top'" class="pinyin">{{ item.pinyin.replace(/\s+/g, '') }}</span>
  23. </span>
  24. </span>
  25. </span>
  26. </div>
  27. <CorrectPinyin
  28. :visible.sync="visible"
  29. :select-content="selectContent"
  30. :component-type="componentType"
  31. @fillTonePinyin="fillTonePinyin"
  32. />
  33. <el-dialog
  34. ref="optimizedDialog"
  35. title=""
  36. :visible.sync="noteDialogVisible"
  37. width="680px"
  38. :style="dialogStyle"
  39. :close-on-click-modal="false"
  40. destroy-on-close
  41. @close="noteDialogVisible = false"
  42. >
  43. <span v-html="sanitizeHTML(note)"></span>
  44. </el-dialog>
  45. </div>
  46. </template>
  47. <script>
  48. import CorrectPinyin from '@/views/book/courseware/create/components/base/common/CorrectPinyin.vue';
  49. import { sanitizeHTML } from '@/utils/common';
  50. export default {
  51. name: 'PinyinText',
  52. components: {
  53. CorrectPinyin,
  54. },
  55. props: {
  56. paragraphList: {
  57. type: Array,
  58. required: true,
  59. },
  60. pinyinPosition: {
  61. type: String,
  62. required: true,
  63. },
  64. isPreview: {
  65. type: Boolean,
  66. default: false,
  67. },
  68. componentType: {
  69. type: String,
  70. default: '',
  71. },
  72. },
  73. data() {
  74. return {
  75. sanitizeHTML,
  76. paragraph_list: [],
  77. visible: false,
  78. selectContent: {},
  79. paragraph_index: 0,
  80. sentence_index: 0,
  81. word_index: 0,
  82. noteDialogVisible: false,
  83. note: '',
  84. dialogStyle: {
  85. position: 'fixed',
  86. top: '0',
  87. left: '0',
  88. margin: '0',
  89. },
  90. };
  91. },
  92. methods: {
  93. // 校对拼音
  94. correctPinyin(item, i, j, k) {
  95. if (this.isPreview) {
  96. if (item.note) {
  97. this.note = item.note;
  98. this.noteDialogVisible = true;
  99. this.$nextTick(() => {
  100. const dialogElement = this.$refs.optimizedDialog;
  101. // 确保对话框DOM已渲染
  102. if (!dialogElement) {
  103. return;
  104. }
  105. // 获取对话框内容区域的DOM元素
  106. const dialogContent = dialogElement.$el.querySelector('.el-dialog');
  107. if (!dialogContent) {
  108. return;
  109. }
  110. const dialogRect = dialogContent.getBoundingClientRect();
  111. const dialogWidth = dialogRect.width;
  112. const dialogHeight = dialogRect.height;
  113. const padding = 10; // 安全边距
  114. const clickX = event.clientX;
  115. const clickY = event.clientY;
  116. const windowWidth = window.innerWidth;
  117. const windowHeight = window.innerHeight;
  118. // 水平定位 - 中心对齐
  119. let left = clickX - dialogWidth / 2;
  120. // 边界检查
  121. left = Math.max(padding, Math.min(left, windowWidth - dialogWidth - padding));
  122. // 垂直定位 - 点击位置作为下边界中心
  123. let top = clickY - dialogHeight;
  124. // 上方空间不足时,改为向下展开
  125. if (top < padding) {
  126. top = clickY + padding;
  127. // 如果向下展开会超出屏幕,则贴底部显示
  128. if (top + dialogHeight > windowHeight - padding) {
  129. top = windowHeight - dialogHeight - padding;
  130. }
  131. }
  132. this.dialogStyle = {
  133. position: 'fixed',
  134. top: `${top - 20}px`,
  135. left: `${left}px`,
  136. margin: '0',
  137. transform: 'none',
  138. };
  139. });
  140. }
  141. return;
  142. } // 如果是预览模式,不操作
  143. if (item) {
  144. this.visible = true;
  145. this.selectContent = item;
  146. this.paragraph_index = i;
  147. this.sentence_index = j;
  148. this.word_index = k;
  149. }
  150. },
  151. // 回填校对后的拼音
  152. fillTonePinyin(dataContent) {
  153. this.$emit('fillCorrectPinyin', {
  154. selectContent: dataContent,
  155. i: this.paragraph_index,
  156. j: this.sentence_index,
  157. k: this.word_index,
  158. });
  159. },
  160. },
  161. };
  162. </script>
  163. <style lang="scss" scoped>
  164. .pinyin-area {
  165. .pinyin-paragraph {
  166. .pinyin-sentence {
  167. .pinyin-text {
  168. padding: 0 2px;
  169. font-size: 16px;
  170. text-wrap: pretty;
  171. hanging-punctuation: allow-end;
  172. > span {
  173. display: inline-flex;
  174. flex-direction: column;
  175. align-items: center;
  176. }
  177. .py-char {
  178. ruby-align: center;
  179. }
  180. .pinyin {
  181. font-family: 'PINYIN-B';
  182. font-size: 12px;
  183. font-weight: lighter;
  184. line-height: 12px;
  185. color: $font-color;
  186. }
  187. }
  188. }
  189. }
  190. .active {
  191. color: rgb(242, 85, 90) !important;
  192. }
  193. }
  194. </style>
  195. <style lang="scss">
  196. .pinyin-area + .pinyin-area {
  197. margin-top: 4px;
  198. }
  199. </style>