CustomEditor.vue 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. <template>
  2. <view class="editor_wrap">
  3. <textarea
  4. class="editor_component"
  5. id="editor2"
  6. ref="editor2"
  7. :value="textValue"
  8. :placeholder="placeholder"
  9. :maxlength="-1"
  10. :adjust-position="false"
  11. :show-confirm-bar="false"
  12. :auto-height="true"
  13. :disable-default-padding="true"
  14. @input="onInput"
  15. @focus="onFocus"
  16. @blur="onBlur"
  17. @confirm="onConfirm"
  18. />
  19. </view>
  20. </template>
  21. <script>
  22. export default {
  23. props: {
  24. placeholder: {
  25. type: String,
  26. default: ''
  27. }
  28. },
  29. data() {
  30. return {
  31. textValue: '',
  32. cursorPos: -1,
  33. };
  34. },
  35. mounted() {
  36. this.$nextTick(() => {
  37. const mockCtx = {
  38. clear: () => {
  39. this.textValue = '';
  40. this.$emit('input', { detail: { html: '' } });
  41. },
  42. undo: () => {
  43. // nvue textarea 暂不支持撤销
  44. }
  45. };
  46. this.$emit('ready', { context: mockCtx });
  47. });
  48. },
  49. methods: {
  50. /**
  51. * 输入事件处理
  52. */
  53. onInput(e) {
  54. this.textValue = e.detail.value;
  55. this.cursorPos = e.detail.cursor;
  56. // 保持与 editor 相同的事件格式
  57. this.$emit('input', { detail: { html: this.textValue } });
  58. // 检测 @ 输入
  59. if (e.detail.cursor > 0) {
  60. const lastChar = this.textValue.slice(e.detail.cursor - 1, e.detail.cursor);
  61. if (lastChar === '@') {
  62. this.$emit('tryAt');
  63. }
  64. }
  65. },
  66. /**
  67. * 聚焦事件
  68. */
  69. onFocus(e) {
  70. this.$emit('focus');
  71. },
  72. /**
  73. * 失焦事件
  74. */
  75. onBlur(e) {
  76. this.cursorPos = e.detail.cursor;
  77. this.$emit('blur');
  78. },
  79. /**
  80. * 确认事件(如点击键盘完成)
  81. */
  82. onConfirm(e) {
  83. // 如果需要处理回车发送,可以在这里添加逻辑
  84. },
  85. /**
  86. * 插入表情(文本形式)
  87. */
  88. insertEmoji(emoji) {
  89. if (this.cursorPos < 0) {
  90. this.textValue += emoji;
  91. } else {
  92. const left = this.textValue.substring(0, this.cursorPos);
  93. const right = this.textValue.substring(this.cursorPos);
  94. this.textValue = left + emoji + right;
  95. this.cursorPos += emoji.length;
  96. }
  97. this.$emit('input', { detail: { html: this.textValue } });
  98. },
  99. /**
  100. * 插入 @ 人员(文本形式,替代原本的 canvas 图片方案)
  101. */
  102. createCanvasData(userID, nickname) {
  103. const atText = `@${nickname} `;
  104. if (this.cursorPos < 0) {
  105. this.textValue += atText;
  106. } else {
  107. // 如果光标前已经是 @,则替换它?
  108. // 简单起见,直接插入
  109. const left = this.textValue.substring(0, this.cursorPos);
  110. const right = this.textValue.substring(this.cursorPos);
  111. this.textValue = left + atText + right;
  112. this.cursorPos += atText.length;
  113. }
  114. this.$emit('input', { detail: { html: this.textValue } });
  115. },
  116. /**
  117. * 聚焦方法
  118. */
  119. focus() {
  120. // nvue textarea 通常无法通过方法聚焦,依赖属性 focus
  121. // 如果需要,可以在 data 中添加 focus 属性绑定到 textarea
  122. }
  123. }
  124. };
  125. </script>
  126. <style lang="scss" scoped>
  127. .editor_wrap {
  128. position: relative;
  129. flex: 1;
  130. flex-direction: column;
  131. }
  132. #editor2 {
  133. flex: 1;
  134. width: 100%;
  135. background-color: #fff;
  136. min-height: 72rpx;
  137. max-height: 240rpx;
  138. padding-left: 10rpx;
  139. padding-right: 10rpx;
  140. padding-top: 20rpx;
  141. padding-bottom: 20rpx;
  142. font-size: 32rpx;
  143. color: #333;
  144. /* 垂直居中可以通过 line-height 或者 flex 布局辅助,但在 nvue textarea 中 padding 是最直接的 */
  145. }
  146. </style>