@netang/quasar 0.0.25 → 0.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,40 +1,77 @@
1
1
  <template>
2
2
  <q-input
3
3
  class="n-input-number"
4
+ :class="{
5
+ 'n-input-number--center': center,
6
+ }"
7
+ :disable="disable"
8
+ :readonly="readonly"
4
9
  v-model="currentValue"
5
- @update:model-value="onInput"
6
10
  @blur="onBlur"
7
- v-on="$listeners"
8
11
  v-bind="$attrs"
9
12
  >
10
- <template #prepend>
13
+ <!-- 内部前置插槽 -->
14
+ <template #prepend v-if="controls && center">
15
+
16
+ <!-- 减少按钮 -->
11
17
  <q-btn
18
+ class="n-input-number__left"
12
19
  color="default"
13
20
  icon="remove"
14
21
  flat
15
22
  dense
16
- :disable="minusDisabled"
23
+ :disable="currentDisableMinus"
17
24
  @click="onChange('minus')"
18
- @overlimit="onOverlimit('minus')"
19
25
  />
26
+
27
+ <!-- 内部前置插槽 -->
28
+ <slot name="prepend" />
29
+
20
30
  </template>
21
- <template #append>
31
+
32
+ <!-- 内部后置插槽 -->
33
+ <template #append v-if="controls">
34
+
35
+ <!-- 内部后置插槽 -->
36
+ <slot name="append" />
37
+
38
+ <!-- 减少按钮 -->
39
+ <q-btn
40
+ color="default"
41
+ icon="remove"
42
+ flat
43
+ dense
44
+ :disable="currentDisableMinus"
45
+ @click="onChange('minus')"
46
+ v-if="! center"
47
+ />
48
+
49
+ <!-- 增加按钮 -->
22
50
  <q-btn
51
+ class="n-input-number__right"
23
52
  color="default"
24
53
  icon="add"
25
54
  flat
26
55
  dense
27
- :disable="plusDisabled"
56
+ :disable="currentDisablePlus"
28
57
  @click="onChange('plus')"
29
- @overlimit="onOverlimit('plus')"
30
58
  />
59
+
60
+ </template>
61
+
62
+ <!-- 插槽 -->
63
+ <template
64
+ v-for="slotName in slotNames"
65
+ v-slot:[slotName]
66
+ >
67
+ <slot :name="slotName" />
31
68
  </template>
69
+
32
70
  </q-input>
33
71
  </template>
34
72
 
35
73
  <script>
36
74
  import { computed, ref, watch } from 'vue'
37
- import { formatNumbers, addNumber } from './number'
38
75
 
