@netang/quasar 0.0.25 → 0.0.27

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
+ <!-- 减少按钮 -->
22
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
+ <!-- 增加按钮 -->
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
+
31
60
  </template>
61
+
62
+ <!-- 插槽 -->
63
+ <template
64
+ v-for="slotName in slotNames"
65
+ v-slot:[slotName]
66
+ >
67
+ <slot :name="slotName" />
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,57 +91,36 @@ 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,
107
- },
115
+ // 是否使用控制按钮
116
+ controls: Boolean,
117
+ // 居中显示
118
+ center: Boolean,
119
+ // 不允许输入的值为空
120
+ noEmpty: Boolean,
121
+ // 是否为人民币的分转元(值为分, 显示为元)
122
+ // 如果为 true, 则 min / max / step 的值默认的单位为人民币的分
123
+ centToYuan: Boolean,
108
124
  },
109
125
 
110
126
  /**
@@ -112,9 +128,7 @@ export default {
112
128
  */
113
129
  emits: [
114
130
  'update:modelValue',
115
- 'change',
116
131
  'blur',
117
- 'overlimit',
118
132
  'minus',
119
133
  'plus',
120
134
  ],
@@ -122,184 +136,378 @@ export default {
122
136
  /**
123
137
  * 组合式
124
138
  */
125
- setup(props, { emit }) {
139
+ setup(props, { emit, slots }) {
126
140
 
127
- // ==========【数据】============================================================================================
141
+ // ==========【计算属性】=========================================================================================
128
142
 
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)
143
+ /**
144
+ * 插槽标识
145
+ */
146
+ const slotNames = computed(function() {
136
147
 
137
- // ==========【计算属性】=========================================================================================
148
+ if (utils.isValidObject(slots)) {
149
+
150
+ // 忽略插槽
151
+ const ignoreKeys = []
152
+
153
+ if (props.controls) {
154
+ ignoreKeys.push('append')
155
+
156
+ if (props.center) {
157
+ ignoreKeys.push('prepend')
158
+ }
159
+ }
160
+
161
+ const keys = Object.keys(slots)
162
+
163
+ if (ignoreKeys.length) {
164
+ return _.filter(keys, e => ignoreKeys.indexOf(e) === -1)
165
+ }
166
+
167
+ return keys
168
+ }
169
+
170
+ return []
171
+ })
138
172
 
139
173
  /**
140
- * 是否禁用减少按钮
174
+ * 当前最小值
141
175
  */
142
- const minusDisabled = computed(function () {
143
- return props.disabled || props.disableMinus || currentValue.value <= +props.min
176
+ const currentMin = computed(function() {
177
+ // 格式化数字
178
+ return formatNumber(props.min, true, true, null)
144
179
  })
145
180
 
146
181
  /**
147
- * 是否禁用增加按钮
182
+ * 当前最大值
148
183
  */
149
- const plusDisabled = computed(function () {
150
- return props.disabled || props.disablePlus || currentValue.value >= +props.max
184
+ const currentMax = computed(function() {
185
+
186
+ // 如果为无限大
187
+ if (props.max === Infinity) {
188
+ // 则返回无限大
189
+ return Infinity
190
+ }
191
+
192
+ // 格式化数字
193
+ return formatNumber(props.max, true, true, null)
151
194
  })
152
195
 
153
- // ==========【监听数据】=========================================================================================
196
+ /**
197
+ * 当前步长(默认为 1, centToYuan开启后默认为 100)
198
+ */
199
+ const currentStep = computed(function() {
200
+
201
+ // 格式化数字
202
+ return formatNumber(props.step ?? (props.centToYuan ? 100 : 1), true, true, null)
203
+ })
154
204
 
155
205
  /**
156
- * 监听值
206
+ * 当前小数位数(默认为 0, centToYuan开启后默认为 2)
157
207
  */
158
- watch(()=>props.modelValue, function (val) {
159
- if (! _.isEqual(val, currentValue.value)) {
160
- currentValue.value = format(val)
208
+ const currentDecimalLength = computed(function() {
209
+ return props.decimalLength ?? (props.centToYuan ? 2 : 0)
210
+ })
211
+
212
+ /**
213
+ * 当前是否禁用减少按钮
214
+ */
215
+ const currentDisableMinus = computed(function () {
216
+
217
+ // 如果禁用 || 如果只读 || 禁用减少按钮
218
+ if (props.disable || props.readonly || props.disableMinus) {
219
+ // 则禁用减少按钮
220
+ return true
221
+ }
222
+
223
+ // 如果没有当前最小值
224
+ if (currentMin.value === null) {
225
+ // 则不禁用减少按钮
226
+ return false
227
+ }
228
+
229
+ // 将当前值转为 BigNumber 类型
230
+ const val = new BigNumber(currentValue.value)
231
+
232
+ // 如果当前值不是有效数字
233
+ if (! val.isFinite()) {
234
+ // 则禁用减少按钮
235
+ return true
161
236
  }
237
+
238
+ // 当前值 <= 当前最小值
239
+ return val.lte(currentMin.value)
162
240
  })
163
241
 
164
242
  /**
165
- * 监听当前值
243
+ * 当前是否禁用增加按钮
166
244
  */
167
- watch(currentValue, function (val) {
168
- emit('update:modelValue', val)
169
- emit('change', val)
245
+ const currentDisablePlus = computed(function () {
246
+
247
+ // 如果禁用 || 如果只读 || 禁用增加按钮
248
+ if (props.disable || props.readonly || props.disablePlus) {
249
+ // 则禁用增加按钮
250
+ return true
251
+ }
252
+
253
+ // 如果没有当前最大值
254
+ if (currentMax.value === null) {
255
+ // 则不禁用增加按钮
256
+ return false
257
+ }
258
+
259
+ // 将当前值转为 BigNumber 类型
260
+ const val = new BigNumber(currentValue.value)
261
+
262
+ // 如果当前值不是有效数字
263
+ if (! val.isFinite()) {
264
+ // 则禁用减少按钮
265
+ return true
266
+ }
267
+
268
+ // 当前值 >= 当前最大值
269
+ return val.gte(currentMax.value)
170
270
  })
171
271
 
272
+ // ==========【数据】============================================================================================
273
+
274
+ // 格式化为当前值
275
+ const currentValue = ref(formatToCurrentValue(props.modelValue, true))
276
+
277
+ // 如果当前值 !== 声明值
278
+ const rawModelValue = formatToModelValue(currentValue.value)
279
+ if (rawModelValue !== props.modelValue) {
280
+ // 则更新值
281
+ emitModelValue(rawModelValue)
282
+ }
283
+
284
+ // ==========【监听数据】=========================================================================================
285
+
172
286
  /**
173
- * 监听其他
287
+ * 监听声明值
174
288
  */
175
- watch([()=>props.max, ()=>props.min, ()=>props.integer, ()=>props.decimalLength], function () {
176
- const val = format(currentValue.value)
177
- if (! _.isEqual(val, currentValue.value)) {
289
+ watch(()=>props.modelValue, function (val) {
290
+
291
+ // 格式化为当前值
292
+ val = formatToCurrentValue(val, true)
293
+
294
+ // 如果当前值有变化
295
+ if (val !== currentValue.value) {
178
296
  currentValue.value = val
179
297
  }
180
298
  })
181
299
 
300
+ /**
301
+ * 监听 当前最小值 / 当前最大值 / 当前小数位数
302
+ */
303
+ watch([currentMin, currentMax, currentDecimalLength], function () {
304
+
305
+ // 格式化当前值
306
+ const val = formatToCurrentValue(currentValue.value, false)
307
+
308
+ // 如果当前值有变化
309
+ if (val !== currentValue.value) {
310
+
311
+ // 更新当前值
312
+ currentValue.value = val
313
+
314
+ // 更新值
315
+ emitModelValue(formatToModelValue(val))
316
+ }
317
+ })
318
+
182
319
  // ==========【方法】=============================================================================================
183
320
 
184
321
  /**
185
- * 格式化数字
322
+ * 更新值
186
323
  */
187
- function formatNumber(value) {
188
- return formatNumbers(String(value), ! props.integer)
324
+ function emitModelValue(val) {
325
+ // 更新值
326
+ emit('update:modelValue', val)
189
327
  }
190
328
 
191
329
  /**
192
- * 格式化
330
+ * 格式化数字
193
331
  */
194
- function format(value) {
195
- if (props.allowEmpty && value === '') {
196
- return value
197
- }
332
+ function formatNumber(val, isCentToYuan, isToNumber, defaultValue) {
198
333
 
199
- value = formatNumber(value)
334
+ // 转为 BigNumber 类型
335
+ val = new BigNumber(val)
200
336
 
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)
337
+ // 如果为有效数字
338
+ if (val.isFinite()) {
205
339
 
206
- // 格式化小数位数
207
- if (! _.isNil(props.decimalLength)) {
208
- value = value.toFixed(props.decimalLength)
340
+ // 如果不为 0
341
+ if (! val.isZero()) {
342
+
343
+ // 如果为人民币的分转元
344
+ if (props.centToYuan && isCentToYuan) {
345
+ // 除 100
346
+ val = val.dividedBy(100)
347
+ }
348
+
349
+ // 如果设置了小数位数
350
+ if (currentDecimalLength.value > 0) {
351
+
352
+ // 如果值精度 > 设置的小数位数
353
+ if (val.dp() > currentDecimalLength.value) {
354
+ // 将值向下舍入 xx 位精度(如 68.345 -> 68.34)
355
+ val = val.dp(currentDecimalLength.value, BigNumber.ROUND_DOWN)
356
+ }
357
+
358
+ // 否则值为整数
359
+ } else {
360
+ // 将值向下取整
361
+ val = val.integerValue(BigNumber.ROUND_DOWN)
362
+ }
363
+ }
364
+
365
+ // 转为数字
366
+ return isToNumber ? val.toNumber() : val
209
367
  }
210
368
 
211
- return value
369
+ return defaultValue
212
370
  }
213
371
 
214
372
  /**
215
- * 输入触发
373
+ * 格式化为更新值
216
374
  */
217
- function onInput(value) {
375
+ function formatToModelValue(value) {
376
+
377
+ // 转为 BigNumber 类型
378
+ let val = new BigNumber(value)
218
379
 
219
- let formatted = formatNumber(value)
380
+ // 如果不是有效数字
381
+ if (! val.isFinite()) {
220
382
 
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)}`
383
+ // 返回当前值
384
+ return value
225
385
  }
226
386
 
227
- // prefer number type
228
- if (formatted === String(+formatted)) {
229
- formatted = +formatted
387
+ // 如果为人民币的分转元
388
+ if (props.centToYuan) {
389
+ // 乘以 100
390
+ val = val.times(100)
391
+
392
+ // 如果值有精度
393
+ if (val.dp()) {
394
+ // 将值向下取整
395
+ val = val.integerValue(BigNumber.ROUND_DOWN)
396
+ }
230
397
  }
231
398
 
232
- emitChange(formatted)
399
+ // 将值转为数字
400
+ return val.toNumber()
233
401
  }
234
402
 
235
403
  /**
236
- * 失去焦点触发
404
+ * 格式化值
237
405
  */
238
- function onBlur(value) {
239
- value = format(value)
240
- emitChange(value)
241
- emit('blur', value)
242
- }
406
+ function formatToCurrentValue(value, isCentToYuan) {
243
407
 
244
- /**
245
- * 提交改变值
246
- */
247
- function emitChange(value) {
408
+ // 格式化数字
409
+ const val = formatNumber(value, isCentToYuan, false, false)
410
+
411
+ // 如果为有效数字
412
+ if (val !== false) {
413
+
414
+ // 如果值 >= 最大值
415
+ if (currentMax.value !== null && val.gte(currentMax.value)) {
416
+
417
+ // 返回最大值
418
+ return currentMax.value
419
+ }
248
420
 
249
- // 是否开启异步变更,开启后需要手动控制输入值
250
- if (props.asyncChange) {
251
- emit('input', value)
252
- emit('change', value)
253
- return
421
+ // 如果值 <= 最小值
422
+ if (currentMin.value !== null && val.lte(currentMin.value)) {
423
+
424
+ // 返回最小值
425
+ return currentMin.value
426
+ }
427
+
428
+ // 将值转为数字
429
+ return val.toNumber()
430
+ }
431
+
432
+ if (
433
+ // 如果不允许值为空
434
+ props.noEmpty
435
+ // 如果有最小值
436
+ && currentMin.value !== null
437
+ ) {
438
+ // 则返回最小值
439
+ return currentMin.value
254
440
  }
255
441
 
256
- currentValue.value = value
442
+ return ''
257
443
  }
258
444
 
259
445
  /**
260
- * 改变值
446
+ * 失去焦点触发
261
447
  */
262
- function onChange(type) {
448
+ function onBlur() {
263
449
 
264
- if (props[`${type}Disabled`]) {
265
- emit('overlimit', type)
266
- return
267
- }
450
+ // 格式化当前值
451
+ let val = formatToCurrentValue(currentValue.value, false)
268
452
 
269
- const diff = type === 'minus' ? -props.step : +props.step
453
+ // 如果当前值有变动
454
+ if (val !== currentValue.value) {
270
455
 
271
- const value = format(addNumber(+currentValue.value, diff))
456
+ // 更新当前值
457
+ currentValue.value = val
458
+
459
+ // 将当前值转为声明值
460
+ val = formatToModelValue(val)
461
+
462
+ // 更新值
463
+ emitModelValue(val)
272
464
 
273
- emitChange(value)
274
- emit(type)
465
+ // 失去焦点触发
466
+ emit('blur', val)
467
+ }
275
468
  }
276
469
 
277
470
  /**
278
- * 点击不可用的按钮时触发
471
+ * 改变值
279
472
  */
280
- function onOverlimit(type) {
281
- emit('overlimit', type)
473
+ function onChange(type) {
474
+
475
+ // 格式化当前值
476
+ const val = formatToCurrentValue(
477
+ new BigNumber(+currentValue.value)
478
+ // 增加 / 减少
479
+ .plus(type === 'minus' ? -currentStep.value : +currentStep.value)
480
+ .toNumber()
481
+ , false
482
+ )
483
+
484
+ // 如果当前值有变动
485
+ if (val !== currentValue.value) {
486
+
487
+ // 更新当前值
488
+ currentValue.value = val
489
+
490
+ // 更新值
491
+ emitModelValue(formatToModelValue(val))
492
+ }
282
493
  }
283
494
 
284
495
  // ==========【返回】=============================================================================================
285
496
 
286
497
  return {
498
+ // 插槽标识
499
+ slotNames,
287
500
  // 当前值
288
501
  currentValue,
502
+ // 当前是否禁用减少按钮
503
+ currentDisableMinus,
504
+ // 当前是否禁用增加按钮
505
+ currentDisablePlus,
289
506
 
290
- // 是否禁用减少按钮
291
- minusDisabled,
292
- // 是否禁用增加按钮
293
- plusDisabled,
294
-
295
- // 输入触发
296
- onInput,
297
507
  // 失去焦点触发
298
508
  onBlur,
299
509
  // 改变值
300
510
  onChange,
301
- // 点击不可用的按钮时触发
302
- onOverlimit,
303
511
  }
304
512
  }
305
513
  }
@@ -309,16 +517,29 @@ export default {
309
517
  @import "@/assets/sass/var.scss";
310
518
 
311
519
  .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;
520
+
521
+ // 居中显示
522
+ &--center {
523
+ &.q-field {
524
+ &--outlined {
525
+ .q-field__control {
526
+ .q-field__native {
527
+ text-align: center;
528
+ }
318
529
  }
319
530
  }
320
531
  }
321
532
  }
533
+
534
+ // 左边按钮
535
+ &__left {
536
+ margin-left: -8px;
537
+ }
538
+
539
+ // 右边按钮
540
+ &__right {
541
+ margin-right: -8px;
542
+ }
322
543
  }
323
544
  </style>
324
545