1.第一个vue实例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>01.创建第一个Vue实例</title>
</head>
<body>
<!--创建Vue实例,初始化渲染-->
<!--1.容器(Vue所管理的范围)-->
<!--2.导包(开发版本包/生产版本包) 官网查找-->
<!--(1) 开发环境版本,包含了有帮助的命令行警告 (使用)-->
<!--<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>-->
<!--(2) 生产环境版本,优化了尺寸和速度 -->
<!--<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>-->
<!--3.实例(new对象)-->
<!--4.配置项(el:挂载点,data:数据)-->
<!--5.完成渲染-->

<!--1.容器-->
<div class="box"></div>
<div class="box2"></div>
<div id="app">
    <!--    编写用于渲染的代码逻辑-->
    <h1>{{msg}}</h1>
    {{count}}
</div>
<!--2.引入开发版本核心包,该包包含完整的注释和警告-->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    <!--    一旦引入VueJS核心包,在全局环境中,就有了Vue构造函数,有了构造函数就可以创建实例-->
    //基于引入的核心包,创建实例对象,在对象中写入配置项(el,data)
    const app = new Vue({
        //通过el配置选择器(指定渲染的容器),只当Vue管理的是哪个盒子
        el: "#app",
        //通过data提供数据
        data: {
            msg: "hello!!!",
            count: 666
        }
    })
</script>
</body>
</html>

2.插值表达式

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>02.插值表达式</title>
</head>
<body>
<!--插值表达式:Vue的一种模板语法
作用:利用表达式,进行插值渲染
语法:{{表达式}}
使用的注意:
(1)不能在标签属性中使用{{}}
(2)使用的数据要存在
(3)支持的式表达式,不是语句 if for
-->
<div id="app">
   <p>{{nickname}}</p>
    <p>{{nickname.toUpperCase()}}</p>
    <p>{{nickname + ',hello!!!'}}</p>
    <p>{{age>=18?'成年':'未成年'}}</p>
    <p>{{nickname}}的朋友:{{friend.name}}</p>
    <p>{{friend.desc}}</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            nickname: "cc",
            age:22,
            friend:{
                name:"lucy",
                desc:"热爱学习"
            }
        }
    })
</script>
</body>
</html>

3.Vue响应式特性

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>03.Vue的响应式特性</title>
</head>
<body>
<!--数据的响应式处理--》响应式:数据变化,视图自动更新-->
<!--使用Vue开发--》专注于业务核心逻辑即可-->
<div id="app">
    {{msg}}
    {{count}}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            // 响应式数据
            msg: "你好,cc",
            count: 100
        }
    })
    //data中的数据,是会被添加到实例上
    //1.访问数据  实例.属性名

    //2.修改数据  实例.属性名=新值
</script>
</body>
</html>

4.Vue指令

4.1 vue-html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>04.Vue指令</title>
</head>
<body>
<!--指令:带有v-前缀的特殊标签属性,不同属性对应不同的功能-->
<!--vue-html:
作用:设置元素的innerHTML,可以解析标签中的数据;
语法:v-html="表达式"-->
<div id="app">
    <div v-html="msg"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    const app=new Vue({
        el:"#app",
        data:{
            //注:数据值要用''包裹
            msg:'<a href="https://www.baidu.com/">百度</a>'
            // msg:'<h3>百度</h3>'
        }
    })
</script>
</body>
</html>

4.2 v-show和v-if

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>04.vue指令(2)</title>
</head>
<body>
<!--vue指令:v-show和v-if-->
<!--(1)v-show
作用:控制元素显示隐藏
语法:v-show="表达式",表达式值true显示,false隐藏
底层原理:切换css的display:none来控制显示隐藏
适用场景:频繁切换需要隐藏的场景-->

<!--(2)v-if
作用:控制元素显示隐藏(条件渲染)
语法:v-if="表达式",表达式值true显示,false隐藏
底层原理:根据判断条件 控制元素的创建和移除(条件渲染)
适用场景:要么显示,要么隐藏,不频繁切换的场景-->
<div id="app">
    <div v-show="flag" class="box">v-show控制</div>
    <div v-if="flag" class="box">v-if控制</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app=new Vue({
    el:"#app",
    data:{
        flag:true
    }
})
</script>
<style>
    .box{
        text-align: center;
        border-radius: 5px;
        box-shadow: 2px 2px 2px #ccc;
    }
</style>
</body>
</html>

4.3 v-else 和 v-else-if

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>04.Vue指令(3)</title>
</head>
<body>
<!--vue指令:v-else 和 v-else-if-->
<!--作用:辅助v-if进行判断渲染
语法:v-else  v-else-if="表达式"
注意:需要紧挨着v-if一起使用-->

<div id="app">
<p v-if="gender===1">性别:男</p>
<p v-else>性别:女</p>
    <hr>
    <p v-if="score>=90">成绩评定A:奖励电脑一台</p>
    <p v-else-if="score>=80">成绩评定B:奖励周末郊游</p>
    <p v-else-if="score>=70">成绩评定C:奖励零食礼包</p>
    <p v-else>成绩评定D:惩罚一周不能玩手机</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    const app=new Vue({
        el:"#app",
        data:{
           gender:0,
            score:80
        }
    })
</script>
</body>
</html>

4.4 v-on

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>04.Vue指令(4)</title>
</head>
<body>
<!--vue指令:v-on
作用:注册事件,注册事件=添加监听+提供处理逻辑
语法:(1)v-on:事件名=“内联语句(可执行语句)"
     (2)v-on:事件名=”methods中的函数名“
 简写: @事件名
-->
<div id="app">
    <!--    (1)v-on:事件名=”内联语句“-->
    <button v-on:click="count--">-1</button>
    <span>{{count}}</span>
    <button v-on:click="count++">+1</button>
    <button v-on:click="count=count+2">+2</button>
    <!--    v-on简写方式:@事件名(将v-on:替换成@)-->
    <button v-on:click="count=count+2">+2</button>
    <hr>
    <!--    (2)v-on:事件名=”methods中的函数名"-->
    <button @click="fn">切换显示隐藏</button>
    <h1 v-show="isShow">hello</h1>
    <hr>
    <!--    v-on调用传参-->
    <button @click="price(-5)">-5</button>
    <span>{{count}}</span>
    <button @click="price(5)">+5</button>
    <hr>
    <div class="box">
        <h1>yueyue自动售货机</h1>
        <button @click="yueyue(5)">可乐5元</button>
        <button @click="yueyue(8)">牛奶10元</button>
        <button @click="yueyue(10)">咖啡10元</button>
    </div>
    <p>余额:{{money}}元</p>
    <hr>
    <div class="box2">
        <button @click="datasum(1,1)">{{param1}}+{{param2}}={{sum}}</button>
    </div>

</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        //提供数据
        data: {
            count: 100,
            isShow: true,
            money:100,
            param1:0,
            param2:0,
            sum:0
        },
        //提供方法
        methods: {
            //让提供的所有methods中的函数,this都指向当前实例
            fn() {
                // console.log("执行了fn",app.isShow)
                console.log(app === this)   //true
                app.isShow = !app.isShow
                this.isShow = !this.isShow
            },
            price(x){
                this.count+=x
                console.log("count=",this.count)
            },
            yueyue(x){
                this.money-=x
                if(this.money<0){
                    alert("余额不足,努力挣钱!!!")
                    return
                }
                console.log("money=",this.money)
            },
            datasum(x,y){
                this.param1=x
                this.param2=y
                this.sum=x+y
                console.log(this.param1,"+",this.param2,"=",this.sum(x,y))
            }
        }

    })
</script>
</body>
</html>

4.5 v-bind

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>04.Vue指令(5)</title>
</head>
<body>
<!--vue指令:v-bind-->
<!--作用:动态的设置html的标签属性->src  url
语法:v-bind:属性名="表达式"
简写:  :事件

-->
<div id="app">
    <img v-bind:src="imgUrl" v-bind:title="msg" alt=""/>
    <!--    简写-->
    <img :src="imgUrl" :title="msg" alt=""/>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            msg: "波仔",
            imgUrl: "../images/01.imgs/10-02.png"
        },
    })
</script>
</body>
</html>

4.6 v-for

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>04.vue指令(6)</title>
</head>
<body>
<!--vue指令:v-for-->
<!--作用:基于数据循环,多次渲染整个元素->数组(常用)、对象、数字
语法:v-for="(item,index) in 数组",item每一项,index下标
注意:v-for中使用的是()
-->
<!--v-for="(item,index) in 数组" :key="item.id"
语法:key属性="唯一标识"
作用:给列表项添加的唯一标识,便于vue进行列表项的正确排序复用
注意点:
(1)key的值只能是字符串或数字类型
(2)key的值必须具有唯一性
(3)推荐使用id作为key(唯一),不推荐使用index作为key(会变化,不对应)-->
<div id="app">
    <h3>yueyue水果店</h3>
    <ul>
