使用指令实现价格单位转换

喵先生的进阶之路

要求:输入的是xxx.xx元,后台保存的是xxxxx分。并添加表单验证。

基本思路

在上面的描述中,包含了两个价格,一个是输入的价格,也就是我们能看到的价格;另一个就是实际传给后台的价格。当输入一个价格的时候,指令监听到价格发生变化,然后对输入的价格乘100,将转换成

指令实现

确定绑定的数据

根据要求,第一个必须绑定的数据就是ng-model中的值,也就是最后传给后台的数据。

另外,需要实现表单验证,所以required属性是必须的,然后有时我们还要给用户一些提示信息,提示这是一个必填项,所以还需要name

scope: {
    ngModel: '=',       // 绑定的价格(以`分`为单位)
    name: '@?',         // 输入框name属性,使用‘@’,获取name属性的值
    required: '=?'      // 表单验证,默认为true;如果不想进行验证,将其设置为false
}  

由于namerequired都不是必须设置的属性,所以使用了

主功能实现

首先我们要初始化一个显示在input框的价格:

scope.price = undefined;

但是考虑到编辑的时候,会从后台获取一个已经存在的价格,这个时候我们还要将其显示出来,所以修改一下:

scope.price = scope.ngModel / 100

接下来就是对我们刚刚定义的价格进行监听了,当其发生变化的时候,对其进行单位转换处理。

// 监听价格
scope.$watch('price', function(newValue) {
    if (newValue) {
        scope.ngModel = newValue * 100; // 将‘元’转换成‘分’
    }
});

由于在监听的时候,当删除所有输入后,price的值就是undefined了,也就是newValueundefined,所以这时ngModel中还是上一次的值。所以要做一下清空处理。

scope.$watch('price', function(newValue) {
    if (newValue) {
        scope.ngModel = newValue * 100; // 将‘元’转换成‘分’
    }
    
    // 防止删除所有的输入后,ngModel还有值
    if (typeof(scope.price) === 'undefined') {
        scope.ngModel = 0;
    }
});

这个时候我们的效果就是这样的:

clipboard.png

再测试一下编辑时的效果:

clipboard.png

出问题了,实际传给后台的价格有,但是并没有显示成元,也就是说我们设置的初始值没有生效。

初始化之前打印一下ngModel

clipboard.png

造成这个的原因是因为angular在进行渲染的时候很快,当我们定义price的时候,ngModel的值还么有绑定过来,所以这里就是undefined了。

解决办法就是让他延迟一会再进行初始化。

$timeout(function() {
    scope.price = scope.ngModel / 100; // 实际显示的价格(以’元‘为单位)
}, 100);

再来看下编辑的效果:

clipboard.png

验证的实现

最开始我们在绑定的时候选择了两个属性:namerequired然后我们将他绑定在输入框上:

clipboard.png

在指令中用一下:

clipboard.png

然后看看验证的效果:

clipboard.png


功能补充

从上面的效果我们可以看到,还没有真正的满足我们对价格输入的期待:

1.控制两位小数
2.不能出现输入多个小数点的情况

所以,为了使用户的体验更加好,这里还需要做一点改进,对价格做一下格式化。

显示价格的格式化

首先我们要控制价格显示两位小数,大概要按照下面的思路来处理:

1.获取小数点的位置
2.获取小数点后面的部分

  • 如果没有后面的部分,添加00
  • 如果后面的部分只有一位,添加一个0
  • 如果后面的部分有两位及以上,截取到两位的部分

所以按照上面的思路,我们就能实现普遍的金钱显示的效果。然后再考虑小数点的问题。

因为为了使用户输入的时候能够显示xx.00的样子,所以我们在前台显示的时候,就必须将price的类型换成是字符串,而一旦我们的类型是字符串,就意味着用户可以输入不止一个小数点,我们要做的就是禁止用户输入多个小数点。

接着上面的思路继续:

3.从第一个小数点后面的部分,获取第二个小数点的位置。

  • 如果位置下标为负数,说明第一个小数点后面的部分是一个整数,不存在第二个小数点
  • 如果下标不为负数,说明第一个小数点后面的部分存在第二个小数点,我们只保留到第二个小数点前面的那部分

按照这个思路,我们可以整理出下面的代码:

self.format = function(price) {
    // 获取小数点后的数字,并计算长度
    var firstPoint = price.indexOf("."); // 获取小数点的位置

    if (firstPoint >= 0) {
        var mantissa = price.slice(firstPoint + 1);  // 获取小数点后面的部分
        var secondPoint = mantissa.indexOf("."); // 获取第二个小数的位置,防出现输入两个及以上的小数点的情况

        if (secondPoint < 0) {
            // 如果小数点后超过两位,去掉后面的
            if (mantissa.length >= 2) {
                return price.slice(0, firstPoint + 3);
            } else if (mantissa.length === 1) {
                return price + '0';
            }
            } else {
                // 去除第二个小数点
                return price.slice(0, firstPoint + secondPoint + 1);
            }
         } else {
             return price + '.00';
         }
};

