mirror of
https://gitee.com/wot-design-uni/wot-design-uni.git
synced 2025-12-07 01:28:30 +08:00
fix: 🐛 优化 InputNumbe 处理中间状态值的逻辑,支持配置不立即响应输入变化 (#1116)
This commit is contained in:
parent
cb408f553a
commit
ff99b22a69
@ -49,6 +49,17 @@ function handleChange({ value }) {
|
|||||||
<wd-input-number v-model="value" @change="handleChange" disable-input />
|
<wd-input-number v-model="value" @change="handleChange" disable-input />
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 禁用按钮
|
||||||
|
|
||||||
|
可以单独禁用增加或减少按钮。
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- 禁用减号按钮 -->
|
||||||
|
<wd-input-number v-model="value" @change="handleChange" disable-minus />
|
||||||
|
|
||||||
|
<!-- 禁用加号按钮 -->
|
||||||
|
<wd-input-number v-model="value" @change="handleChange" disable-plus />
|
||||||
|
```
|
||||||
|
|
||||||
## 无输入框
|
## 无输入框
|
||||||
|
|
||||||
@ -97,6 +108,49 @@ function handleChange({ value }) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 非立即更新模式
|
||||||
|
|
||||||
|
设置 `immediate-change` 为 `false`,输入框内容变化时不会立即触发 `change` 事件,仅在失焦或点击按钮时触发。
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- 立即更新模式(默认) -->
|
||||||
|
<wd-input-number v-model="value1" @change="handleChange" :immediate-change="true" />
|
||||||
|
|
||||||
|
<!-- 非立即更新模式 -->
|
||||||
|
<wd-input-number v-model="value2" @change="handleChange" :immediate-change="false" />
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const value1 = ref<number>(1)
|
||||||
|
const value2 = ref<number>(1)
|
||||||
|
function handleChange({ value }) {
|
||||||
|
console.log(value)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 初始化时自动更新
|
||||||
|
|
||||||
|
设置 `update-on-init` 属性控制是否在初始化时更新 `v-model` 为修正后的值。
|
||||||
|
|
||||||
|
- 当 `update-on-init="true"`(默认)时,会将初始值修正到符合 `min`、`max`、`step`、`precision` 等规则的有效值,并同步更新 `v-model`
|
||||||
|
- 当 `update-on-init="false"` 时,保持初始值不修正(不改变 `v-model`),但仍会进行显示格式化(如精度处理)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- 自动更新初始值(默认) -->
|
||||||
|
<wd-input-number v-model="value1" @change="handleChange" :update-on-init="true" :min="3" :max="15" :step="2" step-strictly />
|
||||||
|
|
||||||
|
<!-- 不更新初始值,保持原始值 -->
|
||||||
|
<wd-input-number v-model="value2" @change="handleChange" :update-on-init="false" :min="3" :max="15" :step="2" step-strictly />
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const value1 = ref<number>(1) // 会自动修正为4(≥3的最小2的倍数)
|
||||||
|
const value2 = ref<number>(1) // 保持为1,不会修正但会格式化显示
|
||||||
|
function handleChange({ value }) {
|
||||||
|
console.log(value)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## 异步变更
|
## 异步变更
|
||||||
|
|
||||||
通过 `before-change` 可以在输入值变化前进行校验和拦截。
|
通过 `before-change` 可以在输入值变化前进行校验和拦截。
|
||||||
@ -153,6 +207,9 @@ const beforeChange: InputNumberBeforeChange = (value) => {
|
|||||||
| adjustPosition | 原生属性,键盘弹起时,是否自动上推页面 | boolean | - | true | 1.3.11 |
|
| adjustPosition | 原生属性,键盘弹起时,是否自动上推页面 | boolean | - | true | 1.3.11 |
|
||||||
| before-change | 输入框值改变前触发,返回 false 会阻止输入框值改变,支持返回 `Promise` | `(value: number \| string) => boolean \| Promise<boolean>` | - | - | 1.6.0 |
|
| before-change | 输入框值改变前触发,返回 false 会阻止输入框值改变,支持返回 `Promise` | `(value: number \| string) => boolean \| Promise<boolean>` | - | - | 1.6.0 |
|
||||||
| long-press | 是否允许长按进行加减 | boolean | - | false | 1.8.0 |
|
| long-press | 是否允许长按进行加减 | boolean | - | false | 1.8.0 |
|
||||||
|
| immediate-change | 是否立即响应输入变化,false 时仅在失焦和按钮点击时更新 | boolean | - | true | $LOWEST_VERSION$ |
|
||||||
|
| update-on-init | 是否在初始化时更新 v-model 为修正后的值 | boolean | - | true | $LOWEST_VERSION$ |
|
||||||
|
| input-type | 输入框类型 | string | number / digit | digit | $LOWEST_VERSION$ |
|
||||||
|
|
||||||
|
|
||||||
## Events
|
## Events
|
||||||
|
|||||||
@ -49,6 +49,18 @@ Set the `disable-input` property.
|
|||||||
<wd-input-number v-model="value" @change="handleChange" disable-input />
|
<wd-input-number v-model="value" @change="handleChange" disable-input />
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Disable Buttons
|
||||||
|
|
||||||
|
You can disable the increase or decrease buttons individually.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Disable minus button -->
|
||||||
|
<wd-input-number v-model="value" @change="handleChange" disable-minus />
|
||||||
|
|
||||||
|
<!-- Disable plus button -->
|
||||||
|
<wd-input-number v-model="value" @change="handleChange" disable-plus />
|
||||||
|
```
|
||||||
|
|
||||||
## Without Input Box
|
## Without Input Box
|
||||||
|
|
||||||
Set `without-input` to hide the input box.
|
Set `without-input` to hide the input box.
|
||||||
@ -96,6 +108,49 @@ function handleChange({ value }) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Non-Immediate Update Mode
|
||||||
|
|
||||||
|
Set `immediate-change` to `false`, the `change` event will not be triggered immediately when the input box content changes, only when it loses focus or buttons are clicked.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Immediate update mode (default) -->
|
||||||
|
<wd-input-number v-model="value1" @change="handleChange" :immediate-change="true" />
|
||||||
|
|
||||||
|
<!-- Non-immediate update mode -->
|
||||||
|
<wd-input-number v-model="value2" @change="handleChange" :immediate-change="false" />
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const value1 = ref<number>(1)
|
||||||
|
const value2 = ref<number>(1)
|
||||||
|
function handleChange({ value }) {
|
||||||
|
console.log(value)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Auto-update on Initialization
|
||||||
|
|
||||||
|
Set the `update-on-init` property to control whether to update the `v-model` with the corrected value during initialization.
|
||||||
|
|
||||||
|
- When `update-on-init="true"` (default), the initial value will be corrected to comply with `min`, `max`, `step`, `precision` and other rules, and the `v-model` will be updated synchronously
|
||||||
|
- When `update-on-init="false"`, the initial value will not be corrected (v-model unchanged), but display formatting (such as precision) will still be applied
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Auto-update initial value (default) -->
|
||||||
|
<wd-input-number v-model="value1" @change="handleChange" :update-on-init="true" :min="3" :max="15" :step="2" step-strictly />
|
||||||
|
|
||||||
|
<!-- Don't update initial value, keep original value -->
|
||||||
|
<wd-input-number v-model="value2" @change="handleChange" :update-on-init="false" :min="3" :max="15" :step="2" step-strictly />
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const value1 = ref<number>(1) // Will be auto-corrected to 4 (minimum multiple of 2 that is ≥3)
|
||||||
|
const value2 = ref<number>(1) // Remains 1, will not be corrected but will be formatted for display
|
||||||
|
function handleChange({ value }) {
|
||||||
|
console.log(value)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Asynchronous Change
|
## Asynchronous Change
|
||||||
|
|
||||||
Through `before-change`, you can validate and intercept before the input value changes.
|
Through `before-change`, you can validate and intercept before the input value changes.
|
||||||
@ -152,6 +207,9 @@ Set the `long-press` property to allow long press for increment/decrement.
|
|||||||
| adjustPosition | Native property, whether to automatically push up the page when keyboard pops up | boolean | - | true | 1.3.11 |
|
| adjustPosition | Native property, whether to automatically push up the page when keyboard pops up | boolean | - | true | 1.3.11 |
|
||||||
| before-change | Triggered before input box value changes, returning false will prevent input box value from changing, supports returning `Promise` | `(value: number \| string) => boolean \| Promise<boolean>` | - | - | 1.6.0 |
|
| before-change | Triggered before input box value changes, returning false will prevent input box value from changing, supports returning `Promise` | `(value: number \| string) => boolean \| Promise<boolean>` | - | - | 1.6.0 |
|
||||||
| long-press | Whether to allow long press for increment/decrement | boolean | - | false | 1.8.0 |
|
| long-press | Whether to allow long press for increment/decrement | boolean | - | false | 1.8.0 |
|
||||||
|
| immediate-change | Whether to respond to input changes immediately, false will only update on blur and button clicks | boolean | - | true | $LOWEST_VERSION$ |
|
||||||
|
| update-on-init | Whether to update v-model with corrected value during initialization | boolean | - | true | $LOWEST_VERSION$ |
|
||||||
|
| input-type | Input field type | string | number / digit | digit | $LOWEST_VERSION$ |
|
||||||
|
|
||||||
## Events
|
## Events
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,12 @@
|
|||||||
<demo-block :title="$t('jin-yong-shu-ru-kuang')">
|
<demo-block :title="$t('jin-yong-shu-ru-kuang')">
|
||||||
<wd-input-number v-model="value10" @change="handleChange4" disable-input />
|
<wd-input-number v-model="value10" @change="handleChange4" disable-input />
|
||||||
</demo-block>
|
</demo-block>
|
||||||
|
<demo-block title="禁用减号按钮">
|
||||||
|
<wd-input-number v-model="value13" @change="handleChange13" disable-minus />
|
||||||
|
</demo-block>
|
||||||
|
<demo-block title="禁用加号按钮">
|
||||||
|
<wd-input-number v-model="value14" @change="handleChange14" disable-plus />
|
||||||
|
</demo-block>
|
||||||
<demo-block :title="$t('wu-shu-ru-kuang')">
|
<demo-block :title="$t('wu-shu-ru-kuang')">
|
||||||
<view class="flex">
|
<view class="flex">
|
||||||
<view>{{ $t('shu-liang-value5') }}{{ value5 }}</view>
|
<view>{{ $t('shu-liang-value5') }}{{ value5 }}</view>
|
||||||
@ -27,12 +33,55 @@
|
|||||||
<demo-block :title="$t('shu-ru-yan-ge-wei-bu-shu-de-bei-shu')">
|
<demo-block :title="$t('shu-ru-yan-ge-wei-bu-shu-de-bei-shu')">
|
||||||
<wd-input-number v-model="value7" @change="handleChange7" step-strictly :step="2" />
|
<wd-input-number v-model="value7" @change="handleChange7" step-strictly :step="2" />
|
||||||
</demo-block>
|
</demo-block>
|
||||||
|
<demo-block title="严格步进+边界限制">
|
||||||
|
<view class="strict-bounds-demo">
|
||||||
|
<view class="demo-description">值:{{ value19 }}(步进值2,最小值3,最大值15,严格步进模式)</view>
|
||||||
|
<wd-input-number v-model="value19" @change="handleChange19" step-strictly :step="2" :min="3" :max="15" />
|
||||||
|
<view class="demo-note">
|
||||||
|
尝试输入各种值:
|
||||||
|
<br />
|
||||||
|
• 输入1 → 自动调整为4(≥3的最小2的倍数)
|
||||||
|
<br />
|
||||||
|
• 输入5 → 自动调整为4(最接近的2的倍数)
|
||||||
|
<br />
|
||||||
|
• 输入17 → 自动调整为14(≤15的最大2的倍数)
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</demo-block>
|
||||||
<demo-block :title="$t('xiu-gai-shu-ru-kuang-kuan-du')">
|
<demo-block :title="$t('xiu-gai-shu-ru-kuang-kuan-du')">
|
||||||
<wd-input-number v-model="value8" input-width="70px" @change="handleChange8" />
|
<wd-input-number v-model="value8" input-width="70px" @change="handleChange8" />
|
||||||
</demo-block>
|
</demo-block>
|
||||||
<demo-block :title="$t('yun-xu-kong-zhi-bing-she-zhi-placeholder')">
|
<demo-block :title="$t('yun-xu-kong-zhi-bing-she-zhi-placeholder')">
|
||||||
<wd-input-number v-model="value9" allow-null :placeholder="$t('bu-xian')" input-width="70px" @change="handleChange9" />
|
<wd-input-number v-model="value9" allow-null :placeholder="$t('bu-xian')" input-width="70px" @change="handleChange9" />
|
||||||
</demo-block>
|
</demo-block>
|
||||||
|
<demo-block title="非允许空值但可临时删除">
|
||||||
|
<view class="temp-empty-demo">
|
||||||
|
<view class="demo-description">值:{{ value18 }}(可以删除为空,但失焦时会自动修正为最小值)</view>
|
||||||
|
<wd-input-number v-model="value18" @change="handleChange18" :allow-null="false" :min="1" />
|
||||||
|
<view class="demo-note">尝试删除输入框中的所有内容,然后点击其他地方失焦,会自动修正为最小值1</view>
|
||||||
|
</view>
|
||||||
|
</demo-block>
|
||||||
|
<demo-block title="键盘弹起不上推页面">
|
||||||
|
<wd-input-number v-model="value15" @change="handleChange15" :adjust-position="false" />
|
||||||
|
</demo-block>
|
||||||
|
<demo-block title="非立即更新模式">
|
||||||
|
<view class="immediate-demo">
|
||||||
|
<view class="demo-title">立即更新模式(默认)- 值:{{ value16 }}</view>
|
||||||
|
<wd-input-number v-model="value16" @change="handleChange16" :immediate-change="true" />
|
||||||
|
<view class="demo-title">非立即更新模式 - 值:{{ value17 }}</view>
|
||||||
|
<wd-input-number v-model="value17" @change="handleChange17" :immediate-change="false" />
|
||||||
|
<view class="demo-note">在输入框中输入内容时,上方的值会立即更新,下方的值仅在失焦或点击按钮时更新</view>
|
||||||
|
</view>
|
||||||
|
</demo-block>
|
||||||
|
<demo-block title="初始化时自动修正">
|
||||||
|
<view class="format-init-demo">
|
||||||
|
<view class="demo-title">自动修正初始值 - 值:{{ value20 }}</view>
|
||||||
|
<wd-input-number v-model="value20" @change="handleChange20" :update-on-init="true" :min="3" :max="15" :step="2" step-strictly />
|
||||||
|
<view class="demo-title">不修正初始值 - 值:{{ value21 }}</view>
|
||||||
|
<wd-input-number v-model="value21" @change="handleChange21" :update-on-init="false" :min="3" :max="15" :step="2" step-strictly />
|
||||||
|
<view class="demo-note">上方组件会在初始化时自动将值1修正为4(≥3的最小2的倍数),下方组件不会自动修正</view>
|
||||||
|
</view>
|
||||||
|
</demo-block>
|
||||||
<demo-block :title="$t('yi-bu-bian-geng')">
|
<demo-block :title="$t('yi-bu-bian-geng')">
|
||||||
<wd-input-number v-model="value11" :before-change="beforeChange" />
|
<wd-input-number v-model="value11" :before-change="beforeChange" />
|
||||||
</demo-block>
|
</demo-block>
|
||||||
@ -43,7 +92,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useToast } from '@/uni_modules/wot-design-uni'
|
import { useToast } from '@/uni_modules/wot-design-uni'
|
||||||
import type { InputNumberBeforeChange } from '@/uni_modules/wot-design-uni/components/wd-input-number/types'
|
import { type InputNumberBeforeChange } from '@/uni_modules/wot-design-uni/components/wd-input-number/types'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
const { loading, close } = useToast()
|
const { loading, close } = useToast()
|
||||||
@ -61,35 +110,71 @@ const value9 = ref<string>('')
|
|||||||
const value10 = ref<number>(1)
|
const value10 = ref<number>(1)
|
||||||
const value11 = ref<number>(1)
|
const value11 = ref<number>(1)
|
||||||
const value12 = ref<number>(1)
|
const value12 = ref<number>(1)
|
||||||
|
const value13 = ref<number>(1)
|
||||||
|
const value14 = ref<number>(1)
|
||||||
|
const value15 = ref<number>(1)
|
||||||
|
const value16 = ref<number>(1)
|
||||||
|
const value17 = ref<number>(1)
|
||||||
|
const value18 = ref<number>(1)
|
||||||
|
const value19 = ref<number>(4)
|
||||||
|
const value20 = ref<number>(1)
|
||||||
|
const value21 = ref<number>(1)
|
||||||
|
|
||||||
function handleChange1({ value }: any) {
|
function handleChange1({ value }: { value: number | string }) {
|
||||||
console.log(value)
|
console.log(value)
|
||||||
}
|
}
|
||||||
function handleChange2({ value }: any) {
|
function handleChange2({ value }: { value: number | string }) {
|
||||||
console.log(value)
|
console.log(value)
|
||||||
}
|
}
|
||||||
function handleChange3({ value }: any) {
|
function handleChange3({ value }: { value: number | string }) {
|
||||||
console.log(value)
|
console.log(value)
|
||||||
}
|
}
|
||||||
function handleChange4({ value }: any) {
|
function handleChange4({ value }: { value: number | string }) {
|
||||||
console.log(value)
|
console.log(value)
|
||||||
}
|
}
|
||||||
function handleChange5({ value }: any) {
|
function handleChange5({ value }: { value: number | string }) {
|
||||||
console.log(value)
|
console.log(value)
|
||||||
}
|
}
|
||||||
function handleChange6({ value }: any) {
|
function handleChange6({ value }: { value: number | string }) {
|
||||||
console.log(value)
|
console.log(value)
|
||||||
}
|
}
|
||||||
function handleChange7({ value }: any) {
|
function handleChange7({ value }: { value: number | string }) {
|
||||||
console.log(value)
|
console.log(value)
|
||||||
}
|
}
|
||||||
function handleChange8({ value }: any) {
|
function handleChange8({ value }: { value: number | string }) {
|
||||||
console.log(value)
|
console.log(value)
|
||||||
}
|
}
|
||||||
function handleChange9({ value }: any) {
|
function handleChange9({ value }: { value: number | string }) {
|
||||||
console.log(value)
|
console.log(value)
|
||||||
}
|
}
|
||||||
function handleChange12({ value }: any) {
|
function handleChange12({ value }: { value: number | string }) {
|
||||||
|
console.log(value)
|
||||||
|
}
|
||||||
|
function handleChange13({ value }: { value: number | string }) {
|
||||||
|
console.log(value)
|
||||||
|
}
|
||||||
|
function handleChange14({ value }: { value: number | string }) {
|
||||||
|
console.log(value)
|
||||||
|
}
|
||||||
|
function handleChange15({ value }: { value: number | string }) {
|
||||||
|
console.log(value)
|
||||||
|
}
|
||||||
|
function handleChange16({ value }: { value: number | string }) {
|
||||||
|
console.log(value)
|
||||||
|
}
|
||||||
|
function handleChange17({ value }: { value: number | string }) {
|
||||||
|
console.log(value)
|
||||||
|
}
|
||||||
|
function handleChange18({ value }: { value: number | string }) {
|
||||||
|
console.log(value)
|
||||||
|
}
|
||||||
|
function handleChange19({ value }: { value: number | string }) {
|
||||||
|
console.log(value)
|
||||||
|
}
|
||||||
|
function handleChange20({ value }: { value: number | string }) {
|
||||||
|
console.log(value)
|
||||||
|
}
|
||||||
|
function handleChange21({ value }: { value: number | string }) {
|
||||||
console.log(value)
|
console.log(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,4 +194,96 @@ const beforeChange: InputNumberBeforeChange = (value) => {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.immediate-demo {
|
||||||
|
.demo-title {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-note {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
|
padding: 8px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-input-number {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.temp-empty-demo {
|
||||||
|
.demo-description {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-note {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
|
padding: 8px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-input-number {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.strict-bounds-demo {
|
||||||
|
.demo-description {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-note {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
|
padding: 8px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-input-number {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.format-init-demo {
|
||||||
|
.demo-title {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-note {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
|
padding: 8px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wd-input-number {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: weisheng
|
* @Author: weisheng
|
||||||
* @Date: 2024-03-15 20:40:34
|
* @Date: 2024-03-15 20:40:34
|
||||||
* @LastEditTime: 2025-02-19 12:47:54
|
* @LastEditTime: 2025-06-21 18:23:35
|
||||||
* @LastEditors: weisheng
|
* @LastEditors: weisheng
|
||||||
* @Description:
|
* @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 type { ExtractPropTypes, PropType } from 'vue'
|
||||||
import { baseProps, makeBooleanProp, makeNumberProp, makeNumericProp, makeRequiredProp, makeStringProp, numericProp } from '../common/props'
|
import { baseProps, makeBooleanProp, makeNumberProp, makeNumericProp, makeRequiredProp, makeStringProp, numericProp } from '../common/props'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,20 +19,6 @@ export type InputNumberBeforeChange = (value: number | string) => boolean | Prom
|
|||||||
|
|
||||||
export type OperationType = 'add' | 'sub'
|
export type OperationType = 'add' | 'sub'
|
||||||
|
|
||||||
/**
|
|
||||||
* 输入数字组件事件类型枚举
|
|
||||||
* Input: 用户输入事件
|
|
||||||
* Blur: 失焦事件
|
|
||||||
* Watch: 监听值变化事件
|
|
||||||
* Button: 按钮点击事件
|
|
||||||
*/
|
|
||||||
export enum InputNumberEventType {
|
|
||||||
Input = 'input',
|
|
||||||
Blur = 'blur',
|
|
||||||
Watch = 'watch',
|
|
||||||
Button = 'button'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const inputNumberProps = {
|
export const inputNumberProps = {
|
||||||
...baseProps,
|
...baseProps,
|
||||||
/**
|
/**
|
||||||
@ -102,5 +88,23 @@ export const inputNumberProps = {
|
|||||||
/**
|
/**
|
||||||
* 是否开启长按加减手势
|
* 是否开启长按加减手势
|
||||||
*/
|
*/
|
||||||
longPress: makeBooleanProp(false)
|
longPress: makeBooleanProp(false),
|
||||||
|
/**
|
||||||
|
* 是否立即响应输入变化,false 时仅在失焦和按钮点击时更新
|
||||||
|
*/
|
||||||
|
immediateChange: makeBooleanProp(true),
|
||||||
|
/**
|
||||||
|
* 是否在初始化时更新 v-model 为修正后的值
|
||||||
|
* true: 自动修正并更新 v-model
|
||||||
|
* false: 保持原始值不修正,但仍会进行显示格式化
|
||||||
|
*/
|
||||||
|
updateOnInit: makeBooleanProp(true),
|
||||||
|
/**
|
||||||
|
* 输入框类型
|
||||||
|
* number: 数字输入
|
||||||
|
* digit: 整数输入
|
||||||
|
*/
|
||||||
|
inputType: makeStringProp<'number' | 'digit'>('digit')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type InputNumberProps = ExtractPropTypes<typeof inputNumberProps>
|
||||||
|
|||||||
@ -14,7 +14,8 @@
|
|||||||
<input
|
<input
|
||||||
class="wd-input-number__input"
|
class="wd-input-number__input"
|
||||||
:style="`${inputWidth ? 'width: ' + inputWidth : ''}`"
|
:style="`${inputWidth ? 'width: ' + inputWidth : ''}`"
|
||||||
type="digit"
|
:type="inputType"
|
||||||
|
:input-mode="precision ? 'decimal' : 'numeric'"
|
||||||
:disabled="disabled || disableInput"
|
:disabled="disabled || disableInput"
|
||||||
:value="String(inputValue)"
|
:value="String(inputValue)"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
@ -52,263 +53,410 @@ export default {
|
|||||||
import wdIcon from '../wd-icon/wd-icon.vue'
|
import wdIcon from '../wd-icon/wd-icon.vue'
|
||||||
import { computed, nextTick, ref, watch } from 'vue'
|
import { computed, nextTick, ref, watch } from 'vue'
|
||||||
import { isDef, isEqual } from '../common/util'
|
import { isDef, isEqual } from '../common/util'
|
||||||
import { inputNumberProps, InputNumberEventType, type OperationType } from './types'
|
import { inputNumberProps, type OperationType } from './types'
|
||||||
import { callInterceptor } from '../common/interceptor'
|
import { callInterceptor } from '../common/interceptor'
|
||||||
|
|
||||||
const props = defineProps(inputNumberProps)
|
const props = defineProps(inputNumberProps)
|
||||||
const emit = defineEmits(['focus', 'blur', 'change', 'update:modelValue'])
|
const emit = defineEmits<{
|
||||||
const inputValue = ref<string | number>(getInitValue()) // 输入框的值
|
/**
|
||||||
let longPressTimer: ReturnType<typeof setTimeout> | null = null // 长按定时器
|
* 数值变化事件
|
||||||
|
*/
|
||||||
|
(e: 'change', value: { value: number | string }): void
|
||||||
|
/**
|
||||||
|
* 输入框聚焦事件
|
||||||
|
*/
|
||||||
|
(e: 'focus', detail: any): void
|
||||||
|
/**
|
||||||
|
* 输入框失焦事件
|
||||||
|
*/
|
||||||
|
(e: 'blur', value: { value: string | number }): void
|
||||||
|
/**
|
||||||
|
* v-model 更新事件
|
||||||
|
*/
|
||||||
|
(e: 'update:modelValue', value: number | string): void
|
||||||
|
}>()
|
||||||
|
const inputValue = ref<string | number>(getInitValue())
|
||||||
|
let longPressTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断数字是否达到最小值限制
|
* 判断数字是否达到最小值限制
|
||||||
*/
|
*/
|
||||||
const minDisabled = computed(() => {
|
const minDisabled = computed(() => {
|
||||||
const value = formatValue(inputValue.value)
|
const val = toNumber(inputValue.value)
|
||||||
const { disabled, min, step } = props
|
return props.disabled || val <= props.min || addStep(val, -props.step) < props.min
|
||||||
return disabled || Number(value) <= min || changeStep(value, -step) < min
|
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 判断数字是否达到最大值限制
|
* 判断数字是否达到最大值限制
|
||||||
*/
|
*/
|
||||||
const maxDisabled = computed(() => {
|
const maxDisabled = computed(() => {
|
||||||
const value = formatValue(inputValue.value)
|
const val = toNumber(inputValue.value)
|
||||||
const { disabled, max, step } = props
|
return props.disabled || val >= props.max || addStep(val, props.step) > props.max
|
||||||
return disabled || Number(value) >= max || changeStep(value, step) > max
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 监听 modelValue 变化
|
// 监听 modelValue 变化
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
(value) => {
|
(val) => {
|
||||||
updateValue(value, InputNumberEventType.Watch)
|
inputValue.value = formatValue(val)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// 监听 max, min, precision 变化
|
// 监听 max, min, precision 变化时重新格式化当前值
|
||||||
watch([() => props.max, () => props.min, () => props.precision], () => {
|
watch([() => props.max, () => props.min, () => props.precision], () => {
|
||||||
const value = formatValue(inputValue.value)
|
const val = toNumber(inputValue.value)
|
||||||
updateValue(value, InputNumberEventType.Watch)
|
inputValue.value = formatValue(val)
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 对比两个值是否相等
|
* 获取初始值
|
||||||
* @param value1 第一个值
|
|
||||||
* @param value2 第二个值
|
|
||||||
* @returns 是否相等
|
|
||||||
*/
|
*/
|
||||||
function isValueEqual(value1: number | string, value2: number | string) {
|
|
||||||
return isEqual(String(value1), String(value2))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取初始值
|
|
||||||
function getInitValue() {
|
function getInitValue() {
|
||||||
|
if (!props.updateOnInit) {
|
||||||
|
// 不自动修正时,仅做显示格式化,不修正值
|
||||||
|
return formatDisplay(props.modelValue)
|
||||||
|
}
|
||||||
|
|
||||||
const formatted = formatValue(props.modelValue)
|
const formatted = formatValue(props.modelValue)
|
||||||
if (!isValueEqual(formatted, props.modelValue)) {
|
|
||||||
|
// 如果格式化后的值与原始值不同,同步到外部
|
||||||
|
if (!isEqual(String(formatted), String(props.modelValue))) {
|
||||||
emit('update:modelValue', formatted)
|
emit('update:modelValue', formatted)
|
||||||
}
|
}
|
||||||
return formatted
|
return formatted
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将值转换为指定精度
|
|
||||||
function toPrecision(value: number) {
|
|
||||||
const precision = Number(props.precision)
|
|
||||||
return Number(parseFloat(`${Math.round(value * Math.pow(10, precision)) / Math.pow(10, precision)}`).toFixed(precision))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取数字的小数位数
|
* 获取数字的小数位数
|
||||||
* @param value 需要计算精度的数字
|
|
||||||
* @returns 小数位数
|
|
||||||
*/
|
*/
|
||||||
function getPrecision(value?: number) {
|
function getPrecision(val?: number) {
|
||||||
if (!isDef(value)) return 0
|
if (!isDef(val)) return 0
|
||||||
const valueString = value.toString()
|
const str = val.toString()
|
||||||
const dotPosition = valueString.indexOf('.')
|
const dotIndex = str.indexOf('.')
|
||||||
let precision = 0
|
return dotIndex === -1 ? 0 : str.length - dotIndex - 1
|
||||||
if (dotPosition !== -1) {
|
|
||||||
precision = valueString.length - dotPosition - 1
|
|
||||||
}
|
|
||||||
return precision
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 按步进值严格递增或递减
|
* 按指定精度处理数值
|
||||||
* @param value 当前值
|
|
||||||
* @returns 按步进值调整后的值
|
|
||||||
*/
|
*/
|
||||||
function toStrictlyStep(value: number | string) {
|
function toPrecision(val: number) {
|
||||||
const stepPrecision = getPrecision(props.step)
|
const precision = Number(props.precision)
|
||||||
const precisionFactory = Math.pow(10, stepPrecision)
|
return Math.round(val * Math.pow(10, precision)) / Math.pow(10, precision)
|
||||||
return (Math.round(Number(value) / props.step) * precisionFactory * props.step) / precisionFactory
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 内部更新处理函数
|
/**
|
||||||
function doUpdate(value: string | number) {
|
* 将字符串或数字转换为标准数值
|
||||||
inputValue.value = value
|
*/
|
||||||
const formatted = formatValue(value)
|
function toNumber(val: string | number): number {
|
||||||
|
// 空值处理
|
||||||
|
if (props.allowNull && (!isDef(val) || val === '')) {
|
||||||
|
return NaN
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDef(val) || val === '') {
|
||||||
|
return props.min
|
||||||
|
}
|
||||||
|
|
||||||
|
let str = String(val)
|
||||||
|
|
||||||
|
// 处理中间输入状态
|
||||||
|
if (str.endsWith('.')) str = str.slice(0, -1)
|
||||||
|
if (str.startsWith('.')) str = '0' + str
|
||||||
|
if (str.startsWith('-.')) str = '-0' + str.substring(1)
|
||||||
|
if (str === '-' || str === '') return props.min
|
||||||
|
|
||||||
|
let num = Number(str)
|
||||||
|
if (isNaN(num)) num = props.min
|
||||||
|
|
||||||
|
return normalizeValue(num)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标准化数值(应用步进、边界、精度规则)
|
||||||
|
*/
|
||||||
|
function normalizeValue(val: number): number {
|
||||||
|
let result = val
|
||||||
|
|
||||||
|
// 严格步进
|
||||||
|
if (props.stepStrictly) {
|
||||||
|
const stepPrecision = getPrecision(props.step)
|
||||||
|
const factor = Math.pow(10, stepPrecision)
|
||||||
|
result = (Math.round(result / props.step) * factor * props.step) / factor
|
||||||
|
}
|
||||||
|
|
||||||
|
// 边界限制
|
||||||
|
if (props.stepStrictly) {
|
||||||
|
result = applyStrictBounds(result, props.min, props.max)
|
||||||
|
} else {
|
||||||
|
result = Math.min(Math.max(result, props.min), props.max)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 精度处理
|
||||||
|
if (isDef(props.precision)) {
|
||||||
|
result = toPrecision(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 严格步进模式下的边界处理
|
||||||
|
*/
|
||||||
|
function applyStrictBounds(val: number, min: number, max: number): number {
|
||||||
|
if (val >= min && val <= max) return val
|
||||||
|
|
||||||
|
const stepPrecision = getPrecision(props.step)
|
||||||
|
const factor = Math.pow(10, stepPrecision)
|
||||||
|
|
||||||
|
if (val < min) {
|
||||||
|
const minSteps = Math.ceil((min * factor) / (props.step * factor))
|
||||||
|
const candidate = toPrecision((minSteps * props.step * factor) / factor)
|
||||||
|
if (candidate > max) {
|
||||||
|
const maxSteps = Math.floor((max * factor) / (props.step * factor))
|
||||||
|
return toPrecision((maxSteps * props.step * factor) / factor)
|
||||||
|
}
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val > max) {
|
||||||
|
const maxSteps = Math.floor((max * factor) / (props.step * factor))
|
||||||
|
const candidate = toPrecision((maxSteps * props.step * factor) / factor)
|
||||||
|
if (candidate < min) {
|
||||||
|
const minSteps = Math.ceil((min * factor) / (props.step * factor))
|
||||||
|
return toPrecision((minSteps * props.step * factor) / factor)
|
||||||
|
}
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
|
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化值用于显示(包含修正逻辑)
|
||||||
|
*/
|
||||||
|
function formatValue(val: string | number): string | number {
|
||||||
|
if (props.allowNull && (!isDef(val) || val === '')) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const num = toNumber(val)
|
||||||
|
const precision = Number(props.precision)
|
||||||
|
if (!isDef(props.precision)) {
|
||||||
|
return num
|
||||||
|
}
|
||||||
|
return precision === 0 ? Number(num.toFixed(0)) : num.toFixed(precision)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅做显示格式化,不包含值修正逻辑
|
||||||
|
*/
|
||||||
|
function formatDisplay(val: string | number): string | number {
|
||||||
|
if (props.allowNull && (!isDef(val) || val === '')) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDef(val) || val === '') {
|
||||||
|
return props.min
|
||||||
|
}
|
||||||
|
|
||||||
|
let num = Number(val)
|
||||||
|
if (isNaN(num)) {
|
||||||
|
return props.min
|
||||||
|
}
|
||||||
|
|
||||||
|
const precision = Number(props.precision)
|
||||||
|
if (!isDef(props.precision)) {
|
||||||
|
return num
|
||||||
|
}
|
||||||
|
return precision === 0 ? Number(num.toFixed(0)) : num.toFixed(precision)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否为中间输入状态
|
||||||
|
*/
|
||||||
|
function isIntermediate(val: string): boolean {
|
||||||
|
if (!val) return false
|
||||||
|
const str = String(val)
|
||||||
|
return str.endsWith('.') || str.startsWith('.') || str.startsWith('-.') || str === '-' || (Number(props.precision) > 0 && str.indexOf('.') === -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理输入值
|
||||||
|
*/
|
||||||
|
function cleanInput(val: string): string {
|
||||||
|
if (!val) return ''
|
||||||
|
|
||||||
|
// 清理非数字、小数点、负号
|
||||||
|
let cleaned = val.replace(/[^\d.-]/g, '')
|
||||||
|
|
||||||
|
// 处理负号,保证负号只出现在最前面
|
||||||
|
const hasNegative = cleaned.startsWith('-')
|
||||||
|
cleaned = cleaned.replace(/-/g, '')
|
||||||
|
if (hasNegative) cleaned = '-' + cleaned
|
||||||
|
|
||||||
|
// 处理小数点
|
||||||
|
const precision = Number(props.precision)
|
||||||
|
if (precision > 0) {
|
||||||
|
const parts = cleaned.split('.')
|
||||||
|
if (parts.length > 2) {
|
||||||
|
cleaned = parts[0] + '.' + parts.slice(1).join('')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cleaned = cleaned.split('.')[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理以点开头的情况
|
||||||
|
if (cleaned.startsWith('.')) return '0' + cleaned
|
||||||
|
if (cleaned.startsWith('-.')) return '-0' + cleaned.substring(1)
|
||||||
|
|
||||||
|
return cleaned
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新值并触发事件
|
||||||
|
*/
|
||||||
|
function updateValue(val: string | number) {
|
||||||
|
// 空值处理
|
||||||
|
if (props.allowNull && (!isDef(val) || val === '')) {
|
||||||
|
if (isEqual('', String(props.modelValue))) {
|
||||||
|
inputValue.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const doUpdate = () => {
|
||||||
|
inputValue.value = ''
|
||||||
|
emit('update:modelValue', '')
|
||||||
|
emit('change', { value: '' })
|
||||||
|
}
|
||||||
|
|
||||||
|
callInterceptor(props.beforeChange, { args: [''], done: doUpdate })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const num = toNumber(val)
|
||||||
|
const display = formatValue(val)
|
||||||
|
|
||||||
|
if (isEqual(String(num), String(props.modelValue))) {
|
||||||
|
inputValue.value = display
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const doUpdate = () => {
|
||||||
|
inputValue.value = display
|
||||||
|
emit('update:modelValue', num)
|
||||||
|
emit('change', { value: num })
|
||||||
|
}
|
||||||
|
|
||||||
|
callInterceptor(props.beforeChange, { args: [num], done: doUpdate })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按步进值增减
|
||||||
|
*/
|
||||||
|
function addStep(val: string | number, step: number) {
|
||||||
|
const num = Number(val)
|
||||||
|
if (isNaN(num)) return normalizeValue(props.min)
|
||||||
|
|
||||||
|
const precision = Math.max(getPrecision(num), getPrecision(step))
|
||||||
|
const factor = Math.pow(10, precision)
|
||||||
|
const result = (num * factor + step * factor) / factor
|
||||||
|
return normalizeValue(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理按钮点击
|
||||||
|
*/
|
||||||
|
function handleClick(type: OperationType) {
|
||||||
|
const step = type === 'add' ? props.step : -props.step
|
||||||
|
if ((step < 0 && (minDisabled.value || props.disableMinus)) || (step > 0 && (maxDisabled.value || props.disablePlus))) return
|
||||||
|
|
||||||
|
const newVal = addStep(inputValue.value, step)
|
||||||
|
updateValue(newVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理输入事件
|
||||||
|
*/
|
||||||
|
function handleInput(event: any) {
|
||||||
|
const rawVal = event.detail.value || ''
|
||||||
|
|
||||||
|
// 立即更新显示
|
||||||
|
inputValue.value = rawVal
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
inputValue.value = formatted
|
// 空值处理
|
||||||
emit('update:modelValue', inputValue.value)
|
if (rawVal === '') {
|
||||||
emit('change', { value: inputValue.value })
|
inputValue.value = ''
|
||||||
|
if (props.immediateChange && props.allowNull) {
|
||||||
|
updateValue('')
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理输入
|
||||||
|
const cleaned = cleanInput(rawVal)
|
||||||
|
|
||||||
|
// 中间状态处理
|
||||||
|
if (Number(props.precision) > 0 && isIntermediate(cleaned)) {
|
||||||
|
inputValue.value = cleaned
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 正常输入处理
|
||||||
|
inputValue.value = cleaned
|
||||||
|
if (props.immediateChange) {
|
||||||
|
updateValue(cleaned)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清理输入字符串中多余的小数点
|
* 处理失焦事件
|
||||||
* @param value 输入的字符串
|
|
||||||
* @returns 清理后的字符串
|
|
||||||
*/
|
*/
|
||||||
function cleanExtraDecimal(value: string): string {
|
function handleBlur(event: any) {
|
||||||
const precisionAllowed = Number(props.precision) > 0
|
const val = event.detail.value || ''
|
||||||
if (precisionAllowed) {
|
updateValue(val)
|
||||||
const dotIndex = value.indexOf('.')
|
emit('blur', { value: val })
|
||||||
if (dotIndex === -1) {
|
|
||||||
return value
|
|
||||||
} else {
|
|
||||||
const integerPart = value.substring(0, dotIndex + 1)
|
|
||||||
// 去除后续出现的'.'
|
|
||||||
const decimalPart = value.substring(dotIndex + 1).replace(/\./g, '')
|
|
||||||
return integerPart + decimalPart
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 不允许小数:保留整数部分
|
|
||||||
const dotIndex = value.indexOf('.')
|
|
||||||
return dotIndex !== -1 ? value.substring(0, dotIndex) : value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新输入框的值
|
* 处理聚焦事件
|
||||||
* @param value 新的值
|
|
||||||
* @param eventType 触发更新的事件类型
|
|
||||||
*/
|
*/
|
||||||
function updateValue(value: string | number, eventType: InputNumberEventType = InputNumberEventType.Input) {
|
function handleFocus(event: any) {
|
||||||
const fromUser = eventType !== InputNumberEventType.Watch // watch时不认为是用户直接输入
|
emit('focus', event.detail)
|
||||||
const forceFormat = eventType === InputNumberEventType.Blur || eventType === InputNumberEventType.Button
|
|
||||||
// 对于 Input 和 Watch 类型,如果值以'.'结尾,则直接更新,不进行格式化
|
|
||||||
if ((eventType === InputNumberEventType.Input || eventType === InputNumberEventType.Watch) && String(value).endsWith('.') && props.precision) {
|
|
||||||
inputValue.value = value
|
|
||||||
nextTick(() => {
|
|
||||||
inputValue.value = cleanExtraDecimal(String(value))
|
|
||||||
emit('update:modelValue', inputValue.value)
|
|
||||||
emit('change', { value: inputValue.value })
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!forceFormat && isValueEqual(value, inputValue.value)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const update = () => doUpdate(value)
|
|
||||||
|
|
||||||
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 precision = Math.max(getPrecision(val), getPrecision(step))
|
|
||||||
const precisionFactor = Math.pow(10, precision)
|
|
||||||
return toPrecision((val * precisionFactor + step * precisionFactor) / precisionFactor)
|
|
||||||
}
|
|
||||||
|
|
||||||
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, InputNumberEventType.Button)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 增减值
|
|
||||||
function handleClick(type: OperationType) {
|
|
||||||
const diff = type === 'add' ? props.step : -props.step
|
|
||||||
changeValue(diff)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleInput(event: any) {
|
|
||||||
const rawValue = event.detail.value || ''
|
|
||||||
updateValue(rawValue, InputNumberEventType.Input)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleBlur(event: any) {
|
|
||||||
const value = event.detail.value || ''
|
|
||||||
updateValue(value, InputNumberEventType.Blur)
|
|
||||||
emit('blur', { value })
|
|
||||||
}
|
|
||||||
|
|
||||||
// 每隔一段时间,重新调用自身,达到长按加减效果
|
|
||||||
function longPressStep(type: OperationType) {
|
function longPressStep(type: OperationType) {
|
||||||
clearlongPressTimer()
|
clearLongPressTimer()
|
||||||
longPressTimer = setTimeout(() => {
|
longPressTimer = setTimeout(() => {
|
||||||
handleClick(type)
|
handleClick(type)
|
||||||
longPressStep(type)
|
longPressStep(type)
|
||||||
}, 250)
|
}, 250)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按下一段时间后,达到长按状态
|
|
||||||
function handleTouchStart(type: OperationType) {
|
function handleTouchStart(type: OperationType) {
|
||||||
if (!props.longPress) return
|
if (!props.longPress) return
|
||||||
clearlongPressTimer()
|
clearLongPressTimer()
|
||||||
longPressTimer = setTimeout(() => {
|
longPressTimer = setTimeout(() => {
|
||||||
handleClick(type)
|
handleClick(type)
|
||||||
longPressStep(type)
|
longPressStep(type)
|
||||||
}, 600)
|
}, 600)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 触摸结束,清除定时器,停止长按加减
|
|
||||||
function handleTouchEnd() {
|
function handleTouchEnd() {
|
||||||
if (!props.longPress) return
|
if (!props.longPress) return
|
||||||
clearlongPressTimer()
|
clearLongPressTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清除定时器
|
function clearLongPressTimer() {
|
||||||
function clearlongPressTimer() {
|
|
||||||
if (longPressTimer) {
|
if (longPressTimer) {
|
||||||
clearTimeout(longPressTimer)
|
clearTimeout(longPressTimer)
|
||||||
longPressTimer = null
|
longPressTimer = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理聚焦事件
|
|
||||||
function handleFocus(event: any) {
|
|
||||||
emit('focus', event.detail)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化值
|
|
||||||
function formatValue(value: string | number) {
|
|
||||||
if (props.allowNull && (!isDef(value) || value === '')) {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
let formatted = Number(value)
|
|
||||||
|
|
||||||
if (isNaN(formatted)) {
|
|
||||||
formatted = props.min
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.stepStrictly) {
|
|
||||||
formatted = toStrictlyStep(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
formatted = Math.min(Math.max(formatted, props.min), props.max)
|
|
||||||
|
|
||||||
if (isDef(props.precision)) {
|
|
||||||
formatted = toPrecision(formatted)
|
|
||||||
}
|
|
||||||
|
|
||||||
return formatted
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user