u-textarea.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. <template>
  2. <view class="u-textarea" :class="textareaClass" :style="[textareaStyle]">
  3. <textarea
  4. class="u-textarea__field"
  5. :value="innerValue"
  6. :style="{ height: $u.addUnit(height) }"
  7. :placeholder="placeholder"
  8. :placeholder-style="placeholderStyle"
  9. :placeholder-class="placeholderClass"
  10. :disabled="disabled"
  11. :focus="focus"
  12. :autoHeight="autoHeight"
  13. :fixed="fixed"
  14. :cursorSpacing="cursorSpacing"
  15. :cursor="cursor"
  16. :showConfirmBar="showConfirmBar"
  17. :selectionStart="selectionStart"
  18. :selectionEnd="selectionEnd"
  19. :adjustPosition="adjustPosition"
  20. :disableDefaultPadding="disableDefaultPadding"
  21. :holdKeyboard="holdKeyboard"
  22. :maxlength="maxlength"
  23. :confirmType="confirmType"
  24. :ignoreCompositionEvent="ignoreCompositionEvent"
  25. @focus="onFocus"
  26. @blur="onBlur"
  27. @linechange="onLinechange"
  28. @input="onInput"
  29. @confirm="onConfirm"
  30. @keyboardheightchange="onKeyboardheightchange"
  31. ></textarea>
  32. <!-- #ifndef MP-ALIPAY -->
  33. <text class="u-textarea__count" v-if="count">{{ maxlength > 0 ? innerValue.length + '/' + maxlength : innerValue.length }}</text>
  34. <!-- #endif -->
  35. </view>
  36. </template>
  37. <script>
  38. import props from "./props.js"
  39. import mpMixin from '../../libs/mixin/mpMixin'
  40. import mixin from '../../libs/mixin/mixin'
  41. /**
  42. * Textarea 文本域
  43. * @description 文本域此组件满足了可能出现的表单信息补充,编辑等实际逻辑的功能,内置了字数校验等
  44. * @tutorial https://uview.d3u.cn/components/textarea.html
  45. *
  46. * @property {String | Number} value 输入框的内容
  47. * @property {String | Number} placeholder 输入框为空时占位符
  48. * @property {String} placeholderClass 指定placeholder的样式类,注意页面或组件的style中写了scoped时,需要在类名前写/deep/ ( 默认 'input-placeholder' )
  49. * @property {String | Object} placeholderStyle 指定placeholder的样式,字符串/对象形式,如"color: red;"
  50. * @property {String | Number} height 输入框高度(默认 70 )
  51. * @property {String} confirmType 设置键盘右下角按钮的文字,仅微信小程序,App-vue和H5有效(默认 'done' )
  52. * @property {Boolean} disabled 是否禁用(默认 false )
  53. * @property {String} disabledColor 禁用状态下的背景色(默认 '#f5f7fa' )
  54. * @property {Boolean} count 是否显示统计字数(默认 false )
  55. * @property {Boolean} focus 是否自动获取焦点,nvue不支持,H5取决于浏览器的实现(默认 false )
  56. * @property {Boolean | Function} autoHeight 是否自动增加高度(默认 false )
  57. * @property {Boolean} fixed 如果textarea是在一个position:fixed的区域,需要显示指定属性fixed为true(默认 false )
  58. * @property {Number} cursorSpacing 指定光标与键盘的距离(默认 0 )
  59. * @property {String | Number} cursor 指定focus时的光标位置
  60. * @property {Function} formatter 内容式化函数
  61. * @property {Boolean} showConfirmBar 是否显示键盘上方带有”完成“按钮那一栏,(默认 true )
  62. * @property {Number} selectionStart 光标起始位置,自动聚焦时有效,需与selection-end搭配使用,(默认 -1 )
  63. * @property {Number | Number} selectionEnd 光标结束位置,自动聚焦时有效,需与selection-start搭配使用(默认 -1 )
  64. * @property {Boolean} adjustPosition 键盘弹起时,是否自动上推页面(默认 true )
  65. * @property {Boolean | Number} disableDefaultPadding 是否去掉 iOS 下的默认内边距,只微信小程序有效(默认 false )
  66. * @property {Boolean} holdKeyboard focus时,点击页面的时候不收起键盘,只微信小程序有效(默认 false )
  67. * @property {String | Number} maxlength 最大输入长度,设置为 -1 的时候不限制最大长度(默认 140 )
  68. * @property {String} border 边框类型,surround-四周边框,none-无边框,bottom-底部边框(默认 'surround' )
  69. * @property {Boolean} ignoreCompositionEvent 是否忽略组件内对文本合成系统事件的处理
  70. * @property {String} backgroundColor 背景颜色
  71. * @property {String} borderRadius 边框圆角
  72. * @property {String} borderColor 边框颜色
  73. *
  74. * @event {Function(e)} focus 输入框聚焦时触发,event.detail = { value, height },height 为键盘高度
  75. * @event {Function(e)} blur 输入框失去焦点时触发,event.detail = {value, cursor}
  76. * @event {Function(e)} linechange 输入框行数变化时调用,event.detail = {height: 0, heightRpx: 0, lineCount: 0}
  77. * @event {Function(e)} input 当键盘输入时,触发 input 事件
  78. * @event {Function(e)} confirm 点击完成时, 触发 confirm 事件
  79. * @event {Function(e)} keyboardheightchange 键盘高度发生变化的时候触发此事件
  80. * @example <u--textarea v-model="value1" placeholder="请输入内容" ></u--textarea>
  81. */
  82. export default {
  83. name: "u-textarea",
  84. mixins: [mpMixin, mixin, props],
  85. data() {
  86. return {
  87. // 输入框的值
  88. innerValue: "",
  89. // 是否处于获得焦点状态
  90. focused: false,
  91. // value是否第一次变化,在watch中,由于加入immediate属性,会在第一次触发,此时不应该认为value发生了变化
  92. firstChange: true,
  93. // value绑定值的变化是由内部还是外部引起的
  94. changeFromInner: false,
  95. // 过滤处理方法
  96. innerFormatter: value => value
  97. }
  98. },
  99. watch: {
  100. //#ifdef VUE2
  101. value: {
  102. immediate: true,
  103. handler(newVal, oldVal) {
  104. this.init(newVal);
  105. },
  106. },
  107. // #endif
  108. // #ifdef VUE3
  109. modelValue: {
  110. immediate: true,
  111. handler(newVal, oldVal) {
  112. this.init(newVal);
  113. },
  114. }
  115. // #endif
  116. },
  117. computed: {
  118. // 组件的类名
  119. textareaClass() {
  120. let classes = [];
  121. this.border === "surround" && classes.push("u-border");
  122. this.border === "bottom" && classes.push("u-border-bottom");
  123. return classes.join(" ");
  124. },
  125. // 组件的样式
  126. textareaStyle() {
  127. const style = {};
  128. // #ifdef APP-NVUE
  129. // 由于textarea在安卓nvue上的差异性,需要额外再调整其内边距
  130. if (uni.$u.os() === "android") {
  131. style.paddingTop = "6px";
  132. style.paddingLeft = "9px";
  133. style.paddingBottom = "3px";
  134. style.paddingRight = "6px";
  135. }
  136. // #endif
  137. if(this.backgroundColor){
  138. style.backgroundColor = this.backgroundColor;
  139. }
  140. //nvue 必须分开写才有效
  141. if(this.borderColor){
  142. style.borderLeftColor = this.borderColor;
  143. style.borderTopColor = this.borderColor;
  144. style.borderRightColor = this.borderColor;
  145. style.borderBottomColor = this.borderColor;
  146. }
  147. // 禁用状态下,被背景色加上对应的样式
  148. if (this.disabled) {
  149. style.backgroundColor = this.disabledColor;
  150. }
  151. if(this.border !== "bottom"){
  152. style.borderRadius = uni.$u.addUnit(this.round)
  153. }
  154. return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle));
  155. },
  156. },
  157. // #ifdef VUE3
  158. emits: ['update:modelValue', 'focus', 'blur', 'confirm', 'change', 'linechange', 'keyboardheightchange'],
  159. // #endif
  160. methods: {
  161. init(newVal){
  162. this.innerValue = newVal;
  163. /* #ifdef H5 */
  164. // 在H5中,外部value变化后,修改input中的值,不会触发@input事件,此时手动调用值变化方法
  165. if (
  166. this.firstChange === false &&
  167. this.changeFromInner === false
  168. ) {
  169. this.valueChange();
  170. }
  171. /* #endif */
  172. this.firstChange = false;
  173. // 重置changeFromInner的值为false,标识下一次引起默认为外部引起的
  174. this.changeFromInner = false;
  175. },
  176. // 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
  177. setFormatter(e) {
  178. this.innerFormatter = e
  179. },
  180. onFocus(e) {
  181. this.$emit("focus", e);
  182. },
  183. onBlur(e) {
  184. this.$emit("blur", e);
  185. // 尝试调用u-form的验证方法
  186. uni.$u.formValidate(this, "blur");
  187. },
  188. onLinechange(e) {
  189. this.$emit("linechange", e);
  190. },
  191. onInput(e) {
  192. let { value = "" } = e.detail || {};
  193. if (this.maxlength > 0 && value.length > this.maxlength) {
  194. value = value.subtring(0, this.maxlength);
  195. }
  196. const formatter = this.formatter || this.innerFormatter;
  197. const formatValue = formatter(value)
  198. // 为了避免props的单向数据流特性,需要先将innerValue值设置为当前值,再在$nextTick中重新赋予设置后的值才有效
  199. this.innerValue = value
  200. this.$nextTick(() => {
  201. this.innerValue = formatValue;
  202. this.valueChange();
  203. })
  204. },
  205. // 内容发生变化,进行处理
  206. valueChange() {
  207. const value = this.innerValue;
  208. this.$nextTick(() => {
  209. // #ifdef VUE2
  210. this.$emit("input", value);
  211. // #endif
  212. // #ifdef VUE3
  213. this.$emit("update:modelValue", value);
  214. // #endif
  215. // 标识value值的变化是由内部引起的
  216. this.changeFromInner = true;
  217. this.$emit("change", value);
  218. // 尝试调用u-form的验证方法
  219. uni.$u.formValidate(this, "change");
  220. });
  221. },
  222. onConfirm(e) {
  223. this.$emit("confirm", e);
  224. },
  225. onKeyboardheightchange(e) {
  226. this.$emit("keyboardheightchange", e);
  227. },
  228. },
  229. };
  230. </script>
  231. <style lang="scss" scoped>
  232. @import "../../libs/css/components.scss";
  233. .u-textarea {
  234. position: relative;
  235. @include flex;
  236. flex: 1;
  237. padding: 9px;
  238. // &--disabled {
  239. // background-color: #f5f7fa;
  240. // }
  241. &__field {
  242. flex: 1;
  243. font-size: 15px;
  244. color: $u-content-color;
  245. width: 100%;
  246. }
  247. &__count {
  248. position: absolute;
  249. right: 5px;
  250. bottom: 2px;
  251. font-size: 12px;
  252. color: $u-tips-color;
  253. padding: 1px 4px;
  254. }
  255. }
  256. </style>