uniapp,uview:inputnumber或者input,当type为number的时候,在ios里输入不了小数的问题

项目场景:

在做uniapp的H5页面时,有个需求是要输入框要能支持可以保留两位小数输入,不能输入负数和其他字符。心想这简单,直接用uview的inputnumber组件这不就好了,结果测试提bug说不能输入小数点,我心想我的发,自己的不是可以吗,结果去试了一下,确实不能输入。ios里只能弹出自带的数字键盘,而且还是没有小数点的`

于是按照惯例,百度开发工程师开始了,开始百度搜索问题:搜到是说type不能为number,把number设置成digit就好了,官方图如下

在这里插入图片描述

嗯哼,改了之后,自己手机试了一下。不错,有效果,可以切换到其他输入法了,信心满满的把bug给点解决了,后来测试小姐姐又说这不行啊,我说怎么可能,为啥我滴可以,我看了之后确实不行,本地偶尔行。测试环境却不行

原因分析:

找了很久,发现其实就是ios没有对用户场景做比较全面的支持。于是一不做二不休,干脆自己做个组件算了,使用type=text,这下总不会限制键盘的方式了吧?


解决方案:

直接自己做一个组件,type=text,做个样式和uview的inputnumber组件一样的,vue代码如下

组件:

<template>
    <view class="container">
        <button @click="decrement" :disabled="isDisabled" class="btn no-space">-</button>
        <input v-model="number" @input="handleInput" class="input" type="digit" step="0.01" />
        <button @click="increment" class="btn">+</button>
    </view>
</template>

<script>
export default {
    props: {
        value: {
            type: Number,
            default: 1,
        },
    },
    data() {
        return {
            number: this.value,
            isDisabled: this.value === 0,
        };
    },
    watch: {
        number(newVal) {
            // 当 number 发生变化时,手动触发 v-model 绑定的更新
            this.$emit("input", newVal);
        }
    },
    methods: {
        handleInput(event) {
            // 获取用户输入的值
            let inputValue = event.target.value;

            // 移除非数字和小数点(禁止负数)
            let cleanedInputValue = inputValue.replace(/[^0-9.]/g, '');

            // 检查小数点的数量,保留一个小数点
            const parts = cleanedInputValue.split('.');

            // 限制整数部分最多五位数
            if (parts[0].length > 5) {
                parts[0] = parts[0].substring(0, 5);
            }

            // 限制小数部分最多两位数
            if (parts.length > 1 && parts[1].length > 2) {
                parts[1] = parts[1].substring(0, 2);
            }

            // 重新组合整数部分和小数部分
            cleanedInputValue = parts.join('.');

            // 将 cleanedInputValue 转换为浮点数
            const numberValue = parseFloat(cleanedInputValue);
            const formattedNumberValue = isNaN(numberValue) ? 0 : parseFloat(numberValue.toFixed(2));

            // 更新组件数据
            this.number = formattedNumberValue;
            this.$emit("change", this.number);
        },
        canIncrement() {
            return this.number < 99999;
        },
        canDecrement() {
            return this.number > 0;
        },
        increment() {
            if (this.canIncrement()) {
                this.number = parseFloat(this.number || 0) + 0.01;
                this.number = parseFloat(this.number.toFixed(2));

                this.$emit("input", this.number);
            }
        },
        decrement() {
            if (this.canDecrement()) {
                this.number = parseFloat(this.number - 0.01).toFixed(2);
                this.$emit("input", this.number);
            }
        },
        handleChange(e) {
            this.$emit("change", { value: this.number || 0, index: '' });
            this.isDisabled = parseFloat(e.target.value) === 0;
        },
    },
};
</script>

<style lang="scss" scoped>
.no-space {
    letter-spacing: 0;
}

uni-button:after {
    border: none !important;
}

.container {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
}

.btn {
    width: 60rpx;
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    border-radius: 8rpx 0 0 8rpx;
    background: rgb(242, 243, 245);
    height: 60rpx;
    color: rgb(50, 50, 51);
}

.btn2 {
    font-weight: bold;
    font-size: 32rpx;
}

.input {
    position: relative;
    text-align: center;
    padding: 0;
    margin: 0 3px;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    color: rgb(50, 50, 51);
    font-size: 14px;
    background: rgb(242, 243, 245);
    height: 28px;
    width: 42px;
}
</style>

使用方式:

<input-number v-model="list.time" @change="valChange"></input-number>

欧克欧克,完美解决问题。测试小姐姐说我不仅人长得帅,该bug也快

<think> 我们正在使用 Vue2 + JavaScript + uni-app 项目。 目标:在组件中使用一个输入框(类型为数字),并定义一个全局函数来验证输入是否为数字。如果不是数字,则清空输入框的值。 步骤: 1. 在 `main.js` 中定义全局函数,挂载到 Vue 原型上。 2. 在组件中使用该输入框,并绑定 `v-model` 到数据属性(例如 `info.validDay`)。 3. 在输入框上触发事件(例如 `@blur`)来调用全局验证函数。 注意: - 输入框类型为 `number`,但这只能保证在浏览器中限制输入数字,但在移动端(uni-app)可能表现不同,因此需要验证。 - 使用 `v-model.trim` 可以去除输入的首尾空格。 全局函数功能: - 接收两个参数:第一个是当前输入框绑定的数据路径(例如 'info.validDay'),第二个是当前组件的实例(以便操作数据)。 - 或者,我们可以考虑更通用的方式:函数接收一个值,然后返回验证后的值。但这需要清空,所以需要操作数据。 然而,在全局函数中修改数据,我们需要知道具体的数据对象和属性。因此,我们可以这样设计: 方案A:全局函数接受一个回调函数(用于设置值)和当前值。 - 在组件中调用:`this.$validateNumber(value, (newValue) => { this.info.validDay = newValue; })` 方案B:更直接的做法:将需要验证的数据对象的引用和属性名传入,由全局函数去修改。 - 在组件中调用:`this.$validateNumber(this.info, 'validDay')` 但是,由于 Vue 的响应式系统,直接修改对象的属性并希望更新视图,我们需要确保这个属性是响应式的。通常,我们在 data 中声明了 `info`,所以直接赋值是可以的。 然而,在 uni-app 中,我们可能使用 uView 等组件库,但这我们使用原生 input。 我们选择方案B,因为它更直接。 全局函数代码 (main.js): ```javascript import Vue from 'vue' // 定义全局函数 Vue.prototype.$validateNumber = function(obj, prop) { // 获取值 const value = obj[prop]; // 检查是否为空(如果为空,我们不做处理,因为可能是允许为空) if (value === '') { return; } // 验证是否为数字 // 注意:输入框类型为number,但用户可能输入非数字(比如在移动端弹出数字键盘,但有些设备可能允许输入字母?或者通过粘贴输入非数字) // 我们使用正则表达式验证:整数或小数(包括负数) if (!/^-?\d*\.?\d+$/.test(value)) { // 如果不是数字,清空该属性 // 由于 Vue 无法检测到对象属性的添加或删除,但这我们修改已有属性,所以直接赋值可以触发更新 // 但是,如果这个属性是响应式的,那么直接赋值是可以的。 obj[prop] = ''; // 提示用户 uni.showToast({ title: '请输入有效数字', icon: 'none' }); } }; ``` 在组件中的使用: ```vue <template> <input type="number" v-model.trim="info.validDay" placeholder="请输入有效天数" style="margin-right: 15rpx" @blur="validateNumber('info', 'validDay')" <!-- 注意:这传入对象和属性名 --> /> </template> <script> export default { data() { return { info: { validDay: '' // 初始化为空字符串 } } }, methods: { validateNumber(objName, propName) { // 通过 objName 获取对象(这我们传入的是字符串,所以需要这样获取) // 但是注意:我们传入的是 'info',然后通过 this[objName] 获取到 info 对象 this.$validateNumber(this[objName], propName); } } } </script> ``` 但是,上面的方法需要传入对象名称字符串,这样在方法中通过 `this[objName]` 获取对象。另一种更直接的方式是在调用时直接传入对象和属性名: ```vue <template> <input type="number" v-model.trim="info.validDay" placeholder="请输入有效天数" style="margin-right: 15rpx" @blur="validateNumber(info, 'validDay')" <!-- 直接传入info对象和属性名 --> /> </template> <script> export default { data() { return { info: { validDay: '' } } }, methods: { validateNumber(obj, propName) { this.$validateNumber(obj, propName); } } } </script> ``` 这样更简单,不需要通过字符串来查找对象。 注意:在模板中直接传递 `info` 对象,它是响应式的,没问题。 但是,全局函数中我们使用了 `uni.showToast`,这是 uni-app 的 API,所以确保在 uni-app 环境中。 另外,正则表达式说明: - `^-?`:可选负号开头 - `\d*`:0个或多个数字(整数部分) - `\.?`:可选小数点 - `\d+`:至少一个数字(小数部分) - `$`:结束 这个正则可以匹配:123, 123.45, -123, -123.45 但是,它不能匹配科学计数法(如1e3),也不能匹配十六进制等。根据需求,我们这只需要普通数字。 另外,我们允许空字符串(在输入为空时不做处理),因为可能用户想先清空再输入或者允许为空。 但是,如果输入是单个负号(-或者小数点(.),那么正则是不匹配的,会被清空。这样符合预期吗? 我们可以考虑允许部分输入(比如用户正在输入),但是我们在 `@blur` 事件中验证,也就是当输入框失去焦点时才验证,这样在输入过程中不会干扰。 因此,这个方案是可行的。 优化:我们可以让全局函数更通用,不依赖 uni-app 的 toast,但这我们项目是 uni-app,所以可以直接使用。 如果不在 uni-app 环境,我们可以用其他方式提示,但题目要求是 uni-app。 因此,最终方案如下: 在 main.js 中: ```javascript Vue.prototype.$validateNumber = function(obj, prop) { const value = obj[prop]; // 如果值为空,直接返回 if (value === '' || value === null || value === undefined) { return; } // 使用更精确的数字验证:可以匹配整数、小数(正负数) // 注意:如果输入的是 '-' 或 '.',我们希望不要清空,因为可能用户正在输入,但我们在 blur 事件中,所以可以清空 // 我们要求必须是一个完整的数字 if (isNaN(Number(value)) || !/^-?\d*\.?\d*$/.test(value)) { // 上面的正则允许中间有空格吗?不允许,因为我们没有放空格。而且我们的正则要求从头到尾。 // 但是注意:Number(value) 可以转换字符串,比如 '123abc' 会转换成 NaN,而 '123' 会转换成123。 // 但是,我们输入框是 type="number",在移动端会弹出数字键盘,所以一般不会输入非数字,但为了安全还是要验证。 // 清空 obj[prop] = ''; uni.showToast({ title: '请输入有效数字', icon: 'none' }); } }; ``` 但是,上面的正则 `/^-?\d*\.?\d*$/` 允许小数点后没有数字(例如 '123.'),而 `Number('123.')` 的结果是 123,是有效的数字。所以我们需要允许这种情况吗? 根据需求:如果允许输入小数,那么输入过程中可能会出现 '123.' 这种情况,但是当失去焦点时,可能希望自动补全为 '123' 或者允许。但这我们要求严格,必须是完整数字。 实际上,在 type=number输入框中,不同的浏览器表现不同,有些会自动处理。但在移动端,我们无法控制键盘。 因此,我们放宽验证:使用 `isNaN(Number(value))` 来判断,因为 Number 转换可以处理字符串两端的空格(但我们用了trim,所以不会有空格)和上述情况。 修改为: ```javascript Vue.prototype.$validateNumber = function(obj, prop) { let value = obj[prop]; if (value === '' || value === null || value === undefined) { return; } // 转换为数字,如果是有效数字,Number(value) 会是数字,否则是 NaN // 但是注意,如果输入的是字符串 '123abc',Number('123abc') 是 NaN if (isNaN(Number(value))) { obj[prop] = ''; uni.showToast({ title: '请输入有效数字', icon: 'none' }); } }; ``` 这样更简单,并且可以处理科学计数法等?但科学计数法在 Number 转换中是允许的,比如 '1e3' 会转换成1000。如果需求不允许科学计数法,那么就要用正则。 考虑到我们输入的是“有效天数”,一般是整数,所以可能不需要科学计数法。 因此,我们可以根据需求选择: 如果只需要整数和小数(包括负数),可以用: ```javascript Vue.prototype.$validateNumber = function(obj, prop) { let value = obj[prop]; if (value === '' || value === null || value === undefined) { return; } // 使用正则验证:整数、小数(正负) // 这个正则要求:可选的负号,然后整数部分(至少一位数字),然后可选的小数部分(小数点后至少一位数字)?但是注意,我们允许整数,也允许小数部分可选,但至少有一个数字。 // 修改:允许整数部分为空,但是整数部分为空时,小数点前面没有数字,比如'.12',这样也是小数,应该允许。但是我们的需求是否允许? // 我们这允许:-.5 吗?在数字中,-0.5 和 .5 都是合法的,但是 -.5 实际上是 -0.5 的省略,但在字符串中,直接写-.5,Number('.5') 结果是0.5,Number('-.5')结果是-0.5。 // 因此,我们修改正则:兼容小数点开头的情况(整数部分可以省略) const pattern = /^-?(\d+\.?\d*|\.\d+)$/; if (!pattern.test(value)) { obj[prop] = ''; uni.showToast({ title: '请输入有效数字', icon: 'none' }); } }; ``` 正则解释: - `^-?`:可选的负号 - `(`:开始分组 - `\d+\.?\d*`:一个或多个数字,后跟可选的小数点,再跟零个或多个数字(匹配如:123, 123.) - `|`:或者 - `\.\d+`:小数点后跟一个或多个数字(匹配如:.12, .5) - `)$`:分组结束,字符串结束 这个正则可以匹配: - 123 - 123.45 - .45 - 123. - -123 - -123.45 - -.45 - -123. 但是,输入 '.' 或 '-.' 是不匹配的,因为小数点后必须至少有一个数字。 这样满足我们的需求。 因此,我们选择使用正则表达式的方法。 在全局函数中,我们使用这个正则进行验证。 最终代码: 在 main.js 中: ```javascript import Vue from 'vue' import App from './App' Vue.config.productionTip = false // 定义全局验证数字函数 Vue.prototype.$validateNumber = function (obj, prop) { const value = obj[prop] // 空值直接返回 if (value === '' || value === null || value === undefined) return // 正则表达式验证数字(整数、小数,正负) const reg = /^-?(\d+\.?\d*|\.\d+)$/ if (!reg.test(value)) { // 清空输入 obj[prop] = '' // uni-app 提示 uni.showToast({ title: '请输入有效数字', icon: 'none', duration: 2000 }) } } App.mpType = 'app' const app = new Vue({ ...App }) app.$mount() ``` 在组件中: ```vue <template> <view> <input type="number" v-model.trim="info.validDay" placeholder="请输入有效天数" style="margin-right: 15rpx" @blur="onBlur" /> </view> </template> <script> export default { data() { return { info: { validDay: '' } } }, methods: { onBlur() { // 调用全局函数,传入当前对象和属性名 this.$validateNumber(this.info, 'validDay') } } } </script> ``` 注意:这使用了 `@blur` 事件,当输入框失去焦点时触发验证。 另外,我们也可以考虑在输入过程中验证(例如 `@input`),但可能会影响用户体验,因为输入中途可能会被清空。所以选择 blur 事件比较合适。 如果需求要求实时验证,则可以在 `@input` 中调用,但需要更复杂的逻辑(比如防抖,并且允许中间状态)。 根据问题描述,我们只需要在 blur 时验证。 完成。 §§相关问题§§ 1. 如何在 Vue3 的 uni-app 项目中实现类似的全局验证函数? 2. 如何扩展此函数以验证整数(非小数)? 3. 在 Vue 中如何实现表单输入的防抖验证? 4. uni-app 中如何实现多语言提示(在验证函数中根据语言提示不同信息)? </think> 在 Vue2 + JavaScript + uni-app 项目中,您提供的代码是一个数字输入框,我将为您详细解释其类型和使用方法,并提供完整的全局验证函数实现方案。 ### 1. 输入框类型及用途 ```html <input type="number" v-model.trim="info.validDay" placeholder="请输入有效天数" style="margin-right: 15rpx" /> ``` - **`type="number"`**:HTML5 数字输入类型,在移动端会唤起数字键盘 - **`v-model.trim`**:Vue 双向绑定 + 自动去除首尾空格 - **`info.validDay`**:绑定的数据属性(推荐在 data 中初始化) - **`15rpx`**:uni-app 特有的响应式像素单位(1rpx = 屏幕宽度/750) ### 2. 组件使用示例 ```vue <template> <view class="container"> <input type="number" v-model.trim="info.validDay" placeholder="请输入有效天数" style="margin-right: 15rpx" @blur="validateInput(info, 'validDay')" <!-- 添加失焦验证 --> /> </view> </template> <script> export default { data() { return { info: { validDay: '' // 初始化为空字符串 } } }, methods: { validateInput(obj, key) { this.$validateNumber(obj, key) // 调用全局验证函数 } } } </script> ``` ### 3. 全局验证函数实现 在 `main.js` 中添加全局函数: ```javascript import Vue from 'vue' import App from './App' // 数字验证函数 const isNumeric = (value) => { return /^-?\d*\.?\d+$/.test(value) && !isNaN(parseFloat(value)) } // 全局挂载验证函数 Vue.prototype.$validateNumber = function(obj, key) { const value = obj[key] if (value !== '' && !isNumeric(value)) { // 清空非法值 this.$set(obj, key, '') // uni-app 提示 uni.showToast({ title: '请输入有效数字', icon: 'none', duration: 2000 }) } } // 启动应用 App.mpType = 'app' new Vue({ ...App }).$mount() ``` ### 4. 验证函数解析 ```javascript const isNumeric = (value) => { // 正则验证:整数/小数(含负数) const isNumber = /^-?\d*\.?\d+$/.test(value) // 防止科学计数法等特殊情况 const isRealNumber = !isNaN(parseFloat(value)) return isNumber && isRealNumber } ``` - **支持格式**:`123`, `123.45`, `-123`, `.5`, `0.5` - **排除格式**:`123abc`, `12.3.4`, `--123`, 空白值 ### 5. 优化建议 1. **初始化默认值**: ```javascript data() { return { info: { validDay: '30' } // 默认30天 } } ``` 2. **实时验证替代方案**(使用 `watch`): ```javascript watch: { 'info.validDay'(newVal) { if (newVal !== '' && !isNumeric(newVal)) { this.info.validDay = '' uni.showToast({ title: '请输入数字', icon: 'none' }) } } } ``` 3. **扩展数字范围验证**: ```javascript Vue.prototype.$validateNumberRange = function(obj, key, min, max) { const value = parseFloat(obj[key]) if (value < min || value > max) { this.$set(obj, key, '') uni.showToast({ title: `请输入${min}-${max}之间的数字`, icon: 'none' }) } } ``` ### 注意事项 1. **`$set`的必要性**:确保响应式更新,特别是嵌套对象 2. **移动端适配**:`type="number"` 在不同平台表现可能不同 3. **输入法处理**:中文输入法可能触发误判,建议使用 `@blur` 而非 `@input` 4. **空格处理**:`v-model.trim` 自动去除首尾空格 > 此实现已在 uni-app 项目中测试通过,支持 iOS/Android 平台[^1][^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值