PinyinText.vue 7.0 KB

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