CustomEditor.vue 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. <template>
  2. <view
  3. :insertImageFlag="insertImageFlag"
  4. :insertAtFlag="insertAtFlag"
  5. :insertEmojiFlag="insertEmojiFlag"
  6. :change:insertEmojiFlag="input.insertEmojiFlagUpdate"
  7. :change:insertAtFlag="input.insertAtFlagUpdate"
  8. :change:insertImageFlag="input.insertImageFlagUpdate"
  9. class="editor_wrap"
  10. >
  11. <editor :placeholder="placeholder" id="editor2" class="editor2" @ready="editorReady" @focus="editorFocus" @blur="editorBlur" @input="editorInput" />
  12. <view class="canvas_container">
  13. <canvas v-if="canvasData.show" canvas-id="atCanvas" :style="{ width: canvasData.width }" id="atCanvas"></canvas>
  14. </view>
  15. </view>
  16. </template>
  17. <script>
  18. import { html2Text } from '../../../../../util/common';
  19. export default {
  20. props: {
  21. placeholder: {
  22. type: String,
  23. default: ''
  24. }
  25. },
  26. data() {
  27. return {
  28. editorCtx: null,
  29. canvasData: {
  30. width: 0,
  31. show: false
  32. },
  33. imageData: {},
  34. insertImageFlag: null,
  35. insertAtFlag: null,
  36. lastStr: '',
  37. emojiData: '',
  38. insertEmojiFlag: null
  39. };
  40. },
  41. beforeDestroy() {
  42. this.editorCtx = null; // 释放上下文
  43. },
  44. methods: {
  45. editorReady() {
  46. // uni.createSelectorQuery()
  47. // .select("#editor2")
  48. // .context((res) => {
  49. // this.$emit("ready", res);
  50. // this.editorCtx = res.context;
  51. // }).exec();
  52. this.$nextTick(() => {
  53. // 等待 DOM 更新
  54. uni.createSelectorQuery()
  55. .in(this)
  56. .select('#editor2')
  57. .context((res) => {
  58. this.$emit('ready', res);
  59. this.editorCtx = res.context;
  60. })
  61. .exec();
  62. });
  63. },
  64. insertImage(imageData) {
  65. this.imageData = imageData;
  66. this.insertImageFlag = true;
  67. this.internalInsertImage()
  68. },
  69. insertEmoji(emoji) {
  70. this.$nextTick(() => {
  71. this.insertEmojiFlag = true;
  72. this.emojiData = emoji;
  73. this.internalInsertEmoji()
  74. });
  75. },
  76. internalInsertEmoji() {
  77. this.editorCtx.insertText({
  78. text: this.emojiData,
  79. complete: () => {
  80. this.insertEmojiFlag = false;
  81. this.emojiData = null;
  82. }
  83. });
  84. },
  85. internalInsertImage() {
  86. this.editorCtx.insertImage({
  87. ...this.imageData,
  88. complete: () => {
  89. this.insertImageFlag = false;
  90. this.insertAtFlag = null;
  91. // plus.key.showSoftKeybord()
  92. // this.setDraftTextItem();
  93. }
  94. });
  95. },
  96. internalInsertAtEl({ text, width, userID, nickname }) {
  97. this.canvasData.width = `${width}px`;
  98. this.canvasData.show = true;
  99. setTimeout(() => {
  100. const ctx = uni.createCanvasContext('atCanvas');
  101. const fontSize = 14;
  102. ctx.setFontSize(fontSize);
  103. ctx.setFillStyle('#3e44ff');
  104. ctx.fillText(text, 0, 16);
  105. ctx.draw();
  106. this.canvasToTempFilePath(userID, nickname, width);
  107. }, 20);
  108. },
  109. createCanvasData(userID, nickname) {
  110. this.$nextTick(() => {
  111. this.insertAtFlag = {
  112. userID,
  113. nickname
  114. };
  115. this.internalInsertAtEl()
  116. });
  117. },
  118. canvasToTempFilePath(sendID, senderNickname, width) {
  119. uni.canvasToTempFilePath({
  120. canvasId: 'atCanvas',
  121. success: (res) => {
  122. this.insertImage({
  123. src: res.tempFilePath,
  124. width,
  125. height: '20px',
  126. data: {
  127. sendID,
  128. senderNickname
  129. },
  130. extClass: 'at_el'
  131. });
  132. uni.createCanvasContext('atCanvas').clearRect();
  133. }
  134. });
  135. },
  136. editorFocus() {
  137. this.$emit('focus');
  138. },
  139. editorBlur() {
  140. this.$emit('blur');
  141. },
  142. editorInput(e) {
  143. let str = e.detail.html;
  144. const oldArr = (this.lastStr ?? '').split('');
  145. let contentStr = str;
  146. oldArr.forEach((str) => {
  147. contentStr = contentStr.replace(str, '');
  148. });
  149. contentStr = html2Text(contentStr);
  150. if (contentStr === '@') {
  151. this.$emit('tryAt');
  152. }
  153. this.$emit('input', e);
  154. this.lastStr = e.detail.html;
  155. }
  156. }
  157. };
  158. </script>
  159. <script module="input" lang="renderjs">
  160. export default {
  161. methods: {
  162. insertEmojiFlagUpdate(newValue, oldValue, ownerVm, vm) {
  163. if (newValue === null) {
  164. return;
  165. }
  166. if (newValue) {
  167. this.$el.querySelector('.ql-editor').setAttribute('inputmode', 'none')
  168. ownerVm.callMethod('internalInsertEmoji')
  169. } else {
  170. this.$el.querySelector('.ql-editor').removeAttribute('inputmode')
  171. }
  172. },
  173. insertImageFlagUpdate(newValue, oldValue, ownerVm, vm) {
  174. if (newValue === null) {
  175. return;
  176. }
  177. if (newValue) {
  178. this.$el.querySelector('.ql-editor').setAttribute('inputmode', 'none')
  179. ownerVm.callMethod('internalInsertImage')
  180. } else {
  181. this.$el.querySelector('.ql-editor').removeAttribute('inputmode')
  182. }
  183. },
  184. insertAtFlagUpdate(newValue, oldValue, ownerVm, vm){
  185. if (newValue === null) {
  186. return;
  187. }
  188. if (newValue) {
  189. const data = this.truncateText(`@${newValue.nickname}`,120)
  190. ownerVm.callMethod('internalInsertAtEl', {...data,...newValue})
  191. }
  192. },
  193. truncateText(text, maxWidth) {
  194. const container = document.createElement("div");
  195. container.style.width = "auto";
  196. container.style.overflow = "hidden";
  197. container.style.textOverflow = "ellipsis";
  198. container.style.whiteSpace = "nowrap";
  199. container.style.position = "absolute";
  200. container.style.visibility = "hidden";
  201. const textNode = document.createTextNode(text);
  202. container.appendChild(textNode);
  203. document.body.appendChild(container);
  204. const isOverflowing = container.scrollWidth > maxWidth;
  205. if (!isOverflowing) {
  206. const width = container.clientWidth + 4
  207. document.body.removeChild(container);
  208. return {
  209. text,
  210. width
  211. };
  212. }
  213. container.style.width = maxWidth + "px";
  214. container.style.visibility = "visible";
  215. let truncatedText = text;
  216. while (container.scrollWidth > maxWidth) {
  217. truncatedText = truncatedText.slice(0, -1);
  218. container.textContent = truncatedText + "...";
  219. }
  220. document.body.removeChild(container);
  221. return {
  222. text: `${truncatedText}...`,
  223. width: maxWidth + 4
  224. };
  225. }
  226. },
  227. }
  228. </script>
  229. <style lang="scss" scoped>
  230. .editor_wrap {
  231. position: relative;
  232. padding: 10rpx;
  233. background-color: #fff;
  234. }
  235. .editor2 {
  236. background-color: #fff;
  237. min-height: 1rem;
  238. max-height: 4rem;
  239. height: auto;
  240. word-break: break-all;
  241. }
  242. /deep/.ql-editor {
  243. img {
  244. vertical-align: sub !important;
  245. }
  246. p {
  247. padding: 4px;
  248. }
  249. }
  250. .canvas_container {
  251. position: fixed;
  252. bottom: -99px;
  253. z-index: -100;
  254. &_name {
  255. max-width: 480rpx;
  256. display: inline-block;
  257. overflow: hidden;
  258. text-overflow: ellipsis;
  259. white-space: nowrap;
  260. }
  261. #atCanvas {
  262. height: 20px;
  263. }
  264. .convas_container_name {
  265. font-size: 16px !important;
  266. }
  267. }
  268. </style>