CustomTextarea.vue 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. <template>
  2. <view class="textarea_wrap">
  3. <textarea
  4. class="custom_textarea"
  5. :value="inputValue"
  6. :placeholder="placeholder"
  7. :maxlength="-1"
  8. :auto-height="true"
  9. :show-confirm-bar="false"
  10. :adjust-position="false"
  11. :cursor-spacing="20"
  12. @input="handleInput"
  13. @focus="handleFocus"
  14. @blur="handleBlur"
  15. @confirm="handleConfirm"
  16. />
  17. </view>
  18. </template>
  19. <script>
  20. export default {
  21. props: {
  22. placeholder: {
  23. type: String,
  24. default: ''
  25. },
  26. value: {
  27. type: String,
  28. default: ''
  29. }
  30. },
  31. data() {
  32. return {
  33. inputValue: '',
  34. cursor: -1
  35. };
  36. },
  37. watch: {
  38. value: {
  39. immediate: true,
  40. handler(val) {
  41. this.inputValue = val;
  42. }
  43. }
  44. },
  45. methods: {
  46. handleInput(e) {
  47. const value = e.detail.value;
  48. this.inputValue = value;
  49. this.cursor = e.detail.cursor;
  50. // 检测 @ 输入
  51. const lastChar = value.slice(this.cursor - 1, this.cursor);
  52. if (lastChar === '@') {
  53. this.$emit('tryAt');
  54. }
  55. this.$emit('input', e);
  56. },
  57. handleFocus(e) {
  58. this.$emit('focus', e);
  59. },
  60. handleBlur(e) {
  61. this.$emit('blur', e);
  62. },
  63. handleConfirm(e) {
  64. this.$emit('confirm', e);
  65. },
  66. // 供外部调用的方法
  67. insertEmoji(emoji) {
  68. // 在光标处插入 emoji
  69. const value = this.inputValue;
  70. // 如果没有光标位置,默认追加到末尾
  71. const cursor = this.cursor >= 0 ? this.cursor : value.length;
  72. const newValue = value.slice(0, cursor) + emoji + value.slice(cursor);
  73. this.inputValue = newValue;
  74. // 更新光标位置
  75. this.cursor = cursor + emoji.length;
  76. // 触发 input 事件
  77. this.$emit('input', { detail: { value: newValue, cursor: this.cursor } });
  78. },
  79. insertAt(nickname) {
  80. // 在光标处插入 @Name
  81. // 简单处理:直接插入 @nickname + 空格
  82. // 如果需要处理用户已经输入了 @ 的情况,需要更复杂的判断(比如判断光标前一个字符是否为 @)
  83. const atText = `@${nickname} `;
  84. this.insertEmoji(atText);
  85. },
  86. createCanvasData(userID, nickname) {
  87. // 兼容 CustomEditor 的接口
  88. this.insertAt(nickname);
  89. },
  90. clear() {
  91. this.inputValue = '';
  92. this.cursor = -1;
  93. this.$emit('input', { detail: { value: '', cursor: -1 } });
  94. },
  95. backspace() {
  96. const value = this.inputValue || '';
  97. const cursor = this.cursor >= 0 ? this.cursor : value.length;
  98. if (cursor === 0) return;
  99. // 使用 Array.from 处理 surrogate pairs (emoji)
  100. const left = value.slice(0, cursor);
  101. const right = value.slice(cursor);
  102. const leftArr = Array.from(left);
  103. if (leftArr.length === 0) return;
  104. leftArr.pop();
  105. const newLeft = leftArr.join('');
  106. const newValue = newLeft + right;
  107. this.inputValue = newValue;
  108. // cursor 更新为新左侧字符串长度
  109. this.cursor = newLeft.length;
  110. this.$emit('input', { detail: { value: newValue, cursor: this.cursor } });
  111. },
  112. focus() {
  113. // textarea 无法通过 JS 强制聚焦,只能依赖用户操作或 auto-focus
  114. // 但我们可以尝试通过 v-if 刷新组件来触发 auto-focus,或者使用 uni.hideKeyboard() 后再 show
  115. }
  116. }
  117. };
  118. </script>
  119. <style lang="scss" scoped>
  120. .textarea_wrap {
  121. width: 100%;
  122. min-height: 30px;
  123. display: flex;
  124. align-items: center;
  125. background-color: #fff;
  126. border-radius: 4px;
  127. padding: 4px 8px;
  128. box-sizing: border-box; /* 确保 padding 不会撑大宽度 */
  129. }
  130. .custom_textarea {
  131. width: 100%;
  132. min-height: 20px; /* 调整最小高度,配合 padding */
  133. max-height: 100px; /* 限制最大高度 */
  134. line-height: 20px;
  135. font-size: 16px;
  136. padding: 0; /* padding 移到 wrap 上 */
  137. background-color: transparent; /* 背景透明,显示 wrap 的背景 */
  138. }
  139. </style>