98 lines
3.1 KiB
Vue
98 lines
3.1 KiB
Vue
<template>
|
||
<input ref="InputRef" class="DecimalInput UI-FLEX-1 UI-P-L24 UI-FS28" type="text" inputmode="decimal"
|
||
:value="InnerValue" :placeholder="placeholder" :placeholder-style="placeholderStyle"
|
||
@input="HandleInput" />
|
||
</template>
|
||
|
||
<script>
|
||
import { limitDecimal } from '@/utils/index.js'
|
||
|
||
export default {
|
||
name: 'DecimalInput',
|
||
props: {
|
||
value: { type: [String, Number], default: '' },
|
||
digit: { type: Number, default: 2 },
|
||
allowNegative: { type: Boolean, default: false },
|
||
max: { type: [String, Number], default: null },
|
||
placeholder: { type: String, default: '' },
|
||
placeholderStyle: { type: String, default: '' }
|
||
},
|
||
data() {
|
||
return {
|
||
// 组件内部输入值
|
||
InnerValue: String(this.value === null || this.value === undefined ? '' : this.value)
|
||
}
|
||
},
|
||
watch: {
|
||
value(Val) {
|
||
const Str = String(Val === null || Val === undefined ? '' : Val)
|
||
if (Str !== this.InnerValue) {
|
||
this.InnerValue = Str
|
||
}
|
||
}
|
||
},
|
||
methods: {
|
||
/** 获取 uni-app input 组件内部的原生 input 节点 */
|
||
GetNativeInput(Target) {
|
||
if (Target && Target.tagName === 'INPUT') return Target
|
||
const ShadowInput = Target && Target.shadowRoot && typeof Target.shadowRoot.querySelector === 'function'
|
||
? Target.shadowRoot.querySelector('input')
|
||
: null
|
||
if (ShadowInput) return ShadowInput
|
||
const ChildInput = Target && typeof Target.querySelector === 'function'
|
||
? Target.querySelector('input')
|
||
: null
|
||
if (ChildInput) return ChildInput
|
||
const RootEl = this.$refs.InputRef && this.$refs.InputRef.$el
|
||
const RootInput = RootEl && typeof RootEl.querySelector === 'function'
|
||
? RootEl.querySelector('input')
|
||
: null
|
||
if (RootInput) return RootInput
|
||
// H5 兜底:正在输入时焦点元素就是原生 input
|
||
const ActiveInput = typeof document !== 'undefined' ? document.activeElement : null
|
||
if (ActiveInput && ActiveInput.tagName === 'INPUT') return ActiveInput
|
||
return null
|
||
},
|
||
|
||
/** 输入时实时限制小数位数与最大值 */
|
||
HandleInput(e) {
|
||
const NativeInput = this.GetNativeInput(e && e.target)
|
||
const DetailValue = e && e.detail ? e.detail.value : undefined
|
||
let Value = String(
|
||
DetailValue !== undefined && DetailValue !== null
|
||
? DetailValue
|
||
: (NativeInput ? NativeInput.value : '')
|
||
)
|
||
|
||
// 限制小数位数、过滤非法字符
|
||
Value = limitDecimal(Value, this.digit, this.allowNegative)
|
||
|
||
// 超过最大值时取最大值
|
||
const Max = Number(this.max)
|
||
if (Value !== '' && this.max !== null && !isNaN(Number(Value)) && !isNaN(Max) && Number(Value) > Max) {
|
||
Value = String(Max)
|
||
}
|
||
|
||
this.InnerValue = Value
|
||
this.$emit('input', Value)
|
||
|
||
// uni-app H5 中 input 被编译为 uni-input 组件,其内部 valueSync 仍是
|
||
// 未过滤的原始值,Vue 重新渲染时会把原生 input 的值打回去。
|
||
// 因此等本轮 patch 结束后(nextTick)再强制纠正原生 input 的显示值。
|
||
this.$nextTick(() => {
|
||
const Target = this.GetNativeInput(e && e.target)
|
||
if (Target && Target.value !== Value) {
|
||
Target.value = Value
|
||
}
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.DecimalInput {
|
||
height: 100%;
|
||
}
|
||
</style>
|