index.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. import baseComponent from '../helpers/baseComponent'
  2. import classNames from '../helpers/classNames'
  3. const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || Math.pow(2, 53) - 1
  4. const toNumberWhenUserInput = (num) => {
  5. if (/\.\d*0$/.test(num) || num.length > 16) {
  6. return num
  7. }
  8. if (isNaN(num)) {
  9. return num
  10. }
  11. return Number(num)
  12. }
  13. const getValidValue = (value, min, max) => {
  14. let val = parseFloat(value)
  15. if (isNaN(val)) {
  16. return value
  17. }
  18. if (val < min) {
  19. val = min
  20. }
  21. if (val > max) {
  22. val = max
  23. }
  24. return val
  25. }
  26. baseComponent({
  27. externalClasses: ['wux-sub-class', 'wux-input-class', 'wux-add-class'],
  28. properties: {
  29. prefixCls: {
  30. type: String,
  31. value: 'wux-input-number',
  32. },
  33. shape: {
  34. type: String,
  35. value: 'square',
  36. },
  37. min: {
  38. type: Number,
  39. value: -MAX_SAFE_INTEGER,
  40. },
  41. max: {
  42. type: Number,
  43. value: MAX_SAFE_INTEGER,
  44. },
  45. step: {
  46. type: Number,
  47. value: 1,
  48. },
  49. defaultValue: {
  50. type: Number,
  51. value: 0,
  52. },
  53. value: {
  54. type: Number,
  55. value: 0,
  56. observer(newVal) {
  57. if (this.data.controlled) {
  58. this.updated(newVal)
  59. }
  60. },
  61. },
  62. disabled: {
  63. type: Boolean,
  64. value: true,
  65. },
  66. longpress: {
  67. type: Boolean,
  68. value: false,
  69. },
  70. color: {
  71. type: String,
  72. value: 'balanced',
  73. },
  74. controlled: {
  75. type: Boolean,
  76. value: false,
  77. },
  78. },
  79. data: {
  80. inputValue: 0,
  81. disabledMin: false,
  82. disabledMax: false,
  83. },
  84. computed: {
  85. classes() {
  86. const { prefixCls, shape, color, disabledMin, disabledMax } = this.data
  87. const wrap = classNames(prefixCls, {
  88. [`${prefixCls}--${shape}`]: shape,
  89. })
  90. const sub = classNames(`${prefixCls}__selector`, {
  91. [`${prefixCls}__selector--sub`]: true,
  92. [`${prefixCls}__selector--${color}`]: color,
  93. [`${prefixCls}__selector--disabled`]: disabledMin,
  94. })
  95. const add = classNames(`${prefixCls}__selector`, {
  96. [`${prefixCls}__selector--add`]: true,
  97. [`${prefixCls}__selector--${color}`]: color,
  98. [`${prefixCls}__selector--disabled`]: disabledMax,
  99. })
  100. const icon = `${prefixCls}__icon`
  101. const input = `${prefixCls}__input`
  102. return {
  103. wrap,
  104. sub,
  105. add,
  106. icon,
  107. input,
  108. }
  109. },
  110. },
  111. methods: {
  112. /**
  113. * 更新值
  114. */
  115. updated(value, condition = true, trigger = false) {
  116. const { min, max } = this.data
  117. const inputValue = getValidValue(value, min, max)
  118. const disabledMin = inputValue <= min
  119. const disabledMax = inputValue >= max
  120. // 更新数值,判断最小或最大值禁用 sub 或 add 按钮
  121. if (condition) {
  122. this.setData({
  123. inputValue,
  124. disabledMin,
  125. disabledMax,
  126. })
  127. }
  128. // 触发事件
  129. if (trigger) {
  130. this.triggerEvent('change', { value: inputValue })
  131. }
  132. },
  133. /**
  134. * 数字计算函数
  135. */
  136. calculation(type, meta) {
  137. const { disabledMax, disabledMin, inputValue, step, longpress, controlled } = this.data
  138. // add
  139. if (type === 'add') {
  140. if (disabledMax) return false
  141. this.updated(inputValue + step, !controlled, true)
  142. }
  143. // sub
  144. if (type === 'sub') {
  145. if (disabledMin) return false
  146. this.updated(inputValue - step, !controlled, true)
  147. }
  148. // longpress
  149. if (longpress && meta) {
  150. this.timeout = setTimeout(() => this.calculation(type, meta), 100)
  151. }
  152. },
  153. /**
  154. * 当键盘输入时,触发 input 事件
  155. */
  156. onInput(e) {
  157. this.clearInputTimer()
  158. this.inputTime = setTimeout(() => {
  159. const value = toNumberWhenUserInput(e.detail.value)
  160. this.updated(value, !this.data.controlled)
  161. this.triggerEvent('change', { value })
  162. }, 300)
  163. },
  164. /**
  165. * 输入框聚焦时触发
  166. */
  167. onFocus(e) {
  168. this.triggerEvent('focus', e.detail)
  169. },
  170. /**
  171. * 输入框失去焦点时触发
  172. */
  173. onBlur(e) {
  174. // always set input value same as value
  175. this.setData({
  176. inputValue: this.data.inputValue,
  177. })
  178. this.triggerEvent('blur', e.detail)
  179. },
  180. /**
  181. * 手指触摸后,超过350ms再离开
  182. */
  183. onLongpress(e) {
  184. const { type } = e.currentTarget.dataset
  185. const { longpress } = this.data
  186. if (longpress) {
  187. this.calculation(type, true)
  188. }
  189. },
  190. /**
  191. * 手指触摸后马上离开
  192. */
  193. onTap(e) {
  194. const { type } = e.currentTarget.dataset
  195. const { longpress } = this.data
  196. if (!longpress || longpress && !this.timeout) {
  197. this.calculation(type, false)
  198. }
  199. },
  200. /**
  201. * 手指触摸动作结束
  202. */
  203. onTouchEnd() {
  204. this.clearTimer()
  205. },
  206. /**
  207. * 手指触摸动作被打断,如来电提醒,弹窗
  208. */
  209. onTouchCancel() {
  210. this.clearTimer()
  211. },
  212. /**
  213. * 清除长按的定时器
  214. */
  215. clearTimer() {
  216. if (this.timeout) {
  217. clearTimeout(this.timeout)
  218. this.timeout = null
  219. }
  220. },
  221. /**
  222. * 清除输入框的定时器
  223. */
  224. clearInputTimer() {
  225. if (this.inputTime) {
  226. clearTimeout(this.inputTime)
  227. this.inputTime = null
  228. }
  229. },
  230. },
  231. attached() {
  232. const { defaultValue, value, controlled } = this.data
  233. const inputValue = controlled ? value : defaultValue
  234. this.updated(inputValue)
  235. },
  236. detached() {
  237. this.clearTimer()
  238. this.clearInputTimer()
  239. },
  240. })