123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. <template>
  2. <div
  3. :class="[
  4. 'el-input-number',
  5. inputNumberSize ? 'el-input-number--' + inputNumberSize : '',
  6. { 'is-disabled': inputNumberDisabled },
  7. { 'is-without-controls': !controls },
  8. { 'is-controls-right': controlsAtRight }
  9. ]"
  10. @dragstart.prevent>
  11. <span
  12. v-repeat-click="decrease"
  13. v-if="controls"
  14. :class="{'is-disabled': minDisabled}"
  15. class="el-input-number__decrease"
  16. role="button"
  17. @keydown.enter="decrease">
  18. <i :class="`el-icon-${controlsAtRight ? 'arrow-down' : 'minus'}`"/>
  19. </span>
  20. <span
  21. v-repeat-click="increase"
  22. v-if="controls"
  23. :class="{'is-disabled': maxDisabled}"
  24. class="el-input-number__increase"
  25. role="button"
  26. @keydown.enter="increase">
  27. <i :class="`el-icon-${controlsAtRight ? 'arrow-up' : 'plus'}`"/>
  28. </span>
  29. <el-input
  30. ref="input"
  31. :value="displayValue"
  32. :placeholder="placeholder"
  33. :disabled="inputNumberDisabled"
  34. :size="inputNumberSize"
  35. :max="max"
  36. :min="min"
  37. :name="name"
  38. :label="label"
  39. @keydown.up.native.prevent="increase"
  40. @keydown.down.native.prevent="decrease"
  41. @blur="handleBlur"
  42. @focus="handleFocus"
  43. @input="handleInput"
  44. @change="handleInputChange">
  45. <template slot="suffix">%</template>
  46. </el-input>
  47. </div>
  48. </template>
  49. <script>
  50. import Focus from 'element-ui/src/mixins/focus'
  51. import RepeatClick from 'element-ui/src/directives/repeat-click'
  52. export default {
  53. name: 'WkPercentInput',
  54. directives: {
  55. RepeatClick
  56. },
  57. mixins: [Focus('input')],
  58. inject: {
  59. elForm: {
  60. default: ''
  61. },
  62. elFormItem: {
  63. default: ''
  64. }
  65. },
  66. props: {
  67. step: {
  68. type: Number,
  69. default: 1
  70. },
  71. stepStrictly: {
  72. type: Boolean,
  73. default: false
  74. },
  75. max: {
  76. type: Number,
  77. default: Infinity
  78. },
  79. min: {
  80. type: Number,
  81. default: -Infinity
  82. },
  83. // eslint-disable-next-line vue/require-prop-types
  84. value: {},
  85. disabled: Boolean,
  86. size: String,
  87. controls: {
  88. type: Boolean,
  89. default: true
  90. },
  91. controlsPosition: {
  92. type: String,
  93. default: ''
  94. },
  95. name: String,
  96. label: String,
  97. placeholder: String,
  98. precision: {
  99. type: Number,
  100. validator(val) {
  101. return val >= 0 && val === parseInt(val, 10)
  102. }
  103. }
  104. },
  105. data() {
  106. return {
  107. currentValue: 0,
  108. userInput: null
  109. }
  110. },
  111. computed: {
  112. minDisabled() {
  113. return this._decrease(this.value, this.step) < this.min
  114. },
  115. maxDisabled() {
  116. return this._increase(this.value, this.step) > this.max
  117. },
  118. numPrecision() {
  119. const { value, step, getPrecision, precision } = this
  120. const stepPrecision = getPrecision(step)
  121. if (precision !== undefined) {
  122. if (stepPrecision > precision) {
  123. console.warn('[Element Warn][InputNumber]precision should not be less than the decimal places of step')
  124. }
  125. return precision
  126. } else {
  127. return Math.max(getPrecision(value), stepPrecision)
  128. }
  129. },
  130. controlsAtRight() {
  131. return this.controls && this.controlsPosition === 'right'
  132. },
  133. _elFormItemSize() {
  134. return (this.elFormItem || {}).elFormItemSize
  135. },
  136. inputNumberSize() {
  137. return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size
  138. },
  139. inputNumberDisabled() {
  140. return this.disabled || !!(this.elForm || {}).disabled
  141. },
  142. displayValue() {
  143. if (this.userInput !== null) {
  144. return this.userInput
  145. }
  146. let currentValue = this.currentValue
  147. if (typeof currentValue === 'number') {
  148. if (this.stepStrictly) {
  149. const stepPrecision = this.getPrecision(this.step)
  150. const precisionFactor = Math.pow(10, stepPrecision)
  151. currentValue = Math.round(currentValue / this.step) * precisionFactor * this.step / precisionFactor
  152. }
  153. if (this.precision !== undefined) {
  154. currentValue = currentValue.toFixed(this.precision)
  155. }
  156. }
  157. return currentValue
  158. }
  159. },
  160. watch: {
  161. value: {
  162. immediate: true,
  163. handler(value) {
  164. let newVal = value === undefined ? value : Number(value)
  165. if (newVal !== undefined) {
  166. if (isNaN(newVal)) {
  167. return
  168. }
  169. if (this.stepStrictly) {
  170. const stepPrecision = this.getPrecision(this.step)
  171. const precisionFactor = Math.pow(10, stepPrecision)
  172. newVal = Math.round(newVal / this.step) * precisionFactor * this.step / precisionFactor
  173. }
  174. if (this.precision !== undefined) {
  175. newVal = this.toPrecision(newVal, this.precision)
  176. }
  177. }
  178. if (newVal >= this.max) newVal = this.max
  179. if (newVal <= this.min) newVal = this.min
  180. this.currentValue = newVal
  181. this.userInput = null
  182. this.$emit('input', newVal)
  183. }
  184. }
  185. },
  186. mounted() {
  187. const innerInput = this.$refs.input.$refs.input
  188. innerInput.setAttribute('role', 'spinbutton')
  189. innerInput.setAttribute('aria-valuemax', this.max)
  190. innerInput.setAttribute('aria-valuemin', this.min)
  191. innerInput.setAttribute('aria-valuenow', this.currentValue)
  192. innerInput.setAttribute('aria-disabled', this.inputNumberDisabled)
  193. },
  194. updated() {
  195. if (!this.$refs || !this.$refs.input) return
  196. const innerInput = this.$refs.input.$refs.input
  197. innerInput.setAttribute('aria-valuenow', this.currentValue)
  198. },
  199. methods: {
  200. toPrecision(num, precision) {
  201. if (precision === undefined) precision = this.numPrecision
  202. return parseFloat(Math.round(num * Math.pow(10, precision)) / Math.pow(10, precision))
  203. },
  204. getPrecision(value) {
  205. if (value === undefined) return 0
  206. const valueString = value.toString()
  207. const dotPosition = valueString.indexOf('.')
  208. let precision = 0
  209. if (dotPosition !== -1) {
  210. precision = valueString.length - dotPosition - 1
  211. }
  212. return precision
  213. },
  214. _increase(val, step) {
  215. if (typeof val !== 'number' && val !== undefined) return this.currentValue
  216. const precisionFactor = Math.pow(10, this.numPrecision)
  217. // Solve the accuracy problem of JS decimal calculation by converting the value to integer.
  218. return this.toPrecision((precisionFactor * val + precisionFactor * step) / precisionFactor)
  219. },
  220. _decrease(val, step) {
  221. if (typeof val !== 'number' && val !== undefined) return this.currentValue
  222. const precisionFactor = Math.pow(10, this.numPrecision)
  223. return this.toPrecision((precisionFactor * val - precisionFactor * step) / precisionFactor)
  224. },
  225. increase() {
  226. if (this.inputNumberDisabled || this.maxDisabled) return
  227. const value = this.value || 0
  228. const newVal = this._increase(value, this.step)
  229. this.setCurrentValue(newVal)
  230. },
  231. decrease() {
  232. if (this.inputNumberDisabled || this.minDisabled) return
  233. const value = this.value || 0
  234. const newVal = this._decrease(value, this.step)
  235. this.setCurrentValue(newVal)
  236. },
  237. handleBlur(event) {
  238. this.$emit('blur', event)
  239. },
  240. handleFocus(event) {
  241. this.$emit('focus', event)
  242. },
  243. setCurrentValue(newVal) {
  244. const oldVal = this.currentValue
  245. if (typeof newVal === 'number' && this.precision !== undefined) {
  246. newVal = this.toPrecision(newVal, this.precision)
  247. }
  248. if (newVal >= this.max) newVal = this.max
  249. if (newVal <= this.min) newVal = this.min
  250. if (oldVal === newVal) return
  251. this.userInput = null
  252. this.$emit('input', newVal)
  253. this.$emit('change', newVal, oldVal)
  254. this.currentValue = newVal
  255. },
  256. handleInput(value) {
  257. this.userInput = value
  258. },
  259. handleInputChange(value) {
  260. const newVal = value === '' ? undefined : Number(value)
  261. if (!isNaN(newVal) || value === '') {
  262. this.setCurrentValue(newVal)
  263. }
  264. this.userInput = null
  265. },
  266. select() {
  267. this.$refs.input.select()
  268. }
  269. }
  270. }
  271. </script>