这里小数点的格式化完成了,但是又出现了另一个问题,就是我们什么时候调用的问题。最开始,我是在监听ngModel变化的时候去调用这个函数。大部分的功能都是没问题的,就是控制小数点不行。

上面已经说了,输入的价格price是一个字符串,这时候如果我们连着输入两个小数点。比如12..,当我们输入12的时候,ngModel的值是12,;当我们输入12.的时候,ngModel还是12;当输入两个点12..的时候,还是12ngModel的值没变,所以不会触发格式化的函数,也就控制不了小数点的个数。同样的,当我们小数点后面连续输入多个0也是这个道理。

所以就在监听价格的时候,去触发格式化的函数。

// 监听价格
self.watchPrice = function(newValue) {
    if (newValue) {
        var point = newValue.indexOf("."); // 获取小数点的位置

        // 防止在输入的时候,当出现输入一个整数的时候,会自动补全小数点
        // 所以这里只有在小数点后的位数超过两位的时候
        // 或者出现两个小数点的时候才触发格式化函数
        if (point >= 0) {
            var mantissa = newValue.slice(point + 1);
            var secondPoint = mantissa.indexOf(".");

            if (mantissa.length >= 2 || secondPoint >= 0) {
                scope.price = self.format(newValue);
            }
        } else {
            scope.price = newValue;
        }

        scope.ngModel = parseFloat(scope.price) * 100;  // 将‘元’转换成‘分’
    }
    ...
}

效果图:

clipboard.png

clipboard.png

完整代码:

angular.module('webappApp')
    .directive('yunzhiPrice', function($timeout) {
        return {
            // 独立scope
            scope: {
                ngModel: '=', // 绑定的价格(以`分`为单位)
                name: '@?', // 输入框name属性,使用‘@’,获取name属性的值
                required: '=?' // 表单验证,默认为true;如果不想进行验证,将其设置为false
            },
            templateUrl: '/views/directive/yunzhiPrice.html',
            restrict: 'E',
            link: function postLink(scope) {
                var self = this;

                // 初始化
                self.init = function() {
                    // 这里由于最开始渲染的时候,ngModel还没有值,所以延迟一会再进行赋初值
                    $timeout(function() {
                        scope.price = self.format((scope.ngModel / 100).toString()); // 实际显示的价格(以’元‘为单位)
                    }, 100);
                    scope.$watch('price', self.watchPrice);
                    scope.$watch('ngModel', self.watchModel);
                };

                // 默认进行验证
                if (typeof(scope.required) === 'undefined') {
                    scope.required = true;
                }

                // 监听价格
                self.watchPrice = function(newValue) {
                    if (newValue) {
                        var point = newValue.indexOf("."); // 获取小数点的位置

                        // 防止在输入的时候,当出现输入一个整数的时候,会自动补全小数点
                        // 所以这里只有在小数点后的位数超过两位的时候
                        // 或者出现两个小数点的时候才触发格式化函数
                        if (point >= 0) {
                            var mantissa = newValue.slice(point + 1);
                            var secondPoint = mantissa.indexOf(".");

                            if (mantissa.length >= 2 || secondPoint >= 0) {
                                scope.price = self.format(newValue);
                            }
                        } else {
                            scope.price = newValue;
                        }

                        scope.ngModel = parseFloat(scope.price) * 100;  // 将‘元’转换成‘分’
                    }

                    // 防止删除所有的输入后,ngModel还有值
                    if (typeof(scope.price) === 'undefined') {
                        scope.ngModel = 0;
                    }
                };

                // 监听ngModel
                self.watchModel = function(newValue) {
                    if (newValue) {
                        scope.price = (newValue / 100).toString();
                    }
                };

                // 价格格式化函数
                // 整数:在后面添加’.00‘
                // 一位小数:添加一个0
                // 两位小数: 不做改变
                // 两位以上:只截取两位的部分
                self.format = function(price) {
                    // 获取小数点后的数字,并计算长度
                    var firstPoint = price.indexOf("."); // 获取小数点的位置

                    if (firstPoint >= 0) {
                        var mantissa = price.slice(firstPoint + 1);
                        var secondPoint = mantissa.indexOf("."); // 获取第二个小数的位置,防出现输入两个及以上的小数点的情况

                        if (secondPoint < 0) {
                            // 如果小数点后超过两位,去掉后面的
                            if (mantissa.length >= 2) {
                                return price.slice(0, firstPoint + 3);
                            } else if (mantissa.length === 1) {
                                return price + '0';
                            }
                        } else {
                            // 去除第二个小数点
                            return price.slice(0, firstPoint + secondPoint + 1);
                        }
                    } else {
                        return price + '.00';
                    }
                };

                self.init();
            }
        };
    });

总结

每一个复杂的问题,只要将其拆分成简单的小问题,都会变得简单。

阅读 914

朴世超
个人学习总结与项目实战问题记录
336 声望
20 粉丝
0 条评论
你知道吗?

336 声望
20 粉丝
文章目录
宣传栏