<!--        v-for中的index是可以省略的,且当只有一个参数时,括号()也是可以省略的-->
        <li v-for="item in list">
            {{item}}
        </li>
        <hr>
        <li v-for="(item,index) in list ">
            {{item}}---{{index}}
        </li>
    </ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            list: ['西瓜', '苹果', '香蕉']
        },
        methods: {}

    })
</script>
</body>
</html>

4.7 v-model

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>04.Vue指令(7)</title>
</head>
<body>
<!--vue指令:v-model-->
<!--作用:给表单元素使用,双向数据绑定->可以快速获取或设置表单元素内容
(1)数据变化->视图自动更新
(2)视图变化->数据自动更新-->
<!--语法:v-model='变量'-->
<div id="app">
    账户:<input type="text" v-model="username"><br><br>
    密码:<input type="password" v-model="password"><br><br>
    <button @click="login">登录</button>
    <button @click="reset">重置</button>
    <hr>
    <hr>

</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            username: '',
            password: ''
        },
        methods: {
            login(username, password) {
                this.username = username,
                    this.password = password
                console.log(this.username, this.password)
            },
            reset() {
                this.username = '',
                this.password = ''
            }
        }
    })
</script>
</body>
</html>

4.8 案例

4.8.1 指令案例(yueyue书屋)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>01.yueyue书屋</title>
</head>
<body>
<!--需求:
(1)列表的渲染
(2)删除功能
-->
<div id="app">
    <h3>yueyue书屋</h3>
    <ul>
        <li v-for="(item,index) in bookList" :key="item.id">
            <span>{{item.name}}</span>
            <span>{{item.author}}</span>
            <button @click="del(item.id)">删除</button>
        </li>
    </ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            bookList: [
                {id: 1, name: "《红楼梦》", author: "曹雪芹"},
                {id: 2, name: "《西游记》", author: "吴承恩"},
                {id: 3, name: "《水浒传》", author: "施耐庵"},
                {id: 4, name: "《三国演义》", author: "罗贯中"}
            ]
        },
        methods: {
            del(id) {
                // console.log("删除",id)
                //    通过id进行删除数组中的对应项->filter(不会改变原数组)
                //    filter:根据条件,保留满足条件的对应项,得到一个新数组
                // console.log(this.bookList.filter(item => item.id !== id));
                this.bookList=this.bookList.filter(item => item.id !== id)
            }
        }
    })
</script>
</body>
</html>

4.8.2 指令案例(yueyue记事本)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>02.yueyue记事本</title>
</head>
<body>
<!--需求:
(1)列表渲染
(2)删除功能
(3)添加功能
(4)底部统计和清空
-->
<!--主体区域-->
<section id="app">
<!--    输入框-->
    <header class="header">
        <h1>yueyue记事本</h1>
        <input v-model="todoName" placeholder="请输入任务" class="new-todo"/>
        <button @click="add()" class="add">添加任务</button>
    </header>

    <!--列表区域-->
    <section class="main">
        <ul class="todo-list">
            <li class="todo" v-for="(item,index) in list" :key="item.id">
                <div class="view">
                    <span class="index">{{index + 1}}.</span><label>{{item.name}}</label>
                    <button @click="del(item.id)" class="destroy"></button>
                </div>
            </li>
        </ul>
    </section>

    <!--统计和清空->如果没有任务了,底部隐藏掉(v-show)-->
    <footer class="footer" v-show="list.length>0">
        <!--    统计-->
        <span class="todo-count">合计:<strong>{{list.length}}</strong></span>
        <!--    清空-->
        <button @click="clear" class="clear-completed">
            清空任务
        </button>
    </footer>
</section>
<!--底部-->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
//添加功能
//(1)通过v-model绑定输入框-》实时获取表单元素的内容
//(2)点击按钮,进行新增,往数组最前面加(unshift)
    const app = new Vue({
        // el: ".main",   //删除
        el: "#app", //添加
        data: {
            todoName:"",
            list: [
                {id: 1, name: "跑步一公里"},
                {id: 2, name: "跳绳200次"},
                {id: 3, name: "游泳100米"}
            ]
        },
        methods: {
            //删除
            del(id) {
                console.log(id)
                // filter():保留所有不等于该id的项
                this.list = this.list.filter(item => item.id !== id)
            },
            //添加
            add(){
                if(this.todoName.trim()==''){
                    alert("请输入任务名称")
                    return
                }
                this.list.unshift({
                    id: +new Date(),
                    name: this.todoName
                })
                this.todoName=''
            },
            //清空
            clear(){
                this.list=[]
            }
        }
    })
</script>
</body>
</html>

5.指令修改符

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>01.指令修饰符</title>
</head>
<body>
<!--指令修饰符:通过"."指明一些指令后缀,不同后缀封装了不同的操作->简化代码-->
<!--
(1)按键修饰符
    @keyup.enter:键盘回车监听
(2)v-model修饰符
    v-model.trim:去除首尾空格
    v-model.number:转数字
(3)事件修饰符
    @事件名.stop:阻止冒泡
    @事件名.prevent:阻止默认行为
-->

<!--主体区域-->
<section id="app">
<!--    输入框-->
    <header class="header">
        <h1>yueyue记事本</h1>
<!--        使用指令修饰符-->
        <input @keyup.enter="add" v-model="todoName" placeholder="请输入任务" class="text">
        <button @click="add" class="add">添加任务</button>
    </header>
<!--    列表区域-->
    <section class="main">
        <ul class="todo-list">
            <li class="todo" v-for="(item,index) in list" :key="item.id">
                <div class="view">
                    <span class="index">{{index + 1}}.</span><label>{{item.name}}</label>
                    <button @click="del(item.id)" class="destroy"></button>
                </div>
            </li>
        </ul>
    </section>
    <!--统计和清空->如果没有任务了,底部隐藏掉(v-show)-->
    <footer class="footer" v-show="list.length>0">
        <!--    统计-->
        <span class="todo-count">合计:<strong>{{list.length}}</strong></span>
        <!--    清空-->
        <button @click="clear" class="clear-completed">
            清空任务
        </button>
    </footer>
</section>
<!--底部-->
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    //添加功能
    //(1)通过v-model绑定输入框-》实时获取表单元素的内容
    //(2)点击按钮,进行新增,往数组最前面加(unshift)
    const app = new Vue({
        // el: ".main",   //删除
        el: "#app", //添加
        data: {
            todoName:"",
            list: [
                {id: 1, name: "跑步一公里"},
                {id: 2, name: "跳绳200次"},
                {id: 3, name: "游泳100米"}
            ]
        },
        methods: {
            //删除
            del(id) {
                console.log(id)
                // filter():保留所有不等于该id的项
                this.list = this.list.filter(item => item.id !== id)
            },
            //添加
            add(){
                if(this.todoName.trim()==''){
                    alert("请输入任务名称")
                    return
                }
                this.list.unshift({
                    id: +new Date(),
                    name: this.todoName
                })
                this.todoName=''
            },
            //清空
            clear(){
                this.list=[]
            }
        }
    })
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>02.指令修饰符(1)</title>
</head>
<body>
<div id="app">
    <h3>@keyup.enter->监听键盘回车事件</h3>
    <input @keyup.enter="fn" v-model="username" type="text">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            username: ''
        },
        methods: {
            fn(e) {
                //     if(e.key==='Enter'){
                console.log('键盘回车时触发', this.username)
            }
            // }
        }
    })
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>02.指令修饰符(2)</title>
    <style>
        .father {
            width: 200px;
            height: 200px;
            background-color: pink;
            margin-top: 20px;
        }
        .son {
            width: 100px;
            height: 100px;
            background-color: skyblue;
        }
    </style>
</head>
<body>
<div id="app">
    <h3>v-model修饰符 .trim // .number</h3>
    姓名:<input v-model.trim="username" type="text"><br>
    年纪:<input v-model.number="age" type="text"><br>


    <h3>@事件名.stop     →  阻止冒泡</h3>
    <div @click="fatherFn" class="father">
        <div @click.stop="sonFn" class="son">son</div>
    </div>

    <h3>@事件名.prevent  →  阻止默认行为</h3>
    <a @click.prevent href="http://www.baidu.com">阻止默认行为</a>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            username: '',
            age: '',
        },
        methods: {
            fatherFn () {
                alert('father被点击了')
            },
            sonFn (e) {
                // e.stopPropagation()
                alert('son被点击了')
            }
        }
    })
</script>
</body>
</html>

6.V-bind增强

