PinyinText.vue 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. <template>
  2. <div class="pinyin-area" :style="{ 'text-align': pinyinOverallPosition }">
  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. pinyinOverallPosition: {
  65. type: String,
  66. required: true,
  67. },
  68. isPreview: {
  69. type: Boolean,
  70. default: false,
  71. },
  72. componentType: {
  73. type: String,
  74. default: '',
  75. },
  76. },
  77. data() {
  78. return {
  79. sanitizeHTML,
  80. paragraph_list: [],
  81. visible: false,
  82. selectContent: {},
  83. paragraph_index: 0,
  84. sentence_index: 0,
  85. word_index: 0,
  86. noteDialogVisible: false,
  87. note: '',
  88. dialogStyle: {
  89. position: 'fixed',
  90. top: '0',
  91. left: '0',
  92. margin: '0',
  93. },
  94. };
  95. },
  96. methods: {
  97. // 校对拼音
  98. correctPinyin(item, i, j, k) {
  99. if (this.isPreview) {
  100. if (item.note) {
  101. this.note = item.note;
  102. this.noteDialogVisible = true;
  103. this.$nextTick(() => {
  104. const dialogElement = this.$refs.optimizedDialog;
  105. // 确保对话框DOM已渲染
  106. if (!dialogElement) {
  107. return;
  108. }
  109. // 获取对话框内容区域的DOM元素
  110. const dialogContent = dialogElement.$el.querySelector('.el-dialog');
  111. if (!dialogContent) {
  112. return;
  113. }
  114. const dialogRect = dialogContent.getBoundingClientRect();
  115. const dialogWidth = dialogRect.width;
  116. const dialogHeight = dialogRect.height;
  117. const padding = 10; // 安全边距
  118. const clickX = event.clientX;
  119. const clickY = event.clientY;
  120. const windowWidth = window.innerWidth;
  121. const windowHeight = window.innerHeight;
  122. // 水平定位 - 中心对齐
  123. let left = clickX - dialogWidth / 2;
  124. // 边界检查
  125. left = Math.max(padding, Math.min(left, windowWidth - dialogWidth - padding));
  126. // 垂直定位 - 点击位置作为下边界中心
  127. let top = clickY - dialogHeight;
  128. // 上方空间不足时,改为向下展开
  129. if (top < padding) {
  130. top = clickY + padding;
  131. // 如果向下展开会超出屏幕,则贴底部显示
  132. if (top + dialogHeight > windowHeight - padding) {
  133. top = windowHeight - dialogHeight - padding;
  134. }
  135. }
  136. this.dialogStyle = {
  137. position: 'fixed',
  138. top: `${top - 20}px`,
  139. left: `${left}px`,
  140. margin: '0',
  141. transform: 'none',
  142. };
  143. });
  144. }
  145. return;
  146. } // 如果是预览模式,不操作
  147. if (item) {
  148. this.visible = true;
  149. this.selectContent = item;
  150. this.paragraph_index = i;
  151. this.sentence_index = j;
  152. this.word_index = k;
  153. }
  154. },
  155. // 回填校对后的拼音
  156. fillTonePinyin(dataContent) {
  157. this.$emit('fillCorrectPinyin', {
  158. selectContent: dataContent,
  159. i: this.paragraph_index,
  160. j: this.sentence_index,
  161. k: this.word_index,
  162. });
  163. },
  164. },
  165. };
  166. </script>
  167. <style lang="scss" scoped>
  168. .pinyin-area {
  169. .pinyin-paragraph {
  170. .pinyin-sentence {
  171. .pinyin-text {
  172. padding: 0 2px;
  173. font-size: 16px;
  174. text-wrap: pretty;
  175. hanging-punctuation: allow-end;
  176. > span {
  177. display: inline-flex;
  178. flex-direction: column;
  179. align-items: center;
  180. }
  181. .py-char {
  182. ruby-align: center;
  183. }
  184. .pinyin {
  185. font-family: 'PINYIN-B';
  186. font-size: 12px;
  187. font-weight: lighter;
  188. line-height: 12px;
  189. color: $font-color;
  190. }
  191. }
  192. }
  193. }
  194. .active {
  195. color: rgb(242, 85, 90) !important;
  196. }
  197. }
  198. </style>
  199. <style lang="scss">
  200. .pinyin-area + .pinyin-area {
  201. margin-top: 4px;
  202. }
  203. </style>