stepper.vue 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. <template>
  2. <div :class="{'nut-stepper': !simple, 'nut-stepper-simple': simple}">
  3. <span
  4. @click="reduce()"
  5. :class="{'nut-stepper-grey': isGray}"
  6. v-html="require('../../assets/svg/minus.svg')">
  7. </span>
  8. <input
  9. type="number"
  10. :min="minNum"
  11. :max="max"
  12. :readonly="readonly"
  13. :value="num | maxv(minNum, max)"
  14. :style="{visibility:showNum? 'visible': 'hidden'}"
  15. @input="numchange"
  16. @keyup="checknum"
  17. @focus="focus"
  18. @blur="blur" />
  19. <div
  20. :class="['nut-stepper-fake', showAddAnim? 'nut-stepper-transition': 'nut-stepper-none-transition']"
  21. :style="{
  22. visibility:showAddAnim? 'visible': 'hidden',
  23. 'transform': 'translate(0,'+animTranslate_add+'%)',
  24. '-ms-transform': 'translate(0,'+animTranslate_add+'%)',
  25. '-moz-transform': 'translate(0,'+animTranslate_add+'%)',
  26. '-webkit-transform': 'translate(0,'+animTranslate_add+'%)',
  27. '-o-transform': 'translate(0,'+animTranslate_add+'%)'
  28. }">
  29. <div>{{animNum[0]}}</div>
  30. <div>{{animNum[1]}}</div>
  31. </div>
  32. <div
  33. :class="['nut-stepper-fake-', showReduceAnim? 'nut-stepper-transition': 'nut-stepper-none-transition']"
  34. :style="{
  35. visibility:showReduceAnim? 'visible': 'hidden',
  36. 'transform': 'translate(0,'+animTranslate_+'%)',
  37. '-ms-transform': 'translate(0,'+animTranslate_+'%)',
  38. '-moz-transform': 'translate(0,'+animTranslate_+'%)',
  39. '-webkit-transform': 'translate(0,'+animTranslate_+'%)',
  40. '-o-transform': 'translate(0,'+animTranslate_+'%)'
  41. }">
  42. <div>{{animNum[0]}}</div>
  43. <div>{{animNum[1]}}</div>
  44. </div>
  45. <span
  46. @click="add()"
  47. :class="{'nut-stepper-grey': max&&(Number(num) > max - step)}"
  48. v-html="require('../../assets/svg/plus.svg')">
  49. </span>
  50. </div>
  51. </template>
  52. <script>
  53. export default {
  54. name: 'nut-stepper',
  55. props: {
  56. simple: {
  57. type: Boolean,
  58. default: true,
  59. },
  60. min: {
  61. type: [Number, String],
  62. default: 0,
  63. },
  64. max: {
  65. type: [Number, String],
  66. default: Infinity,
  67. },
  68. step: {
  69. type: [Number, String],
  70. default: 1
  71. },
  72. readonly: {
  73. type: Boolean,
  74. default: false
  75. },
  76. transition: {
  77. type: Boolean,
  78. default: true
  79. },
  80. value: {
  81. type: [String, Number],
  82. required: true
  83. },
  84. decimalPlaces: {
  85. type: Number,
  86. default: 0
  87. }
  88. },
  89. data() {
  90. return {
  91. tempNum: '',
  92. focusing: false,
  93. num: this.value,
  94. minNum: this.min,
  95. showNum: true,
  96. showAddAnim: false,
  97. showReduceAnim: false,
  98. animNum: [this.value, this.value],
  99. animTranslate_add: 0,
  100. animTranslate_: -100
  101. }
  102. },
  103. filters: {
  104. maxv(v, min, max) {
  105. let val = v;
  106. if(val > max) val = max;
  107. if(val < min) val = min;
  108. return val;
  109. }
  110. },
  111. watch: {
  112. value: {
  113. handler(v, ov) {
  114. if(v > this.max) v = this.max;
  115. if(v < this.minNum) v = this.minNum;
  116. this.num = v > 0? this.fixedDecimalPlaces(v): v;
  117. this.$emit('change', this.num);
  118. },
  119. immediate: true
  120. }
  121. },
  122. computed: {
  123. isGray() {
  124. return (this.focusing? this.tempNum: this.num) - this.step < this.min;
  125. }
  126. },
  127. methods: {
  128. focus(e) {
  129. if(this.readonly) return;
  130. // clear val temporary when focus, e...s
  131. const v = this.num;
  132. this.tempNum = v;
  133. this.minNum = '';
  134. // this.num = '';
  135. this.focusing = true;
  136. this.$emit('focus', e, this.num);
  137. },
  138. blur(e) {
  139. if(this.readonly) return this.$emit('blur', e, this.num);
  140. let v = e.target.value;
  141. this.minNum = this.min;
  142. this.focusing = false;
  143. if(v) {
  144. if(v > this.max) v = this.max;
  145. if(v < this.minNum) v = this.minNum;
  146. this.num = v;
  147. }else{
  148. this.num = this.tempNum;
  149. }
  150. this.$emit('blur', e, this.num);
  151. },
  152. checknum(e) {
  153. let v = e.target.value;
  154. // this.minNum = this.min;
  155. this.focusing = false;
  156. if(v > this.max) v = this.max;
  157. if(v < this.minNum) v = this.minNum;
  158. e.target.value = v;
  159. this.num = v;
  160. },
  161. numchange(e) {
  162. let v = e.target.value;
  163. if(v > this.max) v = this.max;
  164. if(v < this.minNum) v = this.minNum;
  165. e.target.value = v;
  166. this.num = v;
  167. this.$emit('update:value', this.num);
  168. this.$emit('change', this.num);
  169. },
  170. fixedDecimalPlaces(v) {
  171. return Number(v).toFixed(this.decimalPlaces);
  172. // .replace(/(\d+\.[^0]*)0+$/, '$1').replace(/\.$/, '')
  173. },
  174. add() {
  175. this.num = Number(this.num);
  176. if(this.num <= this.max - this.step && this.max > this.minNum) {
  177. let [n1, n2] = this.fixedDecimalPlaces(this.num + Number(this.step)).split('.');
  178. let fixedLen = n2? n2.length: 0;
  179. this.num = parseFloat(n1 + (n2? '.'+n2: '')).toFixed(fixedLen);
  180. if(this.transition) {
  181. this.showNum = false;
  182. this.showAddAnim = true;
  183. this.showReduceAnim = false;
  184. this.animNum = [parseFloat(this.num - this.step).toFixed(fixedLen), this.num];
  185. this.animTranslate_add = -100;
  186. var f = this.$el.querySelector('.nut-stepper-fake');
  187. f.addEventListener("webkitTransitionEnd", () => {
  188. this.showNum = true;
  189. this.showAddAnim = false;
  190. this.animTranslate_add = 0;
  191. });
  192. };
  193. this.$emit('update:value', this.num);
  194. this.$emit('add', this.num);
  195. }
  196. },
  197. animEnd() {
  198. // unbind
  199. this.showNum = true;
  200. },
  201. reduce(){
  202. if(this.num - this.step >= this.minNum) {
  203. let [n1, n2] = this.fixedDecimalPlaces(this.num - Number(this.step)).split('.');
  204. let fixedLen = n2? n2.length: 0;
  205. this.num = parseFloat(n1 + (n2? '.'+n2: '')).toFixed(fixedLen);
  206. if(this.transition) {
  207. this.showNum = false;
  208. this.showAddAnim = false;
  209. this.showReduceAnim = true;
  210. this.animNum = [this.num, this.num];
  211. this.animTranslate_ = 0;
  212. var f = this.$el.querySelector('.nut-stepper-fake-');
  213. f.addEventListener("webkitTransitionEnd", () => {
  214. this.showNum = true;
  215. this.showReduceAnim = false;
  216. this.animTranslate_ = -100;
  217. });
  218. }
  219. this.$emit('update:value', this.num);
  220. this.$emit('reduce', this.num);
  221. }
  222. },
  223. }
  224. }
  225. </script>