6.1 v-bind对于样式控制

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>03.v-bind对于样式控制的增强</title>
    <style>
        .box {
            width: 200px;
            height: 200px;
            border: 3px solid #000;
            font-size: 30px;
            margin-top: 10px;
        }
        .pink {
            background-color: pink;
        }
        .big {
            width: 300px;
            height: 300px;
        }
    </style>
</head>
<body>
<!--v-bind对于样式控制的增强-->
<!--为了方便开发者进行样式控制,Vue拓展了v-bind的语法,可以针对class类名和style行内样式进行控制-->
<!--v-bind对于样式控制的增强--操作class -->
<!--语法:class="对象/数组"
(1)对象->键就是类名,值是布尔值,如果值为true,有这个类,否则没有这个类
<div class="box" :class="{类名1:布尔值,类名2:布尔值}"></div>
(2)数组->数组中所有的类,都会添加到盒子上,本质就是一个class列表
<div class="box" :class="[类名1,类名2,类名3]"></div>
-->

<div id="app">
    <div class="box" :class="{ pink: true, big: true }">黑马程序员</div>
    <div class="box" :class="['pink', 'big']">黑马程序员</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data: {

        }
    })
</script>
</body>
</html>

6.2 v-bind对于样式控制增强

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>03.v-bind对于样式控制的增强-操作style</title>
    <style>
        .box {
            width: 200px;
            height: 200px;
            background-color: rgb(187, 150, 156);
        }

         .progress {
             height: 25px;
             width: 400px;
             border-radius: 15px;
             background-color: #272425;
             border: 3px solid #272425;
             box-sizing: border-box;
             margin-bottom: 30px;
         }
        .inner {
            width: 50%;
            height: 20px;
            border-radius: 10px;
            text-align: right;
            position: relative;
            background-color: #409eff;
            background-size: 20px 20px;
            box-sizing: border-box;
            transition: all 1s;
        }
        .inner span {
            position: absolute;
            right: -20px;
            bottom: -25px;
        }
    </style>
</head>
<body>
<!--语法::style="样式对象"
<div class="box" :style="{CSS属性名1:CSS属性值,CSS属性名2:CSS属性值}"></div>
适用场景:某个具体属性的动态设置
-->
<div id="app2">
    <div class="box" :style="{ width: '400px', height: '400px', backgroundColor: 'green' }"></div>
</div>

<div id="app">
    <!-- 外层盒子底色 (黑色) -->
    <div class="progress">
        <!-- 内层盒子 - 进度(蓝色) -->
        <div class="inner" :style="{ width: percent + '%' }">
            <span>{{ percent }}%</span>
        </div>
    </div>
    <button @click="percent = 25">设置25%</button>
    <button @click="percent = 50">设置50%</button>
    <button @click="percent = 75">设置75%</button>
    <button @click="percent = 100">设置100%</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            percent: 30
        }
    })
</script>
</body>
</html>

6.3 案例

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>03.案例—京东秒杀tab导航高亮</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
        ul {
            display: flex;
            border-bottom: 2px solid #e01222;
            padding: 0 10px;
        }
        li {
            width: 100px;
            height: 50px;
            line-height: 50px;
            list-style: none;
            text-align: center;
        }
        li a {
            display: block;
            text-decoration: none;
            font-weight: bold;
            color: #333333;
        }
        li a.active {
            background-color: #e01222;
            color: #fff;
        }

    </style>
</head>
<body>
<!--核心思路:
(1)基于数据动态渲染tab,->v-for
(2)准备下标高亮的是哪一个tab->activeIndex
(3)基于下标,动态控制class类名 v-bind:class
-->
<div id="app">
    <ul>
<!--        所谓高亮,其实就是修改下标-->
        <li v-for="(item, index) in list" :key="item.id" @click="activeIndex = index">
            <a :class="{ active: index === activeIndex }" href="#">{{ item.name }}</a>
        </li>
    </ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            activeIndex: 2, // 记录高亮
            list: [
                { id: 1, name: '京东秒杀' },
                { id: 2, name: '每日特价' },
                { id: 3, name: '品类秒杀' }
            ]
        }
    })
</script>
</body>
</html>

7. v-model应用于其他表单

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>04.v-model应用于其他表单元素</title>
</head>
<body>
<!--常见的表单元素都可以用v-model绑定关联->快速获取或设置表单元素的值
它会根据控件类型自动选取正确的方法来更新元素-->
<div id="app">
  <h1>yueyue学习网</h1>
  姓名:<input type="text" v-model="username">
  <br><br>

  是否单身:<input type="radio"><input type="radio"><br><br>

<!--  所在城市:-->
  所在城市:
  <select>
    <option>北京</option>
    <option>上海</option>
    <option>河南省</option>
  </select>
    <!--
     前置理解:
       1. name:  给单选框加上 name 属性 可以分组 → 同一组互相会互斥
       2. value: 给单选框加上 value 属性,用于提交给后台的数据
     结合 Vue 使用 → v-model
   -->
    性别:
    <input v-model="gender" type="radio" name="gender" value="1"><input v-model="gender" type="radio" name="gender" value="2"><br><br>

    <!--
      前置理解:
        1. option 需要设置 value 值,提交给后台
        2. select 的 value 值,关联了选中的 option 的 value 值
      结合 Vue 使用 → v-model
    -->
    所在城市:
    <select v-model="cityId">
        <option value="101">北京</option>
        <option value="102">上海</option>
        <option value="103">成都</option>
        <option value="104">南京</option>
    </select>
    <br><br>

    自我描述:
    <textarea v-model="desc"></textarea>

    <button>立即注册</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            username: '',
            isSingle: false,
            gender: "2",
            cityId: '102',
            desc: ""
        }
    })
</script>
</div>
</body>
</html>

8.计算属性

8.1 计算属性的默认用法

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>05.计算属性</title>
    <style>
        table {
            border: 1px solid #000;
            text-align: center;
            width: 240px;
        }
        th,td {
            border: 1px solid #000;
        }
        h3 {
            position: relative;
        }
    </style>
</head>
<body>
<!--计算属性:基于现有的数据,计算出来的新属性,依赖的数据变化,自动重新计算
计算属性->可以将一段求值的代码进行封装-->
<!--语法:
(1)声明再computed配置项中,一个计算属性对应一个函数
(2)使用起来和普通属性一样使用{{计算属性名}}-->
<div id="app">
    <h3>小黑的礼物清单</h3>
    <table>
        <tr>
            <th>名字</th>
            <th>数量</th>
        </tr>
        <tr v-for="(item, index) in list" :key="item.id">
            <td>{{ item.name }}</td>
            <td>{{ item.num }}个</td>
        </tr>
    </table>

    <!-- 目标:统计求和,求得礼物总数 -->
    <p>礼物总数:{{ totalCount }} 个</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            // 现有的数据
            list: [
                { id: 1, name: '篮球', num: 1 },
                { id: 2, name: '玩具', num: 2 },
                { id: 3, name: '铅笔', num: 5 },
            ]
        },
        computed: {
            totalCount () {
                // 基于现有的数据,编写求值逻辑
                // 计算属性函数内部,可以直接通过 this 访问到 app 实例
                // console.log(this.list)

                // 需求:对 this.list 数组里面的 num 进行求和 → reduce
                let total = this.list.reduce((sum, item) => sum + item.num, 0)
                return total
            }
        }
    })
</script>
</body>
</html>

8.2.computed和methods

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>06.computed计算属性和methods方法</title>
    <style>
        table {
            border: 1px solid #000;
            text-align: center;
            width: 300px;
        }
        th,td {
            border: 1px solid #000;
        }
        h3 {
            position: relative;
        }
        span {
            position: absolute;
            left: 145px;
            top: -4px;
            width: 16px;
            height: 16px;
            color: white;
            font-size: 12px;
            text-align: center;
            border-radius: 50%;
            background-color: #e63f32;
        }
    </style>
</head>
<body>
<!--
作用方面:
(1)计算属性:封装了一段对于数据的处理,求得一个结果
(2)methods:给实例提供一个方法,调用以处理业务逻辑
语法方面:
(1)计算属性
    1)写在computed配置项中
    2)作为属性,直接使用->this.计算属性{{计算属性}}
(2)methods
    1)写在methods配置项中;
    2)作为方法,需要调用->this.方法名()  {{方法名()}}   @事件名=“方法名”

