init
This commit is contained in:
86
uni_modules/nutui-uni/components/inputnumber/index.scss
Normal file
86
uni_modules/nutui-uni/components/inputnumber/index.scss
Normal file
@@ -0,0 +1,86 @@
|
||||
.nut-theme-dark {
|
||||
.nut-input-number {
|
||||
&__icon {
|
||||
color: $dark-color;
|
||||
|
||||
&--disabled {
|
||||
color: $dark-color-gray;
|
||||
}
|
||||
}
|
||||
|
||||
input,
|
||||
&__text--readonly {
|
||||
color: $dark-color;
|
||||
background-color: $dark-background;
|
||||
border: 1px solid $dark-color-gray;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
input {
|
||||
color: $dark-color-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nut-input-number {
|
||||
box-sizing: $inputnumber-border-box;
|
||||
display: $inputnumber-display;
|
||||
align-items: center;
|
||||
height: $inputnumber-height;
|
||||
line-height: $inputnumber-line-height;
|
||||
border: $inputnumber-border;
|
||||
border-radius: $inputnumber-border-radius;
|
||||
|
||||
&--disabled {
|
||||
input {
|
||||
color: $inputnumber-icon-void-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: $inputnumber-icon-color;
|
||||
cursor: pointer;
|
||||
|
||||
.nut-icon {
|
||||
width: $inputnumber-icon-size;
|
||||
height: $inputnumber-icon-size;
|
||||
font-size: $inputnumber-icon-size;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
color: $inputnumber-icon-void-color;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
border-top: 0 !important;
|
||||
border-bottom: 0 !important;
|
||||
}
|
||||
|
||||
input,
|
||||
&__text--readonly,
|
||||
&__text--input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: $inputnumber-input-width;
|
||||
height: 100%;
|
||||
margin: $inputnumber-input-margin;
|
||||
font-size: $inputnumber-input-font-size;
|
||||
color: $inputnumber-input-font-color;
|
||||
text-align: center;
|
||||
background-color: $inputnumber-input-background-color;
|
||||
border: $inputnumber-input-border;
|
||||
border-radius: $inputnumber-input-border-radius;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
appearance: none;
|
||||
}
|
||||
}
|
||||
1
uni_modules/nutui-uni/components/inputnumber/index.ts
Normal file
1
uni_modules/nutui-uni/components/inputnumber/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './inputnumber'
|
||||
64
uni_modules/nutui-uni/components/inputnumber/inputnumber.ts
Normal file
64
uni_modules/nutui-uni/components/inputnumber/inputnumber.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import type { BaseEvent, InputOnBlurEvent, InputOnFocusEvent } from '@uni-helper/uni-app-types'
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import { BLUR_EVENT, CHANGE_EVENT, FOCUS_EVENT, UPDATE_MODEL_EVENT } from '../_constants'
|
||||
import { commonProps, makeNumericProp, nullableBooleanProp } from '../_utils'
|
||||
|
||||
export const inputnumberProps = {
|
||||
...commonProps,
|
||||
/**
|
||||
* @description 初始值
|
||||
*/
|
||||
modelValue: makeNumericProp(0),
|
||||
/**
|
||||
* @description 最小值限制
|
||||
*/
|
||||
min: makeNumericProp(1),
|
||||
/**
|
||||
* @description 最大值限制
|
||||
*/
|
||||
max: makeNumericProp(9999),
|
||||
/**
|
||||
* @description 步长
|
||||
*/
|
||||
step: makeNumericProp(1),
|
||||
/**
|
||||
* @description 是否只能输入 step 的倍数
|
||||
*/
|
||||
stepStrictly: Boolean,
|
||||
/**
|
||||
* @description 设置保留的小数位
|
||||
*/
|
||||
decimalPlaces: makeNumericProp(0),
|
||||
/**
|
||||
* @description 禁用所有功能
|
||||
*/
|
||||
disabled: nullableBooleanProp,
|
||||
/**
|
||||
* @description 只读状态禁用输入框操作行为
|
||||
*/
|
||||
readonly: Boolean,
|
||||
/**
|
||||
* @description 输入框宽度
|
||||
*/
|
||||
inputWidth: makeNumericProp(''),
|
||||
/**
|
||||
* @description 操作加减按钮的尺寸
|
||||
*/
|
||||
buttonSize: makeNumericProp(''),
|
||||
}
|
||||
|
||||
export type InputNumberProps = ExtractPropTypes<typeof inputnumberProps>
|
||||
|
||||
/* eslint-disable unused-imports/no-unused-vars */
|
||||
export const inputnumberEmits = {
|
||||
[UPDATE_MODEL_EVENT]: (value: number) => true,
|
||||
[CHANGE_EVENT]: (value: number, event?: BaseEvent) => true,
|
||||
[FOCUS_EVENT]: (event: InputOnFocusEvent) => true,
|
||||
[BLUR_EVENT]: (event: InputOnBlurEvent) => true,
|
||||
reduce: (event: BaseEvent) => true,
|
||||
add: (event: BaseEvent) => true,
|
||||
overlimit: (event: BaseEvent, type: 'reduce' | 'add') => true,
|
||||
}
|
||||
/* eslint-enable unused-imports/no-unused-vars */
|
||||
|
||||
export type InputNumberEmits = typeof inputnumberEmits
|
||||
323
uni_modules/nutui-uni/components/inputnumber/inputnumber.vue
Normal file
323
uni_modules/nutui-uni/components/inputnumber/inputnumber.vue
Normal file
@@ -0,0 +1,323 @@
|
||||
<script lang="ts" setup>
|
||||
import type { BaseEvent, InputOnBlurEvent, InputOnFocusEvent, InputOnInputEvent } from '@uni-helper/uni-app-types'
|
||||
import type { CSSProperties } from 'vue'
|
||||
import { computed, defineComponent, nextTick, onMounted, ref, toRef, useSlots, watch } from 'vue'
|
||||
import { BLUR_EVENT, CHANGE_EVENT, FOCUS_EVENT, PREFIX, UPDATE_MODEL_EVENT } from '../_constants'
|
||||
import { getMainClass, pxCheck } from '../_utils'
|
||||
import { useFormDisabled } from '../form/form'
|
||||
import NutIcon from '../icon/icon.vue'
|
||||
import { inputnumberEmits, inputnumberProps } from './inputnumber'
|
||||
|
||||
type UpdateSource = '' | 'click' | 'input' | 'blur'
|
||||
|
||||
const props = defineProps(inputnumberProps)
|
||||
|
||||
const emit = defineEmits(inputnumberEmits)
|
||||
|
||||
const slots = useSlots()
|
||||
|
||||
const formDisabled = useFormDisabled(toRef(props, 'disabled'))
|
||||
|
||||
const classes = computed(() => {
|
||||
return getMainClass(props, componentName, {
|
||||
[`${componentName}--disabled`]: formDisabled.value,
|
||||
})
|
||||
})
|
||||
|
||||
function toNumber(value: number | string) {
|
||||
if (typeof value === 'number') {
|
||||
return value
|
||||
}
|
||||
|
||||
return Number(value)
|
||||
}
|
||||
|
||||
const innerValue = computed(() => {
|
||||
return toNumber(props.modelValue)
|
||||
})
|
||||
|
||||
const innerMinValue = computed(() => {
|
||||
return toNumber(props.min)
|
||||
})
|
||||
|
||||
const innerMaxValue = computed(() => {
|
||||
return toNumber(props.max)
|
||||
})
|
||||
|
||||
const innerStepValue = computed(() => {
|
||||
return toNumber(props.step)
|
||||
})
|
||||
|
||||
const innerDigits = computed(() => {
|
||||
return toNumber(props.decimalPlaces)
|
||||
})
|
||||
|
||||
const allowDecrease = computed(() => {
|
||||
if (formDisabled.value) {
|
||||
return false
|
||||
}
|
||||
|
||||
return innerValue.value > innerMinValue.value
|
||||
})
|
||||
|
||||
const allowIncrease = computed(() => {
|
||||
if (formDisabled.value) {
|
||||
return false
|
||||
}
|
||||
|
||||
return innerValue.value < innerMaxValue.value
|
||||
})
|
||||
|
||||
const decreaseClasses = computed(() => {
|
||||
return {
|
||||
[`${componentName}__icon--disabled`]: !allowDecrease.value,
|
||||
}
|
||||
})
|
||||
|
||||
const inputStyles = computed(() => {
|
||||
const value: CSSProperties = {}
|
||||
|
||||
const { inputWidth, buttonSize } = props
|
||||
|
||||
if (inputWidth) {
|
||||
value.width = pxCheck(inputWidth)
|
||||
}
|
||||
if (buttonSize) {
|
||||
value.height = pxCheck(buttonSize)
|
||||
}
|
||||
|
||||
return value
|
||||
})
|
||||
|
||||
const increaseClasses = computed(() => {
|
||||
return {
|
||||
[`${componentName}__icon--disabled`]: !allowIncrease.value,
|
||||
}
|
||||
})
|
||||
|
||||
const inputValue = ref('')
|
||||
|
||||
let updateSource: UpdateSource = ''
|
||||
|
||||
function precisionValue(value: number, type: 'number'): number
|
||||
function precisionValue(value: number, type: 'string'): string
|
||||
function precisionValue(value: number, type: 'number' | 'string') {
|
||||
const fixedValue = value.toFixed(innerDigits.value)
|
||||
|
||||
if (type === 'string') {
|
||||
return fixedValue
|
||||
}
|
||||
|
||||
return Number(fixedValue)
|
||||
}
|
||||
|
||||
function updateInputValue(value: number) {
|
||||
const finalValue = precisionValue(value, 'string')
|
||||
|
||||
if (finalValue !== inputValue.value) {
|
||||
inputValue.value = finalValue
|
||||
}
|
||||
else {
|
||||
inputValue.value = ''
|
||||
|
||||
nextTick(() => {
|
||||
inputValue.value = finalValue
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function formatValue(value: number | string) {
|
||||
let trulyValue = Math.max(
|
||||
innerMinValue.value,
|
||||
Math.min(
|
||||
innerMaxValue.value,
|
||||
toNumber(value),
|
||||
),
|
||||
)
|
||||
|
||||
if (props.stepStrictly) {
|
||||
trulyValue = Math.round(trulyValue / innerStepValue.value) * innerStepValue.value
|
||||
}
|
||||
|
||||
return precisionValue(trulyValue, 'number')
|
||||
}
|
||||
|
||||
function emitChange(source: UpdateSource, value: number | string, event?: BaseEvent) {
|
||||
updateSource = source
|
||||
|
||||
const formattedValue = formatValue(value)
|
||||
|
||||
if (['', 'blur'].includes(updateSource)) {
|
||||
updateInputValue(formattedValue)
|
||||
}
|
||||
|
||||
if (formattedValue !== props.modelValue) {
|
||||
emit(UPDATE_MODEL_EVENT, formattedValue)
|
||||
emit(CHANGE_EVENT, formattedValue, event)
|
||||
}
|
||||
}
|
||||
|
||||
function handleInput(event: InputOnInputEvent) {
|
||||
if (formDisabled.value || props.readonly)
|
||||
return
|
||||
|
||||
emitChange('input', event.detail.value, event)
|
||||
}
|
||||
|
||||
function handleDecrease(event: BaseEvent) {
|
||||
if (formDisabled.value)
|
||||
return
|
||||
|
||||
emit('reduce', event)
|
||||
|
||||
const finalValue = innerValue.value - innerStepValue.value
|
||||
|
||||
if (allowDecrease.value && finalValue >= innerMinValue.value) {
|
||||
emitChange('click', finalValue, event)
|
||||
}
|
||||
else {
|
||||
emit('overlimit', event, 'reduce')
|
||||
|
||||
emitChange('click', innerMinValue.value, event)
|
||||
}
|
||||
}
|
||||
|
||||
function handleIncrease(event: BaseEvent) {
|
||||
if (formDisabled.value)
|
||||
return
|
||||
|
||||
emit('add', event)
|
||||
|
||||
const finalValue = innerValue.value + innerStepValue.value
|
||||
|
||||
if (allowIncrease.value && finalValue <= innerMaxValue.value) {
|
||||
emitChange('click', finalValue, event)
|
||||
}
|
||||
else {
|
||||
emit('overlimit', event, 'add')
|
||||
|
||||
emitChange('click', innerMaxValue.value, event)
|
||||
}
|
||||
}
|
||||
|
||||
function handleFocus(event: InputOnFocusEvent) {
|
||||
if (formDisabled.value || props.readonly)
|
||||
return
|
||||
|
||||
emit(FOCUS_EVENT, event)
|
||||
}
|
||||
|
||||
function handleBlur(event: InputOnBlurEvent) {
|
||||
if (formDisabled.value || props.readonly)
|
||||
return
|
||||
|
||||
emit(BLUR_EVENT, event)
|
||||
|
||||
emitChange('blur', event.detail.value, event)
|
||||
}
|
||||
|
||||
function correctValue() {
|
||||
emitChange('', props.modelValue)
|
||||
}
|
||||
|
||||
watch(() => props.modelValue, () => {
|
||||
if (updateSource === 'input') {
|
||||
updateSource = ''
|
||||
return
|
||||
}
|
||||
|
||||
correctValue()
|
||||
})
|
||||
|
||||
watch(() => [
|
||||
props.min,
|
||||
props.max,
|
||||
props.step,
|
||||
props.stepStrictly,
|
||||
props.decimalPlaces,
|
||||
], () => {
|
||||
correctValue()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
correctValue()
|
||||
})
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
const componentName = `${PREFIX}-input-number`
|
||||
|
||||
export default defineComponent({
|
||||
name: componentName,
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view :class="classes" :style="props.customStyle">
|
||||
<view
|
||||
class="nut-input-number__icon nut-input-number__left"
|
||||
:class="decreaseClasses"
|
||||
@click="handleDecrease"
|
||||
>
|
||||
<slot v-if="slots.leftIcon" name="leftIcon" />
|
||||
|
||||
<NutIcon v-else name="minus" :size="props.buttonSize" />
|
||||
</view>
|
||||
|
||||
<view v-if="props.readonly" class="nut-input-number__text--readonly">
|
||||
{{ inputValue }}
|
||||
</view>
|
||||
|
||||
<template v-else>
|
||||
<!-- #ifdef H5 -->
|
||||
<input
|
||||
v-model="inputValue"
|
||||
v-bind="$attrs"
|
||||
class="nut-input-number__text--input"
|
||||
:style="inputStyles"
|
||||
type="number"
|
||||
:min="props.min"
|
||||
:max="props.max"
|
||||
:disabled="formDisabled"
|
||||
@input="handleInput"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifndef H5 -->
|
||||
<input
|
||||
v-model="inputValue"
|
||||
class="nut-input-number__text--input"
|
||||
:style="inputStyles"
|
||||
type="number"
|
||||
:min="props.min"
|
||||
:max="props.max"
|
||||
:disabled="formDisabled"
|
||||
@input="handleInput"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<view
|
||||
class="nut-input-number__icon nut-input-number__right"
|
||||
:class="increaseClasses"
|
||||
@click="handleIncrease"
|
||||
>
|
||||
<slot v-if="slots.rightIcon" name="rightIcon" />
|
||||
|
||||
<NutIcon v-else name="plus" :size="props.buttonSize" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "./index";
|
||||
</style>
|
||||
Reference in New Issue
Block a user