mirror of
https://gitee.com/wot-design-uni/wot-design-uni.git
synced 2025-12-06 17:18:40 +08:00
feat: ✨ 修复 InputNumber 在设置为 allow-null 时被赋值为空时未触发更新的问题并支持异步更新 (#812)
This commit is contained in:
parent
583acc2fa9
commit
0fc90ddcc9
@ -96,6 +96,34 @@ function handleChange1({ value }) {
|
||||
}
|
||||
```
|
||||
|
||||
## 异步变更
|
||||
通过 `before-change` 可以在输入值变化前进行校验和拦截。
|
||||
|
||||
```html
|
||||
<wd-input-number v-model="value" :before-change="beforeChange" />
|
||||
```
|
||||
|
||||
```typescript
|
||||
import { ref } from 'vue'
|
||||
import { useToast } from '@/uni_modules/wot-design-uni'
|
||||
import type { InputNumberBeforeChange } from '@/uni_modules/wot-design-uni/components/wd-input-number/types'
|
||||
const { loading, close } = useToast()
|
||||
|
||||
const value = ref<number>(1)
|
||||
|
||||
const beforeChange: InputNumberBeforeChange = (value) => {
|
||||
loading({ msg: `正在更新到${value}...` })
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
close()
|
||||
resolve(true)
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Attributes
|
||||
|
||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 | 最低版本 |
|
||||
@ -109,12 +137,13 @@ function handleChange1({ value }) {
|
||||
| disabled | 禁用 | boolean | - | false | - |
|
||||
| without-input | 不显示输入框 | boolean | - | false | - |
|
||||
| input-width | 输入框宽度 | string | - | 36px | - |
|
||||
| allow-null | 允许空值 | boolean | - | false | - |
|
||||
| allow-null | 是否允许输入的值为空,设置为 `true` 后允许传入空字符串 | boolean | - | false | - |
|
||||
| placeholder | 占位文本 | string | - | - | - |
|
||||
| disable-input | 禁用输入框 | boolean | - | false | 0.2.14 |
|
||||
| disable-plus | 禁用增加按钮 | boolean | - | false | 0.2.14 |
|
||||
| disable-minus | 禁用减少按钮 | boolean | - | false | 0.2.14 |
|
||||
| adjustPosition | 原生属性,键盘弹起时,是否自动上推页面 | boolean | - | true | 1.3.11 |
|
||||
| before-change | 输入框值改变前触发,返回 false 会阻止输入框值改变,支持返回 `Promise` | `(value: number \| string) => boolean \| Promise<boolean>` | - | - | $LOWEST_VERSION$ |
|
||||
|
||||
|
||||
## Events
|
||||
|
||||
@ -33,10 +33,16 @@
|
||||
<demo-block title="允许空值,并设置 placeholder">
|
||||
<wd-input-number v-model="value9" allow-null placeholder="不限" input-width="70px" @change="handleChange9" />
|
||||
</demo-block>
|
||||
<demo-block title="异步变更">
|
||||
<wd-input-number v-model="value11" :before-change="beforeChange" />
|
||||
</demo-block>
|
||||
</page-wraper>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useToast } from '@/uni_modules/wot-design-uni'
|
||||
import type { InputNumberBeforeChange } from '@/uni_modules/wot-design-uni/components/wd-input-number/types'
|
||||
import { ref } from 'vue'
|
||||
const { loading, close } = useToast()
|
||||
|
||||
const value1 = ref<number>(1)
|
||||
const value2 = ref<number>(1)
|
||||
@ -48,6 +54,7 @@ const value7 = ref<number>(1)
|
||||
const value8 = ref<number>(2)
|
||||
const value9 = ref<string>('')
|
||||
const value10 = ref<number>(1)
|
||||
const value11 = ref<number>(1)
|
||||
|
||||
function handleChange1({ value }: any) {
|
||||
console.log(value)
|
||||
@ -76,6 +83,16 @@ function handleChange8({ value }: any) {
|
||||
function handleChange9({ value }: any) {
|
||||
console.log(value)
|
||||
}
|
||||
|
||||
const beforeChange: InputNumberBeforeChange = (value) => {
|
||||
loading({ msg: `正在更新到${value}...` })
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
close()
|
||||
resolve(true)
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.flex {
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
import { isPromise } from './util'
|
||||
|
||||
function noop() {}
|
||||
|
||||
export type Interceptor = (...args: any[]) => Promise<boolean> | boolean | undefined | void
|
||||
|
||||
export function callInterceptor(
|
||||
interceptor: Interceptor | undefined,
|
||||
{
|
||||
args = [],
|
||||
done,
|
||||
canceled,
|
||||
error
|
||||
}: {
|
||||
args?: unknown[]
|
||||
done: () => void
|
||||
canceled?: () => void
|
||||
error?: () => void
|
||||
}
|
||||
) {
|
||||
if (interceptor) {
|
||||
// eslint-disable-next-line prefer-spread
|
||||
const returnVal = interceptor.apply(null, args)
|
||||
|
||||
if (isPromise(returnVal)) {
|
||||
returnVal
|
||||
.then((value) => {
|
||||
if (value) {
|
||||
done()
|
||||
} else if (canceled) {
|
||||
canceled()
|
||||
}
|
||||
})
|
||||
.catch(error || noop)
|
||||
} else if (returnVal) {
|
||||
done()
|
||||
} else if (canceled) {
|
||||
canceled()
|
||||
}
|
||||
} else {
|
||||
done()
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,17 @@
|
||||
/*
|
||||
* @Author: weisheng
|
||||
* @Date: 2024-03-15 20:40:34
|
||||
* @LastEditTime: 2024-09-18 09:49:12
|
||||
* @LastEditTime: 2024-12-31 00:33:21
|
||||
* @LastEditors: weisheng
|
||||
* @Description:
|
||||
* @FilePath: \wot-design-uni\src\uni_modules\wot-design-uni\components\wd-input-number\types.ts
|
||||
* @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-input-number/types.ts
|
||||
* 记得注释
|
||||
*/
|
||||
import type { PropType } from 'vue'
|
||||
import { baseProps, makeBooleanProp, makeNumberProp, makeNumericProp, makeRequiredProp, makeStringProp, numericProp } from '../common/props'
|
||||
|
||||
export type InputNumberBeforeChange = (value: number | string) => boolean | Promise<boolean>
|
||||
|
||||
export const inputNumberProps = {
|
||||
...baseProps,
|
||||
/**
|
||||
@ -70,5 +73,9 @@ export const inputNumberProps = {
|
||||
/**
|
||||
* 原生属性,键盘弹起时,是否自动上推页面
|
||||
*/
|
||||
adjustPosition: makeBooleanProp(true)
|
||||
adjustPosition: makeBooleanProp(true),
|
||||
/**
|
||||
* 输入值变化前的回调函数,返回 `false` 可阻止输入,支持返回 `Promise`
|
||||
*/
|
||||
beforeChange: Function as PropType<InputNumberBeforeChange>
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
:style="`${inputWidth ? 'width: ' + inputWidth : ''}`"
|
||||
type="digit"
|
||||
:disabled="disabled || disableInput"
|
||||
v-model="inputValue"
|
||||
:value="String(inputValue)"
|
||||
:placeholder="placeholder"
|
||||
:adjust-position="adjustPosition"
|
||||
@input="handleInput"
|
||||
@ -37,57 +37,51 @@ export default {
|
||||
|
||||
<script lang="ts" setup>
|
||||
import wdIcon from '../wd-icon/wd-icon.vue'
|
||||
import { ref, watch } from 'vue'
|
||||
import { debounce, isDef, isEqual } from '../common/util'
|
||||
import { computed, nextTick, ref, watch } from 'vue'
|
||||
import { isDef, isEqual } from '../common/util'
|
||||
import { inputNumberProps } from './types'
|
||||
import { callInterceptor } from '../common/interceptor'
|
||||
|
||||
const props = defineProps(inputNumberProps)
|
||||
const emit = defineEmits(['focus', 'blur', 'change', 'update:modelValue'])
|
||||
const inputValue = ref<string | number>(getInitValue()) // 输入框的值
|
||||
|
||||
const minDisabled = ref<boolean>(false)
|
||||
const maxDisabled = ref<boolean>(false)
|
||||
const inputValue = ref<string | number>('') // 输入框的值
|
||||
// 减号是否禁用
|
||||
const minDisabled = computed(() => {
|
||||
const value = formatValue(inputValue.value)
|
||||
const { disabled, min, step } = props
|
||||
return disabled || Number(value) <= min || changeStep(value, -step) < min
|
||||
})
|
||||
|
||||
// 加号是否禁用
|
||||
const maxDisabled = computed(() => {
|
||||
const value = formatValue(inputValue.value)
|
||||
const { disabled, max, step } = props
|
||||
return disabled || Number(value) >= max || changeStep(value, step) > max
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newValue) => {
|
||||
inputValue.value = newValue
|
||||
splitDisabled(newValue)
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
(value) => {
|
||||
updateValue(value)
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
[() => props.max, () => props.min],
|
||||
() => {
|
||||
updateBoundary()
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
watch([() => props.max, () => props.min, () => props.precision], () => {
|
||||
const value = formatValue(inputValue.value)
|
||||
updateValue(value)
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.disabled,
|
||||
(newValue) => {
|
||||
minDisabled.value = newValue
|
||||
maxDisabled.value = newValue
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
function updateBoundary() {
|
||||
debounce(() => {
|
||||
const value = formatValue(inputValue.value)
|
||||
if (!isEqual(inputValue.value, value)) {
|
||||
setValue(value)
|
||||
}
|
||||
splitDisabled(value)
|
||||
}, 30)()
|
||||
function isValueEqual(value1: number | string, value2: number | string) {
|
||||
return isEqual(String(value1), String(value2))
|
||||
}
|
||||
|
||||
function splitDisabled(value: number | string) {
|
||||
const { disabled, min, max, step } = props
|
||||
minDisabled.value = disabled || Number(value) <= min || changeStep(value, -step) < min
|
||||
maxDisabled.value = disabled || Number(value) >= max || changeStep(value, step) > max
|
||||
function getInitValue() {
|
||||
const formatted = formatValue(props.modelValue)
|
||||
if (!isValueEqual(formatted, props.modelValue)) {
|
||||
emit('update:modelValue', formatted)
|
||||
}
|
||||
return formatted
|
||||
}
|
||||
|
||||
function toPrecision(value: number) {
|
||||
@ -95,7 +89,7 @@ function toPrecision(value: number) {
|
||||
}
|
||||
|
||||
function getPrecision(value?: number) {
|
||||
if (value === undefined) return 0
|
||||
if (!isDef(value)) return 0
|
||||
const valueString = value.toString()
|
||||
const dotPosition = valueString.indexOf('.')
|
||||
let precision = 0
|
||||
@ -111,103 +105,93 @@ function toStrictlyStep(value: number | string) {
|
||||
return (Math.round(Number(value) / props.step) * precisionFactory * props.step) / precisionFactory
|
||||
}
|
||||
|
||||
function setValue(value: string | number, change: boolean = true) {
|
||||
if (props.allowNull && (!isDef(value) || value === '')) {
|
||||
dispatchChangeEvent(value, change)
|
||||
function updateValue(value: string | number, fromUser: boolean = false) {
|
||||
if (isValueEqual(value, inputValue.value)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (props.stepStrictly) {
|
||||
value = toStrictlyStep(value)
|
||||
const update = () => {
|
||||
inputValue.value = value
|
||||
const formatted = formatValue(value)
|
||||
nextTick(() => {
|
||||
inputValue.value = formatted
|
||||
emit('update:modelValue', inputValue.value)
|
||||
emit('change', { value: inputValue.value })
|
||||
})
|
||||
}
|
||||
if ((value || value === 0) && props.precision !== undefined) {
|
||||
value = toPrecision(Number(value))
|
||||
}
|
||||
if (Number(value) > props.max) value = toPrecision(props.max)
|
||||
if (Number(value) < props.min) value = toPrecision(props.min)
|
||||
|
||||
dispatchChangeEvent(value, change)
|
||||
if (fromUser) {
|
||||
callInterceptor(props.beforeChange, {
|
||||
args: [value],
|
||||
done: update
|
||||
})
|
||||
} else {
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
||||
function changeStep(val: string | number, step: number) {
|
||||
val = Number(val)
|
||||
|
||||
if (isNaN(val)) {
|
||||
return props.min
|
||||
}
|
||||
|
||||
const precisionFactory = Math.pow(10, props.precision)
|
||||
return toPrecision((val * precisionFactory + step * precisionFactory) / precisionFactory)
|
||||
}
|
||||
|
||||
function sub() {
|
||||
if (minDisabled.value || props.disableMinus) return
|
||||
function changeValue(step: number) {
|
||||
if ((step < 0 && (minDisabled.value || props.disableMinus)) || (step > 0 && (maxDisabled.value || props.disablePlus))) return
|
||||
const value = changeStep(inputValue.value, step)
|
||||
updateValue(value, true)
|
||||
}
|
||||
|
||||
const newValue = changeStep(inputValue.value, -props.step)
|
||||
dispatchChangeEvent(newValue)
|
||||
function sub() {
|
||||
changeValue(-props.step)
|
||||
}
|
||||
|
||||
function add() {
|
||||
if (maxDisabled.value || props.disablePlus) return
|
||||
|
||||
const newValue = changeStep(inputValue.value, props.step)
|
||||
dispatchChangeEvent(newValue)
|
||||
changeValue(props.step)
|
||||
}
|
||||
|
||||
function handleInput(event: any) {
|
||||
const value = event.detail.value || ''
|
||||
dispatchChangeEvent(value)
|
||||
updateValue(value, true)
|
||||
}
|
||||
|
||||
function handleFocus(event: any) {
|
||||
emit('focus', event.detail)
|
||||
}
|
||||
|
||||
function handleBlur() {
|
||||
const value = formatValue(inputValue.value)
|
||||
if (!isEqual(inputValue.value, value)) {
|
||||
setValue(value)
|
||||
}
|
||||
function handleBlur(event: any) {
|
||||
const value = event.detail.value || ''
|
||||
updateValue(value, true)
|
||||
emit('blur', {
|
||||
value
|
||||
})
|
||||
}
|
||||
|
||||
function dispatchChangeEvent(value: string | number, change: boolean = true) {
|
||||
if (isEqual(inputValue.value, value)) {
|
||||
return
|
||||
}
|
||||
inputValue.value = value
|
||||
change && emit('update:modelValue', inputValue.value)
|
||||
change && emit('change', { value })
|
||||
}
|
||||
|
||||
function formatValue(value: string | number) {
|
||||
if (props.allowNull && (!isDef(value) || value === '')) {
|
||||
return ''
|
||||
}
|
||||
|
||||
let formatValue = Number(value)
|
||||
let formatted = Number(value)
|
||||
|
||||
if (isNaN(formatValue)) {
|
||||
value = props.min
|
||||
if (isNaN(formatted)) {
|
||||
formatted = props.min
|
||||
}
|
||||
|
||||
if (props.stepStrictly) {
|
||||
formatValue = toStrictlyStep(value)
|
||||
formatted = toStrictlyStep(value)
|
||||
}
|
||||
|
||||
if (props.precision !== undefined) {
|
||||
formatValue = Number(formatValue.toFixed(props.precision))
|
||||
}
|
||||
if (formatValue > props.max) {
|
||||
formatValue = props.max
|
||||
}
|
||||
if (formatValue < props.min) {
|
||||
formatValue = props.min
|
||||
formatted = Math.min(Math.max(formatted, props.min), props.max)
|
||||
|
||||
if (isDef(props.precision)) {
|
||||
formatted = Number(formatted.toFixed(props.precision))
|
||||
}
|
||||
|
||||
return formatValue
|
||||
return formatted
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user