使用computed计算属性值的优势:缓存特性(提升性能)
计算属性会对计算出来的结果缓存,再次使用直接读取缓存,依赖项变化了,会自动重新计算->并再次缓存
-->
<div id="app">
    <h3>小黑的礼物清单🛒<span>{{ totalCountFn() }}</span></h3>
    <h3>小黑的礼物清单🛒<span>{{ totalCountFn() }}</span></h3>
    <h3>小黑的礼物清单🛒<span>{{ totalCountFn() }}</span></h3>
    <h3>小黑的礼物清单🛒<span>{{ totalCountFn() }}</span></h3>
    <table>
        <tr>
            <th>名字</th>
            <th>数量</th>
        </tr>
        <tr v-for="(item, index) in list" :key="item.id">
            <td>{{ item.name }}</td>
            <td>{{ item.num }}个</td>
        </tr>
    </table>

    <p>礼物总数:{{ totalCountFn() }} 个</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            // 现有的数据
            list: [
                { id: 1, name: '篮球', num: 3 },
                { id: 2, name: '玩具', num: 2 },
                { id: 3, name: '铅笔', num: 5 },
            ]
        },

        methods: {
            totalCountFn () {
                console.log('methods方法执行了')
                let total = this.list.reduce((sum, item) => sum + item.num, 0)
                return total
            }
        },

        computed: {
            // 计算属性:有缓存的,一旦计算出来结果,就会立刻缓存
            // 下一次读取 → 直接读缓存就行 → 性能特别高
            // totalCount () {
            //   console.log('计算属性执行了')
            //   let total = this.list.reduce((sum, item) => sum + item.num, 0)
            //   return total
            // }
        }
    })
</script>
</body>
</html>

8.3 计算属性的完整写法

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>07.计算属性完整写法</title>
    <style>
        input {
            width: 30px;
        }
    </style>
</head>
<body>
<!--计算属性完整写法
计算属性默认的简写:只能读取访问,不能“修改”
如果需要“修改”->需要写计算属性的完整写法

语法(默认写法):
computed:{
  计算属性名(){
     一段代码逻辑(计算逻辑)
     return 结果
  }
}

语法(完整写法):
computed:{
计算属性名:{
   get(){
      一段代码逻辑(计算逻辑)
      return 结果
     },
   set(修改的值){
      一段代码逻辑(修改逻辑)
     }
   }
}
-->
<div id="app">
    姓:<input type="text" v-model="firstName"> +
    名:<input type="text" v-model="lastName"> =
    <span>{{ fullName }}</span><br><br>

    <button @click="changeName">改名卡</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            firstName: '刘',
            lastName: '备',
        },
        methods: {
            changeName () {
                this.fullName = '黄忠'
            }
        },
        computed: {
            // 简写 → 获取,没有配置设置的逻辑
            // fullName () {
            //   return this.firstName + this.lastName
            // }

            // 完整写法 → 获取 + 设置
            fullName: {
                // (1) 当fullName计算属性,被获取求值时,执行get(有缓存,优先读缓存)
                //     会将返回值作为,求值的结果
                get () {
                    return this.firstName + this.lastName
                },
                // (2) 当fullName计算属性,被修改赋值时,执行set
                //     修改的值,传递给set方法的形参
                set (value) {
                    // console.log(value.slice(0, 1))
                    // console.log(value.slice(1))
                    this.firstName = value.slice(0, 1)
                    this.lastName = value.slice(1)
                }
            }
        }
    })
</script>
</body>
</html>

9.watch监听器

watch是一种用于监视数据变化并执行相应操作的机制,它通常用于Vue.js框架中,可以监视vue实例中的数据变化,并在数据变化时,执行相应的回调函数或触发其他操作;
在Vue.js中,可以通过在Vue实例的选项中定义一个watch对象来使用watch监视,watch对象的属性是要监视的数据属性,值是一个回调函数,用于在数据变化时执行相应的操作。

9.1 watch监听器(监视器)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>08.watch侦听器(监视器)</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-size: 18px;
        }
        #app {
            padding: 10px 20px;
        }
        .query {
            margin: 10px 0;
        }
        .box {
            display: flex;
        }
        textarea {
            width: 300px;
            height: 160px;
            font-size: 18px;
            border: 1px solid #dedede;
            outline: none;
            resize: none;
            padding: 10px;
        }
        textarea:hover {
            border: 1px solid #1589f5;
        }
        .transbox {
            width: 300px;
            height: 160px;
            background-color: #f0f0f0;
            padding: 10px;
            border: none;
        }
        .tip-box {
            width: 300px;
            height: 25px;
            line-height: 25px;
            display: flex;
        }
        .tip-box span {
            flex: 1;
            text-align: center;
        }
        .query span {
            font-size: 18px;
        }

        .input-wrap {
            position: relative;
        }
        .input-wrap span {
            position: absolute;
            right: 15px;
            bottom: 15px;
            font-size: 12px;
        }
        .input-wrap i {
            font-size: 20px;
            font-style: normal;
        }
    </style>
</head>
<body>
<!--watch侦听器(监视器)
作用:监视数据变化,执行一些业务逻辑或异步操作
语法:
(1)简单写法->简单类型数据,直接监视
data:{
  word:'苹果',
  obj:{
     words:'苹果'
  }
},
watch:{
  //该方法会在数据变化时,触发执行
  数据属性名(newValue,oldValue){
    一些业务逻辑 或 异步操作
  },
  对象.属性名(newValue,oldValue){
    一些业务逻辑  或 异步操作
  }
}
(2)完整写法->添加额外配置项

-->

<div id="app">
    <!-- 条件选择框 -->
    <div class="query">
        <span>翻译成的语言:</span>
        <select>
            <option value="italy">意大利</option>
            <option value="english">英语</option>
            <option value="german">德语</option>
        </select>
    </div>

    <!-- 翻译框 -->
    <div class="box">
        <div class="input-wrap">
            <textarea v-model="obj.words"></textarea>
            <span><i>⌨️</i>文档翻译</span>
        </div>
        <div class="output-wrap">
            <div class="transbox">mela</div>
        </div>
    </div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
    // 接口地址:https://applet-base-api-t.itheima.net/api/translate
    // 请求方式:get
    // 请求参数:
    // (1)words:需要被翻译的文本(必传)
    // (2)lang: 需要被翻译成的语言(可选)默认值-意大利
    // -----------------------------------------------

    const app = new Vue({
        el: '#app',
        data: {
            // words: ''
            obj: {
                words: ''
            }
        },
        // 具体讲解:(1) watch语法 (2) 具体业务实现
        watch: {
            // 该方法会在数据变化时调用执行
            // newValue新值, oldValue老值(一般不用)
            // words (newValue) {
            //   console.log('变化了', newValue)
            // }
//用于监视obj中的words数据值的变化,如果变化了就执行console.log(...)方法,注意此处属性名监视属性要使用单引号''包裹,具体是否使用''或反引号``包裹看下面图片内容;
            'obj.words' (newValue) {
                console.log('变化了', newValue)
            }
        }
    })
</script>
</body>
</html>

第一部分 Vue讲解(代码版)-LMLPHP

9.2 watch监听器(简写)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>08.watch简写-业务实现</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-size: 18px;
        }
        #app {
            padding: 10px 20px;
        }
        .query {
            margin: 10px 0;
        }
        .box {
            display: flex;
        }
        textarea {
            width: 300px;
            height: 160px;
            font-size: 18px;
            border: 1px solid #dedede;
            outline: none;
            resize: none;
            padding: 10px;
        }
        textarea:hover {
            border: 1px solid #1589f5;
        }
        .transbox {
            width: 300px;
            height: 160px;
            background-color: #f0f0f0;
            padding: 10px;
            border: none;
        }
        .tip-box {
            width: 300px;
            height: 25px;
            line-height: 25px;
            display: flex;
        }
        .tip-box span {
            flex: 1;
            text-align: center;
        }
        .query span {
            font-size: 18px;
        }

        .input-wrap {
            position: relative;
        }
        .input-wrap span {
            position: absolute;
            right: 15px;
            bottom: 15px;
            font-size: 12px;
        }
        .input-wrap i {
            font-size: 20px;
            font-style: normal;
        }
    </style>
</head>
<body>
<div id="app">
    <!-- 条件选择框 -->
    <div class="query">
        <span>翻译成的语言:</span>
        <select>
            <option value="italy">意大利</option>
            <option value="english">英语</option>
            <option value="german">德语</option>
        </select>
    </div>

    <!-- 翻译框 -->
    <div class="box">
        <div class="input-wrap">
            <textarea v-model="obj.words"></textarea>
            <span><i>⌨️</i>文档翻译</span>
        </div>
        <div class="output-wrap">
            <div class="transbox">{{ result }}</div>
        </div>
    </div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
    // 接口地址:https://applet-base-api-t.itheima.net/api/translate
    // 请求方式:get
    // 请求参数:
    // (1)words:需要被翻译的文本(必传)
    // (2)lang: 需要被翻译成的语言(可选)默认值-意大利
    // -----------------------------------------------

    const app = new Vue({
        el: '#app',
        data: {
            // words: ''
            obj: {
                words: ''
            },
            result: '', // 翻译结果
            // timer: null // 延时器id
        },
        // 具体讲解:(1) watch语法 (2) 具体业务实现
        watch: {
            // 该方法会在数据变化时调用执行
            // newValue新值, oldValue老值(一般不用)
            // words (newValue) {
            //   console.log('变化了', newValue)
            // }

            'obj.words' (newValue) {
                // console.log('变化了', newValue)
                // 防抖: 延迟执行 → 干啥事先等一等,延迟一会,一段时间内没有再次触发,才执行
                clearTimeout(this.timer)
                this.timer = setTimeout(async () => {
                    const res = await axios({
                        url: 'https://applet-base-api-t.itheima.net/api/translate',
                        params: {
                            words: newValue
                        }
                    })
                    this.result = res.data.data
                    console.log(res.data.data)
                }, 300)
            }
        }
    })