39
76
  export default {
40
77
 
@@ -54,56 +91,41 @@ export default {
54
91
  // 最小值
55
92
  min: {
56
93
  type: [Number, String],
57
- default: 1,
94
+ default: 0,
58
95
  },
59
96
  // 最大值
60
97
  max: {
61
98
  type: [Number, String],
62
99
  default: Infinity,
63
100
  },
64
- // 初始值,当 v-model 为空时生效
65
- defaultValue: {
66
- type: [Number, String],
67
- default: 1,
68
- },
69
- // 步长,每次点击时改变的值
70
- step: {
71
- type: [Number, String],
72
- default: 1,
73
- },
74
- // 固定显示的小数位数
101
+ // 步长, 每次点击时改变的值(默认为 1, centToYuan开启后默认为 100)
102
+ step: [Number, String],
103
+ // 小数位数(默认为 0, centToYuan开启后默认为 2)
75
104
  decimalLength: [Number, String],
76
- // 是否只允许输入整数
77
- integer: {
78
- type: Boolean,
79
- default: true,
80
- },
81
- // 是否禁用步进器
82
- disabled: Boolean,
83
- // 是否禁用增加按钮
84
- disablePlus: Boolean,
105
+ // 是否禁用
106
+ disable: Boolean,
107
+ // 是否只读
108
+ readonly: Boolean,
85
109
  // 是否禁用减少按钮
86
110
  disableMinus: Boolean,
111
+ // 是否禁用增加按钮
112
+ disablePlus: Boolean,
87
113
  // 是否禁用输入框
88
114
  disableInput: Boolean,
89
- // 是否开启异步变更,开启后需要手动控制输入值
90
- asyncChange: Boolean,
91
- // 是否显示增加按钮
92
- showPlus: {
93
- type: Boolean,
94
- default: true,
95
- },
96
- // 是否显示减少按钮
97
- showMinus: {
98
- type: Boolean,
99
- default: true,
100
- },
101
- // 是否允许输入的值为空
102
- allowEmpty: Boolean,
103
- // 显示文字
104
- showText: {
105
- type: Boolean,
106
- default: true,
115
+ // 是否使用控制按钮
116
+ controls: Boolean,
117
+ // 居中显示
118
+ center: Boolean,
119
+ // 不允许输入的值为空
120
+ noEmpty: Boolean,
121
+ // 是否为人民币的分转元(值为分, 显示为元)
122
+ // 如果为 true, 则 min / max / step 的值默认的单位为人民币的分
123
+ centToYuan: Boolean,
124
+ // 精度舍入模式(默认: 向下取整)
125
+ // 参考文档: https://mikemcl.github.io/bignumber.js/#constructor-properties
126
+ roundMode: {
127
+ type: Number,
128
+ default: BigNumber.ROUND_DOWN,
107
129
  },
108
130
  },
109
131
 
@@ -112,9 +134,7 @@ export default {
112
134
  */
113
135
  emits: [
114
136
  'update:modelValue',
115
- 'change',
116
137
  'blur',
117
- 'overlimit',
118
138
  'minus',
119
139
  'plus',
120
140
  ],
@@ -122,184 +142,370 @@ export default {
122
142
  /**
123
143
  * 组合式
124
144
  */
125
- setup(props, { emit }) {
145
+ setup(props, { emit, slots }) {
126
146
 
127
- // ==========【数据】============================================================================================
147
+ // ==========【计算属性】=========================================================================================
128
148
 
129
- // 当前值
130
- const defaultValue = props.modelValue ?? props.defaultValue
131
- const value = format(defaultValue)
132
- if (! _.isEqual(value, props.modelValue)) {
133
- emit('update:modelValue', value)
134
- }
135
- const currentValue = ref(value)
149
+ /**
150
+ * 插槽标识
151
+ */
152
+ const slotNames = computed(function() {
136
153
 
137
- // ==========【计算属性】=========================================================================================
154
+ if (utils.isValidObject(slots)) {
155
+
156
+ // 忽略插槽
157
+ const ignoreKeys = []
158
+
159
+ if (props.controls) {
160
+ ignoreKeys.push('append')
161
+
162
+ if (props.center) {
163
+ ignoreKeys.push('prepend')
164
+ }
165
+ }
166
+
167
+ const keys = Object.keys(slots)
168
+
169
+ if (ignoreKeys.length) {
170
+ return _.filter(keys, e => ignoreKeys.indexOf(e) === -1)
171
+ }
172
+
173
+ return keys
174
+ }
175
+
176
+ return []
177
+ })
138
178
 
139
179
  /**
140
- * 是否禁用减少按钮
180
+ * 当前最小值
141
181
  */
142
- const minusDisabled = computed(function () {
143
- return props.disabled || props.disableMinus || currentValue.value <= +props.min
182
+ const currentMin = computed(function() {
183
+ // 格式化数字
184
+ return formatNumber(props.min, true, true, null)
144
185
  })
145
186
 
146
187
  /**
147
- * 是否禁用增加按钮
188
+ * 当前最大值
148
189
  */
149
- const plusDisabled = computed(function () {
150
- return props.disabled || props.disablePlus || currentValue.value >= +props.max
190
+ const currentMax = computed(function() {
191
+
192
+ // 如果为无限大
193
+ if (props.max === Infinity) {
194
+ // 则返回无限大
195
+ return Infinity
196
+ }
197
+
198
+ // 格式化数字
199
+ return formatNumber(props.max, true, true, null)
151
200
  })
152
201
 
153
- // ==========【监听数据】=========================================================================================
202
+ /**
203
+ * 当前步长(默认为 1, centToYuan开启后默认为 100)
204
+ */
205
+ const currentStep = computed(function() {
206
+
207
+ // 格式化数字
208
+ return formatNumber(props.step ?? (props.centToYuan ? 100 : 1), true, true, null)
209
+ })
154
210
 
155
211
  /**
156
- * 监听值
212
+ * 当前小数位数(默认为 0, centToYuan开启后默认为 2)
157
213
  */
158
- watch(()=>props.modelValue, function (val) {
159
- if (! _.isEqual(val, currentValue.value)) {
160
- currentValue.value = format(val)
214
+ const currentDecimalLength = computed(function() {
215
+ return props.decimalLength ?? (props.centToYuan ? 2 : 0)
216
+ })
217
+
218
+ /**
219
+ * 当前是否禁用减少按钮
220
+ */
221
+ const currentDisableMinus = computed(function () {
222
+
223
+ // 如果禁用 || 如果只读 || 禁用减少按钮
224
+ if (props.disable || props.readonly || props.disableMinus) {
225
+ // 则禁用减少按钮
226
+ return true
227
+ }
228
+
229
+ // 如果没有当前最小值
230
+ if (currentMin.value === null) {
231
+ // 则不禁用减少按钮
232
+ return false
161
233
  }
234
+
235
+ // 将当前值转为 BigNumber 类型
236
+ const val = new BigNumber(currentValue.value)
237
+
238
+ // 如果当前值不是有效数字
239
+ if (! val.isFinite()) {
240
+ // 则禁用减少按钮
241
+ return true
242
+ }
243
+
244
+ // 当前值 <= 当前最小值
245
+ return val.lte(currentMin.value)
162
246
  })
163
247
 
164
248
  /**
165
- * 监听当前值
249
+ * 当前是否禁用增加按钮
166
250
  */
167
- watch(currentValue, function (val) {
168
- emit('update:modelValue', val)
169
- emit('change', val)
251
+ const currentDisablePlus = computed(function () {
252
+
253
+ // 如果禁用 || 如果只读 || 禁用增加按钮
254
+ if (props.disable || props.readonly || props.disablePlus) {
255
+ // 则禁用增加按钮
256
+ return true
257
+ }
258
+
259
+ // 如果没有当前最大值
260
+ if (currentMax.value === null) {
261
+ // 则不禁用增加按钮
262
+ return false
263
+ }
264
+
265
+ // 将当前值转为 BigNumber 类型
266
+ const val = new BigNumber(currentValue.value)
267
+
268
+ // 如果当前值不是有效数字
269
+ if (! val.isFinite()) {
270
+ // 则禁用减少按钮
271
+ return true
272
+ }
273
+
274
+ // 当前值 >= 当前最大值
275
+ return val.gte(currentMax.value)
276
+ })
277
+
278
+ // ==========【数据】============================================================================================
279
+
280
+ // 格式化为当前值
281
+ const currentValue = ref(formatToCurrentValue(props.modelValue, true))
282
+
283
+ // 如果当前值 !== 声明值
284
+ const rawModelValue = formatToModelValue(currentValue.value)
285
+ if (rawModelValue !== props.modelValue) {
286
+ // 则更新值
287
+ emitModelValue(rawModelValue)
288
+ }
289
+
290
+ // ==========【监听数据】=========================================================================================
291
+
292
+ /**
293
+ * 监听声明值
294
+ */
295
+ watch(()=>props.modelValue, function (val) {
296
+
297
+ // 格式化为当前值
298
+ val = formatToCurrentValue(val, true)
299
+
300
+ // 如果当前值有变化
301
+ if (val !== currentValue.value) {
302
+ currentValue.value = val
303
+ }
170
304
  })
171
305
 
172
306
  /**
173
- * 监听其他
307
+ * 监听 当前最小值 / 当前最大值 / 当前小数位数
174
308
  */
175
- watch([()=>props.max, ()=>props.min, ()=>props.integer, ()=>props.decimalLength], function () {
176
- const val = format(currentValue.value)
177
- if (! _.isEqual(val, currentValue.value)) {
309
+ watch([currentMin, currentMax, currentDecimalLength], function () {
310
+
311
+ // 格式化当前值
312
+ const val = formatToCurrentValue(currentValue.value, false)
313
+
314
+ // 如果当前值有变化
315
+ if (val !== currentValue.value) {
316
+
317
+ // 更新当前值
178
318
  currentValue.value = val
319
+
320
+ // 更新值
321
+ emitModelValue(formatToModelValue(val))
179
322
  }
180
323
  })
181
324
 
182
325
  // ==========【方法】=============================================================================================
183
326
 
184
327
  /**
185
- * 格式化数字
328
+ * 更新值
186
329
  */
187
- function formatNumber(value) {
188
- return formatNumbers(String(value), ! props.integer)
330
+ function emitModelValue(val) {
331
+ // 更新值
332
+ emit('update:modelValue', val)
189
333
  }
190
334
 
191
335
  /**
192
- * 格式化
336
+ * 格式化数字
193
337
  */
194
- function format(value) {
195
- if (props.allowEmpty && value === '') {
196
- return value
197
- }
338
+ function formatNumber(val, isCentToYuan, isToNumber, defaultValue) {
339
+
340
+ // 转为 BigNumber 类型
341
+ val = new BigNumber(val)
342
+
343
+ // 如果为有效数字
344
+ if (val.isFinite()) {
345
+
346
+ // 如果不为 0
347
+ if (! val.isZero()) {
198
348
 
199
- value = formatNumber(value)
349
+ // 如果为人民币的分转元
350
+ if (props.centToYuan && isCentToYuan) {
351
+ // 除 100
352
+ val = val.dividedBy(100)
353
+ }
200
354
 
201
- // format range
202
- value = value === '' ? 0 : +value
203
- value = isNaN(value) ? props.min : value
204
- value = Math.max(Math.min(props.max, value), props.min)
355
+ // 如果设置了小数位数
356
+ if (currentDecimalLength.value) {
357
+ // 将值舍入 xx 位精度( 68.345 -> 68.34)
358
+ val = val.dp(currentDecimalLength.value, props.roundMode)
205
359
 
206
- // 格式化小数位数
207
- if (! _.isNil(props.decimalLength)) {
208
- value = value.toFixed(props.decimalLength)
360
+ // 否则值为整数
361
+ } else {
362
+ // 将值取整
363
+ val = val.integerValue(props.roundMode)
364
+ }
365
+ }
366
+
367
+ // 转为数字
368
+ return isToNumber ? val.toNumber() : val
209
369
  }
210
370
 
211
- return value
371
+ return defaultValue
212
372
  }
213
373
 
214
374
  /**
215
- * 输入触发
375
+ * 格式化为更新值
216
376
  */
217
- function onInput(value) {
377
+ function formatToModelValue(value) {
218
378
 
219
- let formatted = formatNumber(value)
379
+ // 转为 BigNumber 类型
380
+ let val = new BigNumber(value)
220
381
 
221
- // limit max decimal length
222
- if (! _.isNil(props.decimalLength) && formatted.indexOf('.') !== -1) {
223
- const pair = utils.split(formatted, '.')
224
- formatted = `${pair[0]}.${pair[1].slice(0, props.decimalLength)}`
382
+ // 如果不是有效数字
383
+ if (! val.isFinite()) {
384
+
385
+ // 返回当前值
386
+ return value
225
387
  }
226
388
 
227
- // prefer number type
228
- if (formatted === String(+formatted)) {
229
- formatted = +formatted
389
+ // 如果为人民币的分转元
390
+ if (props.centToYuan) {
391
+ // 乘以 100
392
+ val = val.times(100)
393
+ // 再取整(分必须是整数)
394
+ .integerValue(props.roundMode)
230
395
  }
231
396
 
232
- emitChange(formatted)
397
+ // 将值转为数字
398
+ return val.toNumber()
233
399
  }
234
400
 
235
401
  /**
236
- * 失去焦点触发
402
+ * 格式化值
237
403
  */
238
- function onBlur(value) {
239
- value = format(value)
240
- emitChange(value)
241
- emit('blur', value)
242
- }
404
+ function formatToCurrentValue(value, isCentToYuan) {
243
405
 
244
- /**
245
- * 提交改变值
246
- */
247
- function emitChange(value) {
406
+ // 格式化数字
407
+ const val = formatNumber(value, isCentToYuan, false, false)
408
+
409
+ // 如果为有效数字
410
+ if (val !== false) {
411
+
412
+ // 如果值 >= 最大值
413
+ if (currentMax.value !== null && val.gte(currentMax.value)) {
414
+
415
+ // 返回最大值
416
+ return currentMax.value
417
+ }
418
+
419
+ // 如果值 <= 最小值
420
+ if (currentMin.value !== null && val.lte(currentMin.value)) {
248
421
 
249
- // 是否开启异步变更,开启后需要手动控制输入值
250
- if (props.asyncChange) {
251
- emit('input', value)
252
- emit('change', value)
253
- return
422
+ // 返回最小值
423
+ return currentMin.value
424
+ }
425
+
426
+ // 将值转为数字
427
+ return val.toNumber()
254
428
  }
255
429
 
256
- currentValue.value = value
430
+ if (
431
+ // 如果不允许值为空
432
+ props.noEmpty
433
+ // 如果有最小值
434
+ && currentMin.value !== null
435
+ ) {
436
+ // 则返回最小值
437
+ return currentMin.value
438
+ }
439
+
440
+ return ''
257
441
  }
258
442
 
259
443
  /**
260
- * 改变值
444
+ * 失去焦点触发
261
445
  */
262
- function onChange(type) {
446
+ function onBlur() {
263
447
 
264
- if (props[`${type}Disabled`]) {
265
- emit('overlimit', type)
266
- return
267
- }
448
+ // 格式化当前值
449
+ let val = formatToCurrentValue(currentValue.value, false)
268
450
 
269
- const diff = type === 'minus' ? -props.step : +props.step
451
+ // 如果当前值有变动
452
+ if (val !== currentValue.value) {
270
453
 
271
- const value = format(addNumber(+currentValue.value, diff))
454
+ // 更新当前值
455
+ currentValue.value = val
272
456
 
273
- emitChange(value)
274
- emit(type)
457
+ // 将当前值转为声明值
458
+ val = formatToModelValue(val)
459
+
460
+ // 更新值
461
+ emitModelValue(val)
462
+
463
+ // 失去焦点触发
464
+ emit('blur', val)
465
+ }
275
466
  }
276
467
 
277
468
  /**
278
- * 点击不可用的按钮时触发
469
+ * 改变值
279
470
  */
280
- function onOverlimit(type) {
281
- emit('overlimit', type)
471
+ function onChange(type) {
472
+
473
+ // 格式化当前值
474
+ const val = formatToCurrentValue(
475
+ new BigNumber(+currentValue.value)
476
+ // 增加 / 减少
477
+ .plus(type === 'minus' ? -currentStep.value : +currentStep.value)
478
+ .toNumber()
479
+ , false
480
+ )
481
+
482
+ // 如果当前值有变动
483
+ if (val !== currentValue.value) {
484
+
485
+ // 更新当前值
486
+ currentValue.value = val
487
+
488
+ // 更新值
489
+ emitModelValue(formatToModelValue(val))
490
+ }
282
491
  }
283
492
 
284
493
  // ==========【返回】=============================================================================================
285
494
 
286
495
  return {
496
+ // 插槽标识
497
+ slotNames,
287
498
  // 当前值
288
499
  currentValue,
500
+ // 当前是否禁用减少按钮
501
+ currentDisableMinus,
502
+ // 当前是否禁用增加按钮
503
+ currentDisablePlus,
289
504
 
290
- // 是否禁用减少按钮
291
- minusDisabled,
292
- // 是否禁用增加按钮
293
- plusDisabled,
294
-
295
- // 输入触发
296
- onInput,
297
505
  // 失去焦点触发
298
506
  onBlur,
299
507
  // 改变值
300
508
  onChange,
301
- // 点击不可用的按钮时触发
302
- onOverlimit,
303
509
  }
304
510
  }
305
511
  }
@@ -309,16 +515,29 @@ export default {
309
515
  @import "@/assets/sass/var.scss";
310
516
 
311
517
  .n-input-number {
312
- &.q-field {
313
- &--outlined {
314
- .q-field__control {
315
- padding: 0 4px !important;
316
- .q-field__native {
317
- text-align: center;
518
+
519
+ // 居中显示
520
+ &--center {
521
+ &.q-field {
522
+ &--outlined {
523
+ .q-field__control {
524
+ .q-field__native {
525
+ text-align: center;
526
+ }
318
527
  }
319
528
  }
320
529
  }
321
530
  }
531
+
532
+ // 左边按钮
533
+ &__left {
534
+ margin-left: -8px;
535
+ }
536
+
537
+ // 右边按钮
538
+ &__right {
539
+ margin-right: -8px;
540
+ }
322
541
  }
323
542
  </style>
324
543