PinyinText.vue 7.9 KB

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