</script>
</body>
</html>

9.3 watch监听器(完整写法)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>08.watch侦听器完整写法</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-size: 18px;
        }
        #app {
            padding: 10px 20px;
        }
        .query {
            margin: 10px 0;
        }
        .box {
            display: flex;
        }
        textarea {
            width: 300px;
            height: 160px;
            font-size: 18px;
            border: 1px solid #dedede;
            outline: none;
            resize: none;
            padding: 10px;
        }
        textarea:hover {
            border: 1px solid #1589f5;
        }
        .transbox {
            width: 300px;
            height: 160px;
            background-color: #f0f0f0;
            padding: 10px;
            border: none;
        }
        .tip-box {
            width: 300px;
            height: 25px;
            line-height: 25px;
            display: flex;
        }
        .tip-box span {
            flex: 1;
            text-align: center;
        }
        .query span {
            font-size: 18px;
        }

        .input-wrap {
            position: relative;
        }
        .input-wrap span {
            position: absolute;
            right: 15px;
            bottom: 15px;
            font-size: 12px;
        }
        .input-wrap i {
            font-size: 20px;
            font-style: normal;
        }
    </style>
</head>
<body>
<!--watch侦听器完整写法->添加额外配置项
(1)deep:true对复杂类型深度监视
(2)immediate:true初始化立刻执行一次handler方法
-->
<!--
data:{
 obj:{
   words:'苹果',
   lang:'italy'
 },
},
watch:{  //watch完整写法
 数据属性名:{
   deep:true,//深度监视
   handler(newValue){
     console.log(newValue)
   }
 }
}
-->
<div id="app">
    <!-- 条件选择框 -->
    <div class="query">
        <span>翻译成的语言:</span>
        <select v-model="obj.lang">
            <option value="italy">意大利</option>
            <option value="english">英语</option>
            <option value="german">德语</option>
        </select>
    </div>

    <!-- 翻译框 -->
    <div class="box">
        <div class="input-wrap">
            <textarea v-model="obj.words"></textarea>
            <span><i>⌨️</i>文档翻译</span>
        </div>
        <div class="output-wrap">
            <div class="transbox">{{ result }}</div>
        </div>
    </div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
    // 需求:输入内容,修改语言,都实时翻译

    // 接口地址:https://applet-base-api-t.itheima.net/api/translate
    // 请求方式:get
    // 请求参数:
    // (1)words:需要被翻译的文本(必传)
    // (2)lang: 需要被翻译成的语言(可选)默认值-意大利
    // -----------------------------------------------

    const app = new Vue({
        el: '#app',
        data: {
            obj: {
                words: '小黑',
                lang: 'italy'
            },
            result: '', // 翻译结果
        },
        watch: {
            obj: {
                deep: true, // 深度监视(对象中的数据可以全部进行监视)
                immediate: true, // 立刻执行,一进入页面handler就立刻执行一次
                handler (newValue) {
                    clearTimeout(this.timer)
                    this.timer = setTimeout(async () => {
                        const res = await axios({
                            url: 'https://applet-base-api-t.itheima.net/api/translate',
                            params: newValue
                        })
                        this.result = res.data.data
                        console.log(res.data.data)
                    }, 300)
                }
            }


            // 'obj.words' (newValue) {
            //   clearTimeout(this.timer)
            //   this.timer = setTimeout(async () => {
            //     const res = await axios({
            //       url: 'https://applet-base-api-t.itheima.net/api/translate',
            //       params: {
            //         words: newValue
            //       }
            //     })
            //     this.result = res.data.data
            //     console.log(res.data.data)
            //   }, 300)
            // }
        }
    })
</script>
</body>
</html>

10. Vue的生命周期

10.1 vue的四个生命周期

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>01.Vue生命周期(4个阶段)</title>
</head>
<body>
<!--vue生命周期:一个Vue实例从创建到销毁的整个过程;
生命周期的四个阶段:(1)创建;(2)挂载;(3)更新;(4)销毁
(1)创建阶段:new VUe(),初始化操作,将普通数据转化为响应式数据;
(2)挂载阶段:渲染模板,将数据放到标签模板中;
(3)更新阶段:数据修改,更新视图(用户使用);
(4)销毁阶段:浏览器关闭;-->
<!--1)发初始化渲染请求要在创建阶段的最后,响应式数据完成之后才可以;
    2)操作dom:挂载阶段结束之后,模板数据渲染后;-->


<!--Vue生命周期函数(钩子函数)-->
<!--Vue生命周期过程中,会自动运行一些函数,被称为【生命周期钩子】,让开发者可以在【特定阶段】运行自己的代码。-->
<!--在生命周期4个阶段中会vue提供8个函数(钩子函数),分别位于每个阶段的前和后,每个钩子函数中可以写自己要执行的逻辑代码,这样可以实现开发者在特定阶段执行想要执行的代码-->
<!--钩子函数:就是承载vue生命周期中每个阶段要执行的代码,例如:在创建阶段后的钩子函数中可以写发送初始化请求的代码、在挂载阶段后可以写操作dom的代码-->
<!--钩子函数(8个):
(1)before Create:
(2)created(常用):发送初始化,渲染请求;
(3)before Mount
(4)mounted(常用):操作dom
(5)before Update
(6)updated
(7)before Destroy:释放Vue以外的资源(清除定时器,延时器...(会配合组件使用))
(8)destroyed:销毁Vue实例
-->

<div id="app">
    <h3>{{title}}</h3>
    <div>
        <button @click="count--">-</button>
        <span>{{count}}</span>
        <button @click="count++">+</button>
    </div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            count: 100,
            title: '计数器'
        },
        //    1.创建阶段(准备阶段)
        beforeCreate() {
            console.log("beforeCreate 响应式数据准备好之前")
        },
        created() {
            console.log("created 响应式数据准备好之后",this.count)

            //this.数据名=请求回来的数据
        //    可以开始发送初始化渲染的请求
        },
        //    2.挂载阶段(渲染阶段)
        beforeMount() {
            console.log("beforeMount 模板渲染之前",document.querySelector('h3').innerHTML)
        },
        mounted() {
            console.log("mounted 模板渲染之后",document.querySelector('h3').innerHTML)
        },
        //    3.更新阶段
        beforeUpdate(){
            console.log("beforeUpdate 更新阶段之前(数据修改了,但视图还没更新)",document.querySelector('h3').innerHTML)
        },
        updated(){
            console.log("updated 更新之后(数据修改了,视图也更新了)",document.querySelector('h3').innerHTML)
        },

        //    4.销毁阶段
        beforeDestory() {
            console.log("beforeDestory (卸载vue实例)")
        },
        destoryed() {
            console.log("destoryed 销毁之后")
        }
    })
</script>
</body>
</html>

10.2 vue的生命周期—created

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>02.created应用</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            list-style: none;
        }

        .news {
            display: flex;
            height: 120px;
            width: 600px;
            margin: 0 auto;
            padding: 20px 0;
            cursor: pointer;
        }

        .news .left {
            flex: 1;
            display: flex;
            flex-direction: column;
            justify-content: space-between;
            padding-right: 10px;
        }

        .news .left .title {
            font-size: 20px;
        }

        .news .left .info {
            color: #999999;
        }

        .news .left .info span {
            margin-right: 20px;
        }

        .news .right {
            width: 160px;
            height: 120px;
        }

        .news .right img {
            width: 100%;
            height: 100%;
            object-fit: cover;
        }
    </style>
    <!--    created:响应式数据准备好了,可以开始发送初始化渲染请求-->
</head>
<body>
<div id="app">
    <ul>
        <li v-for="(item,index) in list" :key="item.id" class="news">
            <div class="left">
                <div class="title">{{item.title}}</div>
                <div class="info">
                    <span>{{item.source}}</span>
                    <span>{{item.time}}</span>
                </div>
            </div>
            <div class="right">
                <img src="item.img" alt="">
            </div>
        </li>
    </ul>
