index.js 7.6 KB

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