</div>
<script src="./tools/vue.js"></script>
<script src="./tools/axios.js"></script>
<script>
    // 接口地址:http://hmajax.itheima.net/api/news
    // 请求方式:get
    const app = new Vue({
        el: '#app',
        data: {
            list: []
        },
        async created(){
            //1.发送请求,获取数据
            const res=await  axios.get('http://hmajax.itheima.net/api/news')
            //2.将数据更新给data中的list
            this.list=res.data.data
            // console.log(res)
        }
    })
</script>
</body>
</html>

10.3 vue的生命周期—mounted

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>03.mounted应用</title>
    <!--    mounted:模板渲染完成,可以开始操作DOM-->
    <!--    在mounted完成之后,就代表着dom模板已经渲染完成了,才可以操作dom-->
    <!-- 初始化样式 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reset.css@2.0.2/reset.min.css">
    <!-- 核心样式 -->
    <style>
        html,
        body {
            height: 100%;
        }

        .search-container {
            position: absolute;
            top: 30%;
            left: 50%;
            transform: translate(-50%, -50%);
            text-align: center;
        }

        .search-container .search-box {
            display: flex;
        }

        .search-container img {
            margin-bottom: 30px;
        }

        .search-container .search-box input {
            width: 512px;
            height: 16px;
            padding: 12px 16px;
            font-size: 16px;
            margin: 0;
            vertical-align: top;
            outline: 0;
            box-shadow: none;
            border-radius: 10px 0 0 10px;
            border: 2px solid #c4c7ce;
            background: #fff;
            color: #222;
            overflow: hidden;
            box-sizing: content-box;
            -webkit-tap-highlight-color: transparent;
        }

        .search-container .search-box button {
            cursor: pointer;
            width: 112px;
            height: 44px;
            line-height: 41px;
            line-height: 42px;
            background-color: #ad2a27;
            border-radius: 0 10px 10px 0;
            font-size: 17px;
            box-shadow: none;
            font-weight: 400;
            border: 0;
            outline: 0;
            letter-spacing: normal;
            color: white;
        }

        body {
            background: no-repeat center /cover;
            background-color: #edf0f5;
        }
    </style>
</head>
<body>
<div class="container" id="app">
    <div class="search-container">
        <img src="https://www.itheima.com/images/logo.png" alt="">
        <div class="search-box">
            <input type="text" v-model="words" id="inp">
            <button>搜索一下</button>
        </div>
    </div>
</div>

<script src="./vue.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            words: ''
        },
        //    核心思路:
        //    1.等输入框渲染出来

        //    2.让输入框获取焦点
        mounted() {
            console.log(document.querySelector('#inp'));
            document.querySelector('#inp').focus()
        }
    })
    </script>
</body>
</html>

10.4 案例-yueyue记账清单

10.4.1 需求介绍

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>04.yueyue记账清单</title>
<!--    yueyue记账清单
   功能需求:
    (1)基本渲染;
    (2)添加功能;
    (3)删除功能;
    (4)饼图渲染;
    -->
    <!-- CSS only -->
    <link
            rel="stylesheet"
            href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
    />
    <style>
        .red {
            color: red!important;
        }
        .search {
            width: 300px;
            margin: 20px 0;
        }
        .my-form {
            display: flex;
            margin: 20px 0;
        }
        .my-form input {
            flex: 1;
            margin-right: 20px;
        }
        .table > :not(:first-child) {
            border-top: none;
        }
        .contain {
            display: flex;
            padding: 10px;
        }
        .list-box {
            flex: 1;
            padding: 0 30px;
        }
        .list-box  a {
            text-decoration: none;
        }
        .echarts-box {
            width: 600px;
            height: 400px;
            padding: 30px;
            margin: 0 auto;
            border: 1px solid #ccc;
        }
        tfoot {
            font-weight: bold;
        }
        @media screen and (max-width: 1000px) {
            .contain {
                flex-wrap: wrap;
            }
            .list-box {
                width: 100%;
            }
            .echarts-box {
                margin-top: 30px;
            }
        }
    </style>
</head>
<body>
<div id="app">
    <div class="contain">
        <!-- 左侧列表 -->
        <div class="list-box">

            <!-- 添加资产 -->
            <form class="my-form">
                <input type="text" class="form-control" placeholder="消费名称" />
                <input type="text" class="form-control" placeholder="消费价格" />
                <button type="button" class="btn btn-primary">添加账单</button>
            </form>

            <table class="table table-hover">
                <thead>
                <tr>
                    <th>编号</th>
                    <th>消费名称</th>
                    <th>消费价格</th>
                    <th>操作</th>
                </tr>
                </thead>
                <tbody>
                <tr>
                    <td>1</td>
                    <td>帽子</td>
                    <td>99.00</td>
                    <td><a href="javascript:;">删除</a></td>
                </tr>
                <tr>
                    <td>2</td>
                    <td>大衣</td>
                    <td class="red">199.00</td>
                    <td><a href="javascript:;">删除</a></td>
                </tr>
                </tbody>
                <tfoot>
                <tr>
                    <td colspan="4">消费总计: 298.00</td>
                </tr>
                </tfoot>
            </table>
        </div>

        <!-- 右侧图表 -->
        <div class="echarts-box" id="main"></div>
    </div>
</div>
<script src="../echarts.min.js"></script>
<script src="../vue.js"></script>
<script src="../axios.js"></script>
<script>
    /**
     * 接口文档地址:
     * https://www.apifox.cn/apidoc/shared-24459455-ebb1-4fdc-8df8-0aff8dc317a8/api-53371058
     *
     * 功能需求:
     * 1. 基本渲染
     * 2. 添加功能
     * 3. 删除功能
     * 4. 饼图渲染
     */
    const app = new Vue({
        el: '#app',
        data: {

        },
    })
</script>
</body>
</html>

10.4.2 删除功能

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>04.yueyue记账清单-删除功能</title>
    <!-- CSS only -->
    <link
            rel="stylesheet"
            href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
    />
    <style>
        .red {
            color: red!important;
        }
        .search {
            width: 300px;
            margin: 20px 0;
        }
        .my-form {
            display: flex;
            margin: 20px 0;
        }
        .my-form input {
            flex: 1;
            margin-right: 20px;
        }
        .table > :not(:first-child) {
            border-top: none;
        }
        .contain {
            display: flex;
            padding: 10px;
        }
        .list-box {
            flex: 1;
            padding: 0 30px;
        }
        .list-box  a {
            text-decoration: none;
        }
        .echarts-box {
            width: 600px;
            height: 400px;
            padding: 30px;
            margin: 0 auto;
            border: 1px solid #ccc;
        }
        tfoot {
            font-weight: bold;
        }
        @media screen and (max-width: 1000px) {
            .contain {
                flex-wrap: wrap;
            }
            .list-box {
                width: 100%;
            }
            .echarts-box {
                margin-top: 30px;
            }
        }
    </style>
</head>
<body>
<div id="app">
    <div class="contain">
        <!-- 左侧列表 -->
        <div class="list-box">

            <!-- 添加资产 -->
            <form class="my-form">
                <input v-model.trim="name" type="text" class="form-control" placeholder="消费名称" />
                <input v-model.number="price" type="text" class="form-control" placeholder="消费价格" />
                <button @click="add" type="button" class="btn btn-primary">添加账单</button>
            </form>

            <table class="table table-hover">
                <thead>
                <tr>
                    <th>编号</th>
                    <th>消费名称</th>
                    <th>消费价格</th>
                    <th>操作</th>
                </tr>
                </thead>
                <tbody>
                <tr v-for="(item, index) in list" :key="item.id">
                    <td>{{ index + 1 }}</td>
                    <td>{{ item.name }}</td>
                    <td :class="{ red: item.price > 500 }">{{ item.price.toFixed(2) }}</td>
                    <td><a @click="del(item.id)" href="javascript:;">删除</a></td>
                </tr>
                </tbody>
                <tfoot>
                <tr>
                    <td colspan="4">消费总计: {{ totalPrice.toFixed(2) }}</td>
                </tr>
                </tfoot>
            </table>
        </div>

        <!-- 右侧图表 -->
        <div class="echarts-box" id="main"></div>
    </div>
</div>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
    /**
     * 接口文档地址:
     * https://www.apifox.cn/apidoc/shared-24459455-ebb1-4fdc-8df8-0aff8dc317a8/api-53371058
     *
     * 功能需求:
     * 1. 基本渲染
     *    (1) 立刻发送请求获取数据 created
     *    (2) 拿到数据,存到data的响应式数据中
     *    (3) 结合数据,进行渲染 v-for
     *    (4) 消费统计 => 计算属性
     * 2. 添加功能
     *    (1) 收集表单数据 v-model
     *    (2) 给添加按钮注册点击事件,发送添加请求
     *    (3) 需要重新渲染
     * 3. 删除功能
     *    (1) 注册点击事件,传参传 id
     *    (2) 根据 id 发送删除请求
     *    (3) 需要重新渲染
     * 4. 饼图渲染
     */
    const app = new Vue({
        el: '#app',
        data: {
            list: [],
            name: '',
            price: ''
        },
        computed: {
            totalPrice () {
                return this.list.reduce((sum, item) => sum + item.price, 0)
            }
        },
        created () {
            // const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
            //   params: {
            //     creator: '小黑'
            //   }
            // })
            // this.list = res.data.data

            this.getList()
        },
        methods: {
            async getList () {
                const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
                    params: {
                        creator: '小黑'
                    }
                })
                this.list = res.data.data
            },
            async add () {
                if (!this.name) {
                    alert('请输入消费名称')
                    return
                }
                if (typeof this.price !== 'number') {
                    alert('请输入正确的消费价格')
                    return
                }

                // 发送添加请求
                const res = await axios.post('https://applet-base-api-t.itheima.net/bill', {
                    creator: '小黑',
                    name: this.name,
                    price: this.price
                })
                // 重新渲染一次
                this.getList()

                this.name = ''
                this.price = ''
            },
            async del (id) {
                // 根据 id 发送删除请求
                const res = await axios.delete(`https://applet-base-api-t.itheima.net/bill/${id}`)
                // 重新渲染
                this.getList()
            }
        }
    })
</script>
</body>
</html>

10.4.3 基本渲染

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>yueyue记账清单-基本渲染</title>
    <!-- CSS only -->
    <link
            rel="stylesheet"
            href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
    />
    <style>
        .red {
            color: red!important;
        }
        .search {
            width: 300px;
            margin: 20px 0;
        }
        .my-form {
            display: flex;
            margin: 20px 0;
        }
        .my-form input {
            flex: 1;
            margin-right: 20px;
        }
        .table > :not(:first-child) {
            border-top: none;
        }
        .contain {
            display: flex;
            padding: 10px;
        }
        .list-box {
            flex: 1;
            padding: 0 30px;
        }
        .list-box  a {
            text-decoration: none;
        }
        .echarts-box {
            width: 600px;
            height: 400px;
            padding: 30px;
            margin: 0 auto;
            border: 1px solid #ccc;
        }
        tfoot {
            font-weight: bold;
        }
        @media screen and (max-width: 1000px) {
            .contain {
                flex-wrap: wrap;
            }
            .list-box {
                width: 100%;
            }
            .echarts-box {
                margin-top: 30px;
            }
        }
    </style>
</head>
<body>
<div id="app">
    <div class="contain">
        <!-- 左侧列表 -->
        <div class="list-box">

            <!-- 添加资产 -->
            <form class="my-form">
                <input type="text" class="form-control" placeholder="消费名称" />
                <input type="text" class="form-control" placeholder="消费价格" />
                <button type="button" class="btn btn-primary">添加账单</button>
            </form>

            <table class="table table-hover">
                <thead>
                <tr>
                    <th>编号</th>
                    <th>消费名称</th>
                    <th>消费价格</th>
                    <th>操作</th>
                </tr>
                </thead>
                <tbody>
                <tr v-for="(item, index) in list" :key="item.id">
                    <td>{{ index + 1 }}</td>
                    <td>{{ item.name }}</td>
                    <td :class="{ red: item.price > 500 }">{{ item.price.toFixed(2) }}</td>
                    <td><a href="javascript:;">删除</a></td>
                </tr>
                </tbody>
                <tfoot>
                <tr>
                    <td colspan="4">消费总计: {{ totalPrice.toFixed(2) }}</td>
                </tr>
                </tfoot>
            </table>
        </div>

        <!-- 右侧图表 -->
        <div class="echarts-box" id="main"></div>
    </div>
</div>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
    /**
     * 接口文档地址:
     * https://www.apifox.cn/apidoc/shared-24459455-ebb1-4fdc-8df8-0aff8dc317a8/api-53371058
     *
     * 功能需求:
     * 1. 基本渲染
     *    (1) 立刻发送请求获取数据 created
     *    (2) 拿到数据,存到data的响应式数据中
     *    (3) 结合数据,进行渲染 v-for
     *    (4) 消费统计 => 计算属性
     * 2. 添加功能
     * 3. 删除功能
     * 4. 饼图渲染
     */
    const app = new Vue({
        el: '#app',
        data: {
            list: []
        },
        computed: {
            totalPrice () {
                return this.list.reduce((sum, item) => sum + item.price, 0)
            }
        },
        async created () {
            const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
                params: {
                    creator: 'yueyue'
                }
            })
            this.list = res.data.data
        }
    })
</script>
</body>
</html>

10.4.4 添加功能

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>04.yueyue记账清单-添加功能</title>
    <!-- CSS only -->
    <link
            rel="stylesheet"
            href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
    />
    <style>
        .red {
            color: red!important;
        }
        .search {
            width: 300px;
            margin: 20px 0;
        }
        .my-form {
            display: flex;
            margin: 20px 0;
        }
        .my-form input {
            flex: 1;
            margin-right: 20px;
        }
        .table > :not(:first-child) {
            border-top: none;
        }
        .contain {
            display: flex;
            padding: 10px;
        }
        .list-box {
            flex: 1;
            padding: 0 30px;
        }
        .list-box  a {
            text-decoration: none;
        }
        .echarts-box {
            width: 600px;
            height: 400px;
            padding: 30px;
            margin: 0 auto;
            border: 1px solid #ccc;
        }
        tfoot {
            font-weight: bold;
        }
        @media screen and (max-width: 1000px) {
            .contain {
                flex-wrap: wrap;
            }
            .list-box {
                width: 100%;
            }
            .echarts-box {
                margin-top: 30px;
            }
        }
    </style>
</head>
<body>
<div id="app">
    <div class="contain">
        <!-- 左侧列表 -->
        <div class="list-box">

            <!-- 添加资产 -->
            <form class="my-form">
                <input v-model.trim="name" type="text" class="form-control" placeholder="消费名称" />
                <input v-model.number="price" type="text" class="form-control" placeholder="消费价格" />
                <button @click="add" type="button" class="btn btn-primary">添加账单</button>
            </form>

            <table class="table table-hover">
                <thead>
                <tr>
                    <th>编号</th>
                    <th>消费名称</th>
                    <th>消费价格</th>
                    <th>操作</th>
                </tr>
                </thead>
                <tbody>
                <tr v-for="(item, index) in list" :key="item.id">
                    <td>{{ index + 1 }}</td>
                    <td>{{ item.name }}</td>
                    <td :class="{ red: item.price > 500 }">{{ item.price.toFixed(2) }}</td>
                    <td><a href="javascript:;">删除</a></td>
                </tr>
                </tbody>
                <tfoot>
                <tr>
                    <td colspan="4">消费总计: {{ totalPrice.toFixed(2) }}</td>
                </tr>
                </tfoot>
            </table>
        </div>

        <!-- 右侧图表 -->
        <div class="echarts-box" id="main"></div>
    </div>
</div>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
    /**
     * 接口文档地址:
     * https://www.apifox.cn/apidoc/shared-24459455-ebb1-4fdc-8df8-0aff8dc317a8/api-53371058
     *
     * 功能需求:
     * 1. 基本渲染
     *    (1) 立刻发送请求获取数据 created
     *    (2) 拿到数据,存到data的响应式数据中
     *    (3) 结合数据,进行渲染 v-for
     *    (4) 消费统计 => 计算属性
     * 2. 添加功能
     *    (1) 收集表单数据 v-model
     *    (2) 给添加按钮注册点击事件,发送添加请求
     *    (3) 需要重新渲染
     * 3. 删除功能
     * 4. 饼图渲染
     */
    const app = new Vue({
        el: '#app',
        data: {
            list: [],
            name: '',
            price: ''
        },
        computed: {
            totalPrice () {
                return this.list.reduce((sum, item) => sum + item.price, 0)
            }
        },
        created () {
            // const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
            //   params: {
            //     creator: '小黑'
            //   }
            // })
            // this.list = res.data.data

            this.getList()
        },
        methods: {
            async getList () {
                const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
                    params: {
                        creator: '小黑'
                    }
                })
                this.list = res.data.data
            },
            async add () {
                if (!this.name) {
                    alert('请输入消费名称')
                    return
                }
                if (typeof this.price !== 'number') {
                    alert('请输入正确的消费价格')
                    return
                }

                // 发送添加请求
                const res = await axios.post('https://applet-base-api-t.itheima.net/bill', {
                    creator: 'yueyue',
                    name: this.name,
                    price: this.price
                })
                // 重新渲染一次
                this.getList()

                this.name = ''
                this.price = ''
            }
        }
    })
</script>
</body>
</html>

10.4.5 静态结构

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>04.yueyue记账清单-静态结构</title>
    <!-- CSS only -->
    <link
            rel="stylesheet"
            href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
    />
    <style>
        .red {
            color: red!important;
        }
        .search {
            width: 300px;
            margin: 20px 0;
        }
        .my-form {
            display: flex;
            margin: 20px 0;
        }
        .my-form input {
            flex: 1;
            margin-right: 20px;
        }
        .table > :not(:first-child) {
            border-top: none;
        }
        .contain {
            display: flex;
            padding: 10px;
        }
        .list-box {
            flex: 1;
            padding: 0 30px;
        }
        .list-box  a {
            text-decoration: none;
        }
        .echarts-box {
            width: 600px;
            height: 400px;
            padding: 30px;
            margin: 0 auto;
            border: 1px solid #ccc;
        }
        tfoot {
            font-weight: bold;
        }
        @media screen and (max-width: 1000px) {
            .contain {
                flex-wrap: wrap;
            }
            .list-box {
                width: 100%;
            }
            .echarts-box {
                margin-top: 30px;
            }
        }
    </style>
</head>
<body>
<div id="app">
    <div class="contain">
        <!-- 左侧列表 -->
        <div class="list-box">

            <!-- 添加资产 -->
            <form class="my-form">
                <input type="text" class="form-control" placeholder="消费名称" />
                <input type="text" class="form-control" placeholder="消费价格" />
                <button type="button" class="btn btn-primary">添加账单</button>
            </form>

            <table class="table table-hover">
                <thead>
                <tr>
                    <th>编号</th>
                    <th>消费名称</th>
                    <th>消费价格</th>
                    <th>操作</th>
                </tr>
                </thead>
                <tbody>
                <tr>
                    <td>1</td>
                    <td>帽子</td>
                    <td>99.00</td>
                    <td><a href="javascript:;">删除</a></td>
                </tr>
                <tr>
                    <td>2</td>
                    <td>大衣</td>
                    <td class="red">199.00</td>
                    <td><a href="javascript:;">删除</a></td>
                </tr>
                </tbody>
                <tfoot>
                <tr>
                    <td colspan="4">消费总计: 298.00</td>
                </tr>
                </tfoot>
            </table>
        </div>

        <!-- 右侧图表 -->
        <div class="echarts-box" id="main"></div>
    </div>
</div>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
    /**
     * 接口文档地址:
     * https://www.apifox.cn/apidoc/shared-24459455-ebb1-4fdc-8df8-0aff8dc317a8/api-53371058
     *
     * 功能需求:
     * 1. 基本渲染
     * 2. 添加功能
     * 3. 删除功能
     * 4. 饼图渲染
     */
    const app = new Vue({
        el: '#app',
        data: {

        },
    })
</script>
</body>
</html>

10.4.6 饼图渲染

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>04.yueyue记账清单-饼图渲染</title>
  <!-- CSS only -->
  <link
          rel="stylesheet"
          href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
  />
  <style>
    .red {
      color: red!important;
    }
    .search {
      width: 300px;
      margin: 20px 0;
    }
    .my-form {
      display: flex;
      margin: 20px 0;
    }
    .my-form input {
      flex: 1;
      margin-right: 20px;
    }
    .table > :not(:first-child) {
      border-top: none;
    }
    .contain {
      display: flex;
      padding: 10px;
    }
    .list-box {
      flex: 1;
      padding: 0 30px;
    }
    .list-box  a {
      text-decoration: none;
    }
    .echarts-box {
      width: 600px;
      height: 400px;
      padding: 30px;
      margin: 0 auto;
      border: 1px solid #ccc;
    }
    tfoot {
      font-weight: bold;
    }
    @media screen and (max-width: 1000px) {
      .contain {
        flex-wrap: wrap;
      }
      .list-box {
        width: 100%;
      }
      .echarts-box {
        margin-top: 30px;
      }
    }
  </style>
</head>
<body>
<div id="app">
  <div class="contain">
    <!-- 左侧列表 -->
    <div class="list-box">

      <!-- 添加资产 -->
      <form class="my-form">
        <input v-model.trim="name" type="text" class="form-control" placeholder="消费名称" />
        <input v-model.number="price" type="text" class="form-control" placeholder="消费价格" />
        <button @click="add" type="button" class="btn btn-primary">添加账单</button>
      </form>

      <table class="table table-hover">
        <thead>
        <tr>
          <th>编号</th>
          <th>消费名称</th>
          <th>消费价格</th>
          <th>操作</th>
        </tr>
        </thead>
        <tbody>
        <tr v-for="(item, index) in list" :key="item.id">
          <td>{{ index + 1 }}</td>
          <td>{{ item.name }}</td>
          <td :class="{ red: item.price > 500 }">{{ item.price.toFixed(2) }}</td>
          <td><a @click="del(item.id)" href="javascript:;">删除</a></td>
        </tr>
        </tbody>
        <tfoot>
        <tr>
          <td colspan="4">消费总计: {{ totalPrice.toFixed(2) }}</td>
        </tr>
        </tfoot>
      </table>
    </div>

    <!-- 右侧图表 -->
    <div class="echarts-box" id="main"></div>
  </div>
</div>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
  /**
   * 接口文档地址:
   * https://www.apifox.cn/apidoc/shared-24459455-ebb1-4fdc-8df8-0aff8dc317a8/api-53371058
   *
   * 功能需求:
   * 1. 基本渲染
   *    (1) 立刻发送请求获取数据 created
   *    (2) 拿到数据,存到data的响应式数据中
   *    (3) 结合数据,进行渲染 v-for
   *    (4) 消费统计 => 计算属性
   * 2. 添加功能
   *    (1) 收集表单数据 v-model
   *    (2) 给添加按钮注册点击事件,发送添加请求
   *    (3) 需要重新渲染
   * 3. 删除功能
   *    (1) 注册点击事件,传参传 id
   *    (2) 根据 id 发送删除请求
   *    (3) 需要重新渲染
   * 4. 饼图渲染
   *    (1) 初始化一个饼图 echarts.init(dom)  mounted钩子实现
   *    (2) 根据数据实时更新饼图 echarts.setOption({ ... })
   */
  const app = new Vue({
    el: '#app',
    data: {
      list: [],
      name: '',
      price: ''
    },
    computed: {
      totalPrice () {
        return this.list.reduce((sum, item) => sum + item.price, 0)
      }
    },
    created () {
      // const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
      //   params: {
      //     creator: '小黑'
      //   }
      // })
      // this.list = res.data.data

      this.getList()
    },
    mounted () {
      this.myChart = echarts.init(document.querySelector('#main'))
      this.myChart.setOption({
        // 大标题
        title: {
          text: '消费账单列表',
          left: 'center'
        },
        // 提示框
        tooltip: {
          trigger: 'item'
        },
        // 图例
        legend: {
          orient: 'vertical',
          left: 'left'
        },
        // 数据项
        series: [
          {
            name: '消费账单',
            type: 'pie',
            radius: '50%', // 饼的半径
            data: [
              // { value: 1048, name: '球鞋' },
              // { value: 735, name: '防晒霜' }
            ],
            emphasis: {
              itemStyle: {
                shadowBlur: 10,
                shadowOffsetX: 0,
                shadowColor: 'rgba(0, 0, 0, 0.5)'
              }
            }
          }
        ]
      })
    },

    methods: {
      async getList () {
        const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
          params: {
            creator: '小黑'
          }
        })
        this.list = res.data.data

        // 更新图表
        this.myChart.setOption({
          // 数据项
          series: [
            {
              // data: [
              //   { value: 1048, name: '球鞋' },
              //   { value: 735, name: '防晒霜' }
              // ]
              data: this.list.map(item => ({ value: item.price, name: item.name}))
            }
          ]
        })
      },
      async add () {
        if (!this.name) {
          alert('请输入消费名称')
          return
        }
        if (typeof this.price !== 'number') {
          alert('请输入正确的消费价格')
          return
        }

        // 发送添加请求
        const res = await axios.post('https://applet-base-api-t.itheima.net/bill', {
          creator: '小黑',
          name: this.name,
          price: this.price
        })
        // 重新渲染一次
        this.getList()

        this.name = ''
        this.price = ''
      },
      async del (id) {
        // 根据 id 发送删除请求
        const res = await axios.delete(`https://applet-base-api-t.itheima.net/bill/${id}`)
        // 重新渲染
        this.getList()
      }
    }
  })
</script>
</body>
</html>
04-09 11:48