VUE(基础-xmall)

VUE

cnpm使用

1安装 node.js
--https://nodejs.org/en/
--终端输入 node -v ,保证安装成功

2 安装淘宝镜像
- npm install -g cnpm --registry=https://registry.npm.taobao.org
- 以后的npm可以使用cnpm代替

3 安装 vue Cli3脚手架
-cnpm install -g @vue/cli
-检查其版本是否正确
 vue --version

 4 快速原型开发
 npm install -g @vue/cli-service-global
 --vue serve 
#vue creat (创建一个项目)
 --vue create xxxx



#### nodejs

1. 去官网https://nodejs.org/en/download/ 下载 安装(傻瓜式安装)

2. 打开终端 cmd  :  执行`node -v`   如果出现版本号,证明安装node成功 ,跟安装python雷同

3. 下载完node之后,会自带包管理器 npm,好比 是python中 pip3包管理器。pip3 install xxx

4. 使用npm  

   1. 

        1.要初始化npm的项目 :  
         
         npm init  --yes 自动生成一个package.json文件
              {
               "name": "vue_lesson",
               "version": "1.0.0",
               "description": "这是我的vue的第一个项目",
               "main": "index.js",
               "scripts": {
                 "test": "echo \"Error: no test specified\" && exit 1"
               },
               "author": "mjj",
               "license": "ISC",
               "dependencies": {
                 "vue": "^2.5.16"
               }
             }
          2.npm install vue --save
             npm install jquery --save
             
          3.下载包  npm  uninstall vue --save
          
          4.下载所有的依赖包 npm install

#### vue的起步

   - 引包:    <script type="text/javascript" src="./node_modules/vue/dist/vue.js"></script>
   - 创建实例化对象

```javascript
new Vue({
el:'#app',//目的地
data:{
    msg:"hello Vue"
}
});
/*
{{}}: 模板语法插值
    {{变量}}
    {{1+1}}
    {{'hello'}}
    {{函数的调用}}
    {{1==1?'真的':'假的'}}
*/
```
View Code
vue 笔记


--快速启动一个单文件组件(快速原型开发)
    1 创建一个文件夹 cd到此文件夹
    2 npm init
    3 创建一个.vue文件
        输入vbase
        <template>
          <div>
            <h1>{{ greetint }} World</h1>
          </div>
        </template>

        <script>
            export default {
                data:function () {
                    return {
                        greetint:"hello"
                    }
                }
            };
        </script>
        <style scoped>
            h1 {
                font-size: 2em;
                color: aqua;
            }
        </style>
    4 安装vue_cli和全局扩展
    5 vue serve(启动vue文件)


--使用vue-cli快速创建一个vue项目
(https://cli.vuejs.org/zh/guide/prototyping.html)
    1 安装vue_cli脚手架  查看版本
        npm install -g @vue/cli 
        vue --version
    2安装一个全局的扩展
        npm install -g @vue/cli-service-global
    3快速创建一个项目
        vue create hello-world

--关于插件
    1下载插件
    Vetur Vue VSCode Snippets
    2使用 (快速生成文件结构)
        在.vue组件中  vbase

-- 使用组件 Element 
main.js
    import Vue from 'vue';
    import App from './App.vue';
    import 'element-ui/lib/theme-chalk/index.css'; // 默认主题
    Vue.prototype.$ELEMENT = { size: 'small', zIndex: 3000 }; //设置组件默认尺寸

    import { Button, Select } from 'element-ui';

    Vue.use(Button)
    Vue.use(Select)


    new Vue({
    render: h => h(App),
    }).$mount('#app')
vue-note

1 vue语法

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>vue的起步和插值</title>
</head>

<body>
    <div id="app">
        <h2>{{ msg }}</h2>
        <h3>{{ 2 }}</h3>
        <h3>{{ "hello" }}</h3>
        <h3>{{ {id:1} }}</h3>
        <h3>{{ 1>2 ? '真的':'假的'}}</h3>
        <h3>{{ txt.split('').reverse().join('') }}</h3>
        <h1>{{ getContent() }}</h1>
        <h1>{{ msg3 }}</h1>
    </div>
    <!-- 1.引包 -->
    <script src="./vue.js"></script>
    <script>
        // console.log(Vue);

        // 2.初始化
        const vm = new Vue({
            el: '#app',
            // 数据属性
            data: {
                msg: 'hello vue',
                txt: 'hello',
                msg2: 'content',
                msg3:'<p>插值语法</p>'
            },
            // 存放的是方法
            methods: {
                getContent() {
                    return this.msg + ' ' + this.msg2;
                }
            }
        });
        console.log(vm.msg);
        
    </script>

</body>

</html>
vue的起步和插值
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>指令之v-text和v-html</title>
</head>
<body>
    <div id='app'>
        <h1>{{ msg }}</h1>
        <h2 v-text='msg'></h2>
        <div v-html='htmlMsg'></div>
    </div>
    <script src="./vue.js"></script>
    <script>
        // {{}}和v-text的作用是一样的 都是插入值 直接渲染 innerText
        // v-html既能插入值 又能插入标签 innerHTML
        new Vue({
            el:'#app',
            data:{
                msg:"插入标签",
                htmlMsg:'<h3>小马哥</h3>'
            }
        })
    </script>
</body>
</html>
02 v-text和v-html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>条件渲染</title>
</head>

<body>
    <div id='app'>
        <div v-if = "isShow">
            显示
        </div>
        <div v-else>
            隐藏
        </div>
        <h3 v-show = 'show'>小马哥</h3>
    </div>
    <script src="./vue.js"></script>
    <script>
        // v-if v-else-if  v-else      v-show
        new Vue({
            el: '#app',
            data: {
               isShow:Math.random() > 0.5,
               show:false
            }
        })
    </script>
</body>

</html>
03 v-if
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>v-bind指令</title>
    <style>
        .active{
            color: red;
        }
    </style>
</head>

<body>
    <div id='app'>
        <a v-bind:href = 'res.url' v-bind:title='res.title'>{{res.name}}</a>
        <img :src="imgSrc" alt="">
        <h3 class='name' :class = "{active:isActive}">v-bind的用法</h3>
        <!-- <h4 :aaa = 'res.name'></h4> -->
        <h4 :style='{color:isColor,fontSize:fontSize+"px"}'>hello bind</h4>
    </div>
    <script src="./vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
                res:{
                    name:'百度',
                    url:'https://www.baidu.com',
                    title:'百度一下'
                },
                imgSrc:'./images/logo.png',
                isActive:true,
                isColor:'green',
                fontSize:30
            }
        })
    </script>
</body>

</html>
04 v-bind
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>v-on事件绑定</title>
    <style>
        .box{
            width: 200px;
            height: 200px;
            background-color: red;
        }
        .active{
            background-color: green;
        }
    </style>
</head>

<body>
    <div id='app'>
        <h3>{{num}}</h3>
        <button v-on:click.once="handleClick">+1</button>
        <div class='box' :class='{active:isActive}'></div>
        <button @click='changeClick'>切换</button>
        <input v-on:keyup.up="submit">
    </div>
    <script src="./vue.js"></script>
    <script>

        /* 
        
        1.扫一眼 HTML 模板便能轻松定位在 JavaScript 代码里对应的方法。

        2.因为你无须在 JavaScript 里手动绑定事件,你的 ViewModel 代码可以是非常纯粹的逻辑,和 DOM 完全解耦,更易于测试。

        3.当一个 ViewModel 被销毁时,所有的事件处理器都会自动被删除。你无须担心如何清理它们。
        
        */
        new Vue({
            el: '#app',
            data: {
                num:0,
                isActive:false
            },
            methods: {
                handleClick(){
                    this.num+=1;
                },
                changeClick(){
                  this.isActive = !this.isActive;
                },
                submit(){
                    alert(1);
                }
            },
        })
    </script>
</body>

</html>
05 v-on
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>v-for列表渲染</title>
    <style>
        .box {
            width: 200px;
            height: 200px;
            background-color: red;
        }

        .active {
            background-color: green;
        }
    </style>
</head>

<body>
    <div id='app'>
        <div>
            <ul>
                <li v-for = '(item,index) in menus' :key = 'item.id'>
                    <h3>{{index}} - id:{{item.id}} 菜名:{{item.name}}</h3>
                </li>
            </ul>
            <ol>
                <li v-for = "(val,key) in obj" :key='key'>
                    {{key}}---{{val}}
                </li>
            </ol>
        </div>
    </div>
    <script src="./vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
                menus:[
                    {id:1,name:'大腰子'},
                    {id:2,name:'烤鸡翅'},
                    {id:3,name:'烤韭菜'},
                    {id:4,name:'烤大蒜'},
                ],
                obj:{
                    title:'hello 循环',
                    author:'小马哥'
                }
            },
            methods: {
            },
        })
    </script>
</body>

</html>
06 v-for
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>vue的双向数据绑定v-model</title>
    <style>

    </style>
</head>

<body>
    <div id='app'>
        <p>{{msg}}</p>
        <input type="text" v-model='msg'>
        <!-- 复选框单选 -->
        <label for="checkbox">{{checked}}</label>
        <input type="checkbox" id='checkbox' v-model='checked'>
        
        <!-- 复选框多选 -->
        <div class="box">
            <label for="a">黄瓜</label>
            <input type="checkbox" id='a' value='黄瓜' v-model='checkedNames'>

            <label for="b">西红柿</label>
            <input type="checkbox" id='b' value='西红柿' v-model='checkedNames'>

            <label for="c">芸豆</label>
            <input type="checkbox" id='c' value='芸豆' v-model='checkedNames'>

            <br />

            <span>{{checkedNames}}</span>
        </div>
        <label>{{txt}}</label>
        <input v-model.lazy="txt">
        <label>{{msg2}}</label>
        <input v-model.trim="msg2">
    </div>
    <script src="./vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
                msg: '小马哥',
                msg2: '',
                txt: '',
                checked: false,
                checkedNames: []
            },
            methods: {
            
            },
        })
    </script>
</body>

</html>
08 v-model
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>侦听器watch</title>
</head>

<body>
    <div id='app'>
        <input type="text" v-model='msg'>
        <h3>{{msg}}</h3>
        <h3>{{stus[0].name}}</h3>
        <button @click='stus[0].name = "Tom"'>改变</button>
    </div>
    <script src="./vue.js"></script>
    <script>
        // 基本的数据类型可以使用watch直接监听,复杂数据类型Object Array 要深度监视
        new Vue({
            el: '#app',
            data: {
               msg:'',
               stus:[{name:'jack'}]
            },
            watch: {
                // key是属于data对象的属性名 value:监视后的行为 newV :新值 oldV:旧值
                'msg':function(newV,oldV){
                    // console.log(newV,oldV);
                    if(newV === '100'){
                        console.log('hello');
                    }
                    
                },
                // 深度监视: Object |Array
                "stus":{
                    deep:'true',
                    handler:function(newV,oldV){
                        console.log(newV[0].name);
                        
                    }
                }
            },
        })
    </script>
</body>

</html>
09 watch(侦听器)
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>侦听器watch</title>
</head>

<body>
    <div id='app'>
        {{reverseMsg}}
        <h3>{{fullName}}</h3>
        <button @click='handleClick'>改变</button>
    </div>
    <script src="./vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
                msg: 'hello world',
                firstName: '小马',
                lastName: '哥'
            },
            methods: {
                handleClick(){
                    this.msg = '计算属性computed';
                    this.lastName = '妹';
                }
            },
            computed: {
                // computed默认只有getter方法
                // 计算属性最大的优点:产生缓存 如果数据没有发生变化 直接从缓存中取
                reverseMsg: function () {
                    return this.msg.split('').reverse().join('')
                },
                fullName: function () {
                    return this.firstName + this.lastName;
                }
            },

        })
    </script>
</body>

</html>
10 computerd(计算属性)
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>计算属性setter</title>
</head>

<body>
    <div id='app'>
        {{content}}
        <input type="text" v-model='content' @input = 'handleInput'>
        <button @click='handleClick'>获取</button>
    </div>
    <script src="./vue.js"></script>
    <script>
        new Vue({
            el: '#app',
            data: {
                msg: '',
            },
            methods: {
               handleInput:function(event){
                   const {value} = event.target;
                   this.content = value;
               },
               handleClick(){
                //    console.log();
                if (this.content) {
                    console.log(this.content);
                }
                   
               }
            },
            computed: {
              content:{
                  set:function(newV){
                      this.msg = newV;
                  },
                  get:function(){
                      return this.msg;
                  }
              }
            },

        })
    </script>
</body>

</html>
11 计算属性setter方法
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>过滤器</title>
</head>

<body>
    <div id="app">
        <h3>{{price | myPrice('¥')}}</h3>
        <h3>{{msg|myReverse}}</h3>
    </div>
    <script src="./vue.js"></script>
    <script>
        // 创建全局过滤器
        Vue.filter('myReverse', (val) => {
            return val.split('').reverse().join('');
        })
        // 为数据添油加醋
        // ¥  $20
        new Vue({
            el: '#app',
            data: {
                price: 10,
                msg:'hello 过滤器'
            },
            // 局部过滤器
            filters: {
                myPrice: function (price, a) {
                    return a + price;

                }
            }

        })
    </script>
</body>

</html>
12 过滤器
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>案例:音乐播放器</title>
    <style>
        * {
            padding: 0;
            margin: 0;
        }

        ul {
            list-style: none;
        }

        ul li {
            margin: 20px 20px;
            padding: 10px 5px;
            border-radius: 3px;
        }

        ul li.active {
            background-color: #D2E2F3;
        }
    </style>
</head>

<body>
    <div id='app'>
        <audio :src="currentSrc" controls autoplay @ended='handleEnded'></audio>
        <ul>
            <li :class='{active:index===currentIndex}' v-for='(item,index) in musicData' :key='item.id'
                @click='handleClick(item.songSrc,index)'>
                <h2>{{item.id}}-歌名:{{item.name}}</h2>
                <p>{{item.author}}</p>
            </li>
        </ul>
        <button @click='handleNext'>下一首</button>
    </div>

    <script src="./vue.js"></script>
    <script>
        const musicData = [{
                id: 1,
                name: '于荣光 - 少林英雄',
                author: '于荣光',
                songSrc: './static/于荣光 - 少林英雄.mp3'
            },
            {
                id: 2,
                name: 'Joel Adams - Please Dont Go',
                author: 'Joel Adams',
                songSrc: './static/Joel Adams - Please Dont Go.mp3'
            },
            {
                id: 3,
                name: 'MKJ - Time',
                author: 'MKJ',
                songSrc: './static/MKJ - Time.mp3'
            },
            {
                id: 4,
                name: 'Russ - Psycho (Pt. 2)',
                author: 'Russ',
                songSrc: './static/Russ - Psycho (Pt. 2).mp3'
            }
        ];

        new Vue({
            el: '#app',
            data: {
                musicData,
                currentSrc: './static/于荣光 - 少林英雄.mp3',
                currentIndex: 0
            },
            methods: {
                handleClick(src, index) {
                    this.currentSrc = src;
                    this.currentIndex = index;
                },
                handleEnded() {
                    // // 下一首的播放
                    // this.currentIndex++;
                    // this.currentSrc = this.musicData[this.currentIndex].songSrc;
                    this.handleNext();
                },
                handleNext() {
                    this.currentIndex++;
                    if (this.currentIndex === this.musicData.length) {
                        this.currentIndex = 0;
                    }
                    this.currentSrc = this.musicData[this.currentIndex].songSrc
                }
            }
        })
    </script>

</body>

</html>
13 案列_音乐播放器
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>案例:音乐播放器</title>
    <style>
        * {
            padding: 0;
            margin: 0;
        }

        ul {
            list-style: none;
        }

        ul li {
            margin: 20px 20px;
            padding: 10px 5px;
            border-radius: 3px;
        }

        ul li.active {
            background-color: #D2E2F3;
        }
    </style>
</head>

<body>
    <div id='app'>
        <audio :src="getCurrentSongSrc" controls autoplay @ended='handleEnded'></audio>
        <ul>
            <li :class='{active:index===currentIndex}' v-for='(item,index) in musicData' :key='item.id'
                @click='handleClick(index)'>
                <h2>{{item.id}}-歌名:{{item.name}}</h2>
                <p>{{item.author}}</p>
            </li>
        </ul>
        <button @click='handleNext'>下一首</button>
    </div>

    <script src="./vue.js"></script>
    <script>
        const musicData = [{
                id: 1,
                name: '于荣光 - 少林英雄',
                author: '于荣光',
                songSrc: './static/于荣光 - 少林英雄.mp3'
            },
            {
                id: 2,
                name: 'Joel Adams - Please Dont Go',
                author: 'Joel Adams',
                songSrc: './static/Joel Adams - Please Dont Go.mp3'
            },
            {
                id: 3,
                name: 'MKJ - Time',
                author: 'MKJ',
                songSrc: './static/MKJ - Time.mp3'
            },
            {
                id: 4,
                name: 'Russ - Psycho (Pt. 2)',
                author: 'Russ',
                songSrc: './static/Russ - Psycho (Pt. 2).mp3'
            }
        ];

        new Vue({
            el: '#app',
            data: {
                musicData,
                currentIndex: 0
            },
            computed:{
                getCurrentSongSrc(){
                    return this.musicData[this.currentIndex].songSrc;
                }
            },
            methods: {
                handleClick(index) {
                    this.currentIndex = index;
                },
                handleEnded() {
                    this.handleNext();
                },
                handleNext() {
                    this.currentIndex++;
                    if (this.currentIndex === this.musicData.length) {
                        this.currentIndex = 0;
                    }
                }
            }
        })
    </script>
    <!-- 18511803134 小马哥 -->
    <!-- vue组件 -->
    <!-- ajax  axios  ajax库 -->
</body>

</html>
14 案例——音乐播放器之计算属性

2 Vue组件化开发

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <!-- 3.使用子组件 -->
        <App></App>

    </div>
    <script src="./vue.js"></script>
    <script>
        // App组件 html+css+js

        // 创建全局组件 第一个是组件名 第二个是配置
        // 只要创建全局组件 可以在任意地方使用  (template)
        Vue.component('Vheader', {
            template: `
                <div>
                    我是导航组件
                </div>
            `
        })
        Vue.component('Vaside', {
            template: `
                <div>
                    我是侧边栏
                </div>
            `
        })
        const Vbtn  = {
            template:`
                <button>按钮</button>
            `
        }

        const Vcontent = {
            data() {
                return {

                }
            },
            template: `
                <div>
                    我是内容组件 
                    <Vbtn></Vbtn>
                    <Vbtn></Vbtn>
                    <Vbtn></Vbtn>
                    <Vbtn></Vbtn>
                </div>
            `,
            components:{
                Vbtn
            }
        }


        // 使用局部组件的打油诗: 建子 挂子 用子
        // 1.创建组件
        // 注意:在组件中这个data必须是一个函数,返回一个对象
        const App = {
            data() {
                return {
                    msg: '我是App组件'
                }
            },
            components: {
                Vcontent
            },
            template: `
                <div>
                    <Vheader></Vheader>
                    <div>
                        <Vaside />  
                        <Vcontent />
                    </div>
                </div>
            `,
            methods: {
                handleClick() {
                    this.msg = '学习局部组件';
                }

            },
            computed: {

            }
        }
        new Vue({
            el: '#app',
            data: {

            },
            components: {
                // 2.挂载子组件
                App
            }

        })
    </script>
</body>

</html>
01 局部组件的创建与使用
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <!-- 3.使用子组件 -->
        <App></App>

    </div>
    <script src="./vue.js"></script>
    <script>
        // 全局组件

        // 父传子:通过prop来进行通信

        // 1.在子组件中声明props接收在父组件挂载的属性
        // 2.可以在子组件的template中任意使用
        // 3.在父组件绑定自定义的属性
        Vue.component('Child',{
            template:`
                <div>
                    <h3>我是一个子组件</h3>   
                    <h4>{{childData}}</h4>
                </div>
            `,
            props:['childData']
        })

        const App = {
            data() {
                return {
                    msg: '我是父组件传进来的值'
                }
            },
            template: `
                <div>
                    <Child :childData = 'msg'></Child>
                </div>
            `,
            computed: {

            }
        }
        new Vue({
            el: '#app',
            data: {

            },
            components: {
                // 2.挂载子组件
                App
            }

        })
    </script>
</body>

</html>
02 组件通信-父传子
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <!-- 3.使用子组件 -->
        <App></App>

    </div>
    <script src="./vue.js"></script>
    <script>
        // 全局组件

        // 子往父传值

        // 在父组件中 子组件上绑定自定义事件
        // 在子组件中 触发原生的事件 在事件函数通过this.$emit触发自定义的事件
        Vue.component('Child', {
            template: `
                <div>
                    <h3>我是一个子组件</h3>   
                    <h4>{{childData}}</h4>
                    <input type="text" @input = 'handleInput'/>
                </div>
            `,
            props: ['childData'],
            methods:{
                handleInput(e){
                    const val = e.target.value;

                    this.$emit('inputHandler',val);
                }
            },
        })

        const App = {
            data() {
                return {
                    msg: '我是父组件传进来的值',
                    newVal:''
                }
            },
            methods:{
                input(newVal){
                    // console.log(newVal);
                    this.newVal = newVal;
                }
            },
            template: `
                <div>
                    <div class='father'>
                        数据:{{newVal}}
                    </div>
                    <Child :childData = 'msg' @inputHandler = 'input'></Child>
                </div>
            `,
            computed: {

            }
        }
        new Vue({
            el: '#app',
            data: {

            },
            components: {
                // 2.挂载子组件
                App
            }

        })
    </script>
</body>

</html>
03 子传父
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <!-- 3.使用子组件 -->
        <App></App>

    </div>
    <script src="./vue.js"></script>
    <script>
        const bus = new Vue();
        // 中央事件总线 bus
        Vue.component('B', {
            data() {
                return {
                    count: 0
                }
            },
            template: `
                <div>{{count}}</div>
            `,
            created(){
                // $on 绑定事件
                bus.$on('add',(n)=>{
                    this.count+=n;
                })
            }
        })

        Vue.component('A', {
            data() {
                return {
                    
                }
            },
            template: `
                <div>
                 <button @click='handleClick'>加入购物车</button> 
                    
                </div>
            `,
            methods:{
                 handleClick(){
                    // 触发绑定的函数 // $emit 触发事件
                     bus.$emit('add',1);
                 }
            }
        })


        const App = {
            data() {
                return {

                }
            },

            template: `
                <div>
                    <A></A>
                    <B></B>
                </div>
            `,
        }
        new Vue({
            el: '#app',
            data: {

            },
            components: {
                // 2.挂载子组件
                App
            }

        })
    </script>
</body>

</html>
04 平行组件
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <!-- 3.使用子组件 -->
        <App></App>

    </div>
    <script src="./vue.js"></script>
    <script>
       
        // 插槽 留坑
        // props 验证规则
        // 组件的生命周期
        // UI库 element-ui
        // **** 如何设计组件? *****
        // provide
        // inject
        // 父组件 provide来提供变量,然后再子组件中通过inject来注入变量.无论组件嵌套多深
        // 中央事件总线 bus
        Vue.component('B', {
            data() {
                return {
                    count: 0
                }
            },
            inject:['msg'],
            created(){
                console.log(this.msg);
                
            },
            template: `
                <div>
                    {{msg}}
                </div>
            `,
        })

        Vue.component('A', {
            data() {
                return {

                }
            },
            created(){
                // console.log(this.$parent.$parent);
                // console.log(this.$children);
                console.log(this);
                
                
            },
            template: `
                <div>
                     <B></B>
                </div>
            `
        })


        const App = {
            data() {
                return {
                    title:"老爹"
                }
            },
            provide(){
                return {
                    msg:"老爹的数据"
                }
            },
            template: `
                <div>
                    <A></A>
                </div>
            `,
        }
        new Vue({
            el: '#app',
            data: {

            },
            components: {
                // 2.挂载子组件
                App
            }

        })
    </script>
</body>

</html>
05 其他通信方式
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <!-- 3.使用子组件 -->
        <App></App>

    </div>
    <script src="./vue.js"></script>
    <script>
        
        Vue.component('MBtn',{
            template:`
                <button>
                    <slot></slot>
                </button>
            `
        })

        const App = {
            data() {
                return {
                    title: "老爹"
                }
            },
           
            template: `
                <div>
                    <m-btn><a href="#">登录</a></m-btn>
                    <m-btn>注册</m-btn>
                </div>
            `,
        }
        new Vue({
            el: '#app',
            data: {

            },
            components: {
                // 2.挂载子组件
                App
            }

        })
    </script>
</body>

</html>
06 匿名插槽
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <!-- 3.使用子组件 -->
        <App></App>

    </div>
    <script src="./vue.js"></script>
    <script>
        // 只要匹配到slot标签的name值 template中的内容就会被插入到这个槽中
        Vue.component('MBtn', {
            template: `
                <button>
                     <slot name='submit'></slot>
                     <slot name='login'></slot>
                     <slot name='register'></slot>
                </button>
            `
        })

        const App = {
            data() {
                return {
                    title: "老爹"
                }
            },

            template: `
                <div>
                     <MBtn>
                         <template slot='submit'>
                             提交
                         </template>
                     </MBtn>
                    
                    <m-btn>
                        <template slot='login'>
                            <a href="#">登录</a>
                        </template>
                    </m-btn>
                   
                    <m-btn>
                        <template slot='register'>
                            注册
                        </template>
                    </m-btn>
                </div>
            `,
        }
        new Vue({
            el: '#app',
            data: {

            },
            components: {
                // 2.挂载子组件
                App
            }

        })
    </script>
</body>

</html>
07 具名插槽
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <!-- 3.使用子组件 -->
        <App></App>

    </div>
    <script src="./vue.js"></script>
    <script>
        // 已经开发了一个待办事项列表的组件,很多模块都在
        // A B
        // 1.之前数据格式和引用接口不变,正常显示
        // 2.新功能模块增加对勾
        const todoList = {
            data() {
                return {

                }
            },
            props: {
                todos: Array,
                defaultValue: []
            },
            template: `
        <ul>
            <li v-for='item in todos' :key='item.id'>
                <slot :itemValue = 'item'>
                   
                </slot>
                 {{item.title}}
               
            </li>
        </ul>
        `
        }

       

        const App = {
            data() {
                return {
                    todoList: [{
                            title: '大哥你好么',
                            isComplate: true,
                            id: 1
                        },
                        {
                            title: '小弟我还行',
                            isComplate: false,
                            id: 2
                        },
                        {
                            title: '你在干什么',
                            isComplate: false,
                            id: 3
                        },
                        {
                            title: '抽烟喝酒烫头',
                            isComplate: true,
                            id: 4
                        }
                    ]
                }
            },
            components: {
                todoList
            },
            template: `
                  <todoList :todos='todoList'>
                     <template v-slot='data'>
                        <input type="checkbox" v-model='data.itemValue.isComplate' />
                    </template>
                  </todoList>
        `,
        }
        new Vue({
            el: '#app',
            data: {

            },
            components: {
                App
            }

        })
    </script>
</body>

</html>
08 作用域插槽
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .active{
            color: red;
        }
    </style>
</head>

<body>
    <div id="app">
        <!-- 3.使用子组件 -->
        <App></App>

    </div>
    <script src="./vue.js"></script>
    <script>
        /*
        beforeCreate
        created
        beforeMount
        mounted
        beforeUpdate
        updated
        activated 激活
        deactivated 停用
        配合keep-alive
        beforeDestroy
        destroyed
        */
        Vue.component('Test', {
            data() {
                return {
                    msg: "小马哥",
                    isRed:false
                }
            },
            methods: {
                handlerClick() {
                    this.msg = 'alex';
                    this.isRed = true;
                }
            },
            template: `
                <div>

                    <button @click='handlerClick'>改变</button>
                    <h3 :class='{active:isRed}'>{{msg}}</h3>    
                </div>
            `,
            beforeCreate() {
                console.log('组件创建之前', this.$data);
            },
            created() {
                // 非常重要的事情,在此时发送ajax 请求后端的数据
                console.log('组件创建完成', this.$data);
            },
            beforeMount() {
                // 即将挂载
                console.log('DOM挂载之前', document.getElementById('app'));
            },
            mounted() {
                // 发送ajax
                console.log('DOM挂载完成', document.getElementById('app'));

            },
            beforeUpdate() {
                // 获取更新之前的DOM
                console.log('更新之前的DOM', document.getElementById('app').innerHTML);

            },
            updated() {
                // 获取最新的DOM
                console.log('更新之后的DOM', document.getElementById('app').innerHTML);
            },
            beforeDestroy() {
                console.log('销毁之前');

            },
            destroyed() {
                console.log('销毁完成');

            },
            activated(){
                console.log('组件被激活了');
                
            },
            deactivated(){
                console.log('组件被停用了');
                
            }
        })

        const App = {
            data() {
                return {
                    isShow: true
                }
            },
            components: {},
            methods: {
                clickHandler() {
                    this.isShow = !this.isShow;
                }
            },
            template: `
                 <div>
                    <keep-alive>
                        <Test v-if='isShow'></Test>
                    </keep-alive>
                    <button @click='clickHandler'>改变生死</button>
                </div>
            `,
        }
        new Vue({
            el: '#app',
            data: {

            },
            components: {
                App
            }

        })
    </script>
</body>

</html>
09 生命周期
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
    </style>
</head>

<body>
    <div id="app">
        <!-- 3.使用子组件 -->
        <App></App>

    </div>
    <script src="./vue.js"></script>
    <script type='module'>
        // import xxx from './modules.js';

        const App = {
            data() {
                return {
                    isShow: false
                }
            },
            methods: {
                asyncLoad() {
                    this.isShow = !this.isShow;
                }
            },
            components: {
                Test:()=>import('./Test.js')
            },
            template: `
                 <div>            
                    <button @click='asyncLoad'>异步加载</button>
                    <Test v-if='isShow'></Test>
                 </div>
            `,
        }
        new Vue({
            el: '#app',
            data: {

            },
            components: {
                App
            }

        })
    </script>
</body>

</html>
10 异步组件
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <!-- 3.使用子组件 -->
        <App></App>

    </div>
    <script src="./vue.js"></script>
    <script>
        
        Vue.component('Test', {
            data() {
                return {
                    msg: "小马哥",
                }
            },
            template: `
                <div>
                    <h3>{{msg}}</h3>    
                </div>
            `,
        })

        const App = {
            data() {
                return {
                }
            },
            mounted(){
                // 1.如果给标签添加ref,获取的就是真实的DOM节点
                // 2.如果给子组件添加ref,获取的是当前子组件对象
                console.log(this.$refs.btn);
                // 加载页面,自动获取焦点
                this.$refs.input.focus();
                console.log(this.$refs.test);
                
            },
            components: {},
            template: `
                 <div>
                    <Test ref='test'></Test>
                    <input type="text" ref='input'/>
                    <button ref='btn'>改变生死</button>
                </div>
            `,
        }
        new Vue({
            el: '#app',
            data: {

            },
            components: {
                App
            }

        })
    </script>
</body>

</html>
11 ref使用
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <h3>{{message}}</h3>
    </div>
    <script src="./vue.js"></script>
    <script>
        const vm = new Vue({
            el:'#app',
            data:{
                message:'小马哥'
            }
        })
        vm.message = 'new Message';
        // console.log(vm.$el.textContent);
        // 为了数据变化之后等待vue完成更新DOM,可以在数据变化之后立即使用Vue.nextTick 在当前的回调函数中能获取最新的DOM
        Vue.nextTick(()=>{
           console.log(vm.$el.textContent); 
        })
        
    </script>

    
</body>
</html>
12 nextTick的用法
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id='app'>
        <App></App>
    </div>
    <script src="./vue.js"></script>
    <script>
        /* 
        需求:
            在页面拉取一个接口,这个接口返回一些数据,这些数据是这个页面的一个浮层组件要依赖的,
            然后我在接口一返回数据就展示了这个浮层组件,展示的同时,
            上报一些数据给后台(这些数据是父组件从接口拿的),
            这个时候,神奇的事情发生了,虽然我拿到数据了,但是浮层展现的时候,
            这些数据还未更新到组件上去,上报失败
        */
        const Pop = {
            data() {
                return {
                    isShow: false
                }
            },
            props: {
                name: {
                    type: String,
                    default: ''
                },
            },
            template: `
                <div v-if='isShow'>
                    {{name}}
                </div>
            `,
            methods: {
                show() {
                    this.isShow = true; //弹窗组件展示
                    console.log(this.name);
                    
                }
            },
        }
        const App = {
            data() {
                return {
                    name: ''
                }
            },
            created() {
                //    模拟异步请求
                setTimeout(() => {
                    // 数据更新
                    this.name = '小马哥';
                    this.$nextTick(()=>{
                        this.$refs.pop.show();
                    })
                }, 1000);
            },
            components: {
                Pop
            },
            template: `<pop ref='pop' :name='name'></pop>`
        }
        const vm = new Vue({
            el: '#app',
            components: {
                App
            }
        })
    </script>
</body>

</html>
13 nextTick的应用
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <h3>
            {{user.name}},{{user.age}},{{user.phone}}
            <button @click='handlerAdd'>添加属性</button>
        </h3>
    </div>
    <script src="./vue.js"></script>
    <script>
        // Vue不能检测对象属性的添加和删除
        new Vue({
            el:"#app",
            data:{
                user:{}
            },
            methods: {
                handlerAdd() {
                    // Vue.$set(object,key,value)添加响应式属性
                    // this.user.age = 20;
                    // this.$set(this.user,'age',20);
                    
                    // 添加多个响应式属性
                    this.user = Object.assign({},this.user,{
                        age:20,
                        phone:18511803134
                    })
                }
            },
            created(){
                setTimeout(() => {
                    this.user = {
                        name:"张三"
                    }
                }, 1250);
            }
        })
    
    </script>
</body>
</html>
14 对象变更检测
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id='app'>
        {{msg}}
    </div>
    <script src="./vue.js"></script>
    <script>
        const myMixin = {
            data(){
                return {
                    msg:"123"
                }
            },
            created(){
                this.sayHello();
            },
            methods: {
                sayHello() {
                    console.log('hello mixin');
                }
            },
        }
        // mixin来分发Vue组件中的可复用功能

        new Vue({
            el:"#app",
            data:{
                msg:"小马哥"
            },
            created(){
                console.log(1111);
                
            },
            mixins:[myMixin]
        })
    
    </script>
    
</body>
</html>
15 混入mixin偷懒技术
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>mixin的应用</title>
</head>

<body>
    <div id='app'>

    </div>
    <script src="./vue.js"></script>
    <script>
        //    一个是模态框 一个提示框
        // 它们看起来不一样,用法不一样,但是逻辑一样(切换boolean)
        /* 
        // 全局的mixin 要格外小心 因为每个组件实例创建是,它都会被调用
        Vue.mixin({

        })
        
         */
        const toggleShow = {
            data() {
                return {
                    isShow: false
                }
            },
            methods: {
                toggleShow() {
                    this.isShow = !this.isShow
                }
            }
        }

        const Modal = {
            template: `
                <div v-if='isShow'><h3>模态框组件</h3></div>
            `,
            // 局部的mixin
            mixins: [toggleShow]

        }

        const ToolTip = {
            template: `
            <div v-if='isShow'>
                <h2>提示框组件</h2>
            </div>
         `,
            mixins: [toggleShow]
        }
        new Vue({
            el: "#app",
            data: {

            },
            components: {
                Modal,
                ToolTip
            },
            template: `
                <div>
                    <button @click='handleModel'>模态框</button>
                    <button @click='handleToolTip'>提示框</button>
                    <Modal ref='modal'></Modal>
                    <ToolTip ref='toolTip'></ToolTip>
                </div>
            `,
            methods: {
                handleModel() {
                    this.$refs.modal.toggleShow();
                },
                handleToolTip() {
                    this.$refs.toolTip.toggleShow();
                }
            },
        })
    </script>

</body>

</html>
16 mix混入偷懒技术应用
import Vue from 'vue'
import App from './App.vue'
import router from './router'
// 导入创建的store
import store from './store'
Vue.config.productionTip = false
// 注册全局组件的方式
import ShoppingCart from '@/components/ShoppingCart';
Vue.component(ShoppingCart.name, ShoppingCart)
// 全局注册过滤器
Vue.filter('currency',(value)=>{
  return '$' + value;
})

new Vue({
  router,
  //一定要挂载
  store,
  render: h => h(App)
}).$mount('#app')
17 注册全局组件与全局过滤器

 

3 Vue核心插件(vue_vuex&vue_router)

 3.1 vue-router

import Vue from 'vue';
// 1.导入
import VueRouter from 'vue-router';
// 2.模块化机制 使用Router @=src文件夹
Vue.use(VueRouter)
import Home from '@/views/Home';
import About from '@/views/About';
import User from '@/views/User';

// 3.创建路由器对象
export default new VueRouter({
    mode: 'history', //history模式 干净的网页地址
    routes: [
        {
            path: '/',
            // redirect:'/home'
            redirect: { name: 'home' }
        },
        {
            path: "/home",
            name: 'home',
            // component: Home
            components: {
                default: Home,//默认的名字
                main: () => import('@/views/Main'),
                sideBar: () => import('@/views/SideBar'),
            }
        },
        // 同一个路径可以匹配多个路由,匹配的优先级按照路由的定义顺序:
        // 谁先定义的,谁的优先级最高
        {
            path: '/about',
            name: 'about',
            component: About
        },
        // {
        //     path: '/about',
        //     name: "about",
        //     component: Home
        // },

        {
            path: '/user/:id',
            name: 'user',
            component: User,
            // props:true
            props: (route) => ({
                id: route.params.id,
                title: route.query.title
            }),
            children: [
                {
                    path: 'profile',
                    component: () => import('@/views/Profile')
                },
                {
                    path: 'posts',
                    component: () => import('@/views/Posts')
                }
            ],
            meta: {
                // 加到黑名单
                requireAuth: true
            }

        },
        {
            path: '/post',
            name: "post",
            component: () => import('@/views/Post')
        },
        {
            path: '/notes',
            name: 'notes',
            component: () => import('@/views/Notes'),
            meta: {
                // 加到黑名单
                requireAuth: true
            }
        },
        {
            path: '/login',
            name: 'login',
            component: () => import('@/views/Login')
        },
        {
            path: '/eaditor',
            name: 'eaditor',
            component: () => import('@/views/Eaditor')
        },
        // http://localhost:8080/page?id=1&title=foo  query
        {
            path: '/page',
            name: 'page',
            component: () => import('@/views/Page'),
            alias: '/aaa' //给路由别名 
        },
        {
            path: "/blog",
            name: 'blog',
            component: () => import('@/views/Blog'),
            meta: {
                // 加到黑名单
                requireAuth: true
            }
        },
        {
            path: '/user-*',
            component: () => import('@/views/User-admin')
        },
        {
            path: '*',
            component: () => import('@/views/404')
        }
    ]
})

// http://localhost:8080/user/1
// http://localhost:8080/user/2
// 同一个页面
router/index.js
<template>
  <div id="app">
    <!-- router-link相当于a标签 to属性相当于a标签的href -->
    <router-link to='/'>首页</router-link>|
    <router-link to='/about'>关于</router-link>
    <router-link :to="{name:'home'}">首页</router-link>|
    <router-link :to="{name:'about'}">关于</router-link>|
    <!-- 声明式 --> 
    <router-link :to="{name:'user',params:{id:1}}">User1</router-link>|
    <router-link :to="{name:'user',params:{id:2}}">User2</router-link>|
    <router-link :to="{name:'page',query:{id:1,title:'foo'}}">Page</router-link>|
    <!-- 路由嵌套 -->
    <router-link to='/user/1/profile'>user/1/profile</router-link>|
    <router-link to='/user/1/posts'>user/1/posts</router-link>|
    <router-link :to='{name:"notes"}'>我的笔记</router-link>|
    <router-link :to='{name:"eaditor"}'>编辑</router-link>|
    <router-link :to='{name:"blog"}'>博客</router-link>|
    <router-link :to='{name:"post"}'>Post</router-link>|


    <!-- router-view相当于路由组件的出口 -->
    <!-- Home -->
    <router-view></router-view> 
    <router-view name='main' class="main"></router-view> 
    <router-view name='sideBar' class="sidebar"></router-view> 
  </div>
</template>

<style>
#app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

#nav {
  padding: 30px;
}

#nav a {
  font-weight: bold;
  color: #2c3e50;
}

#nav a.router-link-exact-active {
  color: #42b983;
}
</style>
app.vue
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import axios from 'axios';
Vue.prototype.$https = axios;
Vue.config.productionTip = false

// 全局守卫
/* router.beforeEach((to,from,next)=>{
  // 用户访问了/notes
  if(to.path === '/notes'){
    // 获取用户登录的信息
    const user = JSON.parse(localStorage.getItem('user'));
    if(user){
      // 用户已登录
      next();
    }else{
      // 用户没有登录 跳转到登录页面进行登录
      next('/login');
    }
  }
  next();
}) */
router.beforeEach((to, from, next) => {
  if(to.matched.some(record=>record.meta.requireAuth)){
    // 需要权限,在黑名单
    if(!localStorage.getItem('user')){
      next({
        path:'/login',
        query:{
          redirect:to.fullPath
        }
      })
    }else{
      next();
    }
  }
  // 在白名单
  next();
})

new Vue({
  // 4.挂载到vue的实例中
  router,
  render: h => h(App)
}).$mount('#app')
main.js
<template>
    <div>
        <h2>登录页面</h2>
        <input type="text" v-model='user'>
        <input type="password" v-model='pwd'>
        <button @click='handleLogin'>登录</button>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                user: '',
                pwd:''
            }
        },
        methods: {
            handleLogin() {
                // 1.获取用户名和密码
                // 2.与后端发生交互
                setTimeout(() => {
                    let data = {
                        user:this.user
                    }
                    // 保存用户名刀本地
                    localStorage.setItem('user',JSON.stringify(data));
                    // 跳转到我的笔记页
                    this.$router.push({
                       path: this.$route.query.redirect
                    })

                }, 1000);
            }
        },
    }
</script>

<style lang="scss" scoped>

</style>
views/login.vue
<template>
  <div class="post">
    <div v-if="loading" class="loading">Loading.....</div>
    <div v-if="error" class="error">{{error}}</div>
    <div v-if="post">
      <h3>标题:{{post.title}}</h3>
      <p>内容:{{post.body}}</p>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      post: null,
      error: null,
      loading: false
    };
  },
// 导航完成之后获取数据
  created() {
    console.log(this.$https);
    this.getPostData();
  },
  watch: {
    $route: "getPostData"
  },
  methods: {
    async getPostData() {
      try {
        this.loading = true;
        const { data } = await this.$https.get("/api/post");
        this.loading = false;
        this.post = data;
      } catch (error) {
        this.error = error.toString();
      }
    }
  }
};
</script>

<style lang="scss" scoped>
</style>
views/post.vue
<template>
  <div>
    <div>用户页面 {{$route.params.id}}</div>
    <div>用户页面 {{id}}-{{title}}</div>
    <button @click='goHome'>跳转到首页</button>
    <button @click='goBack'>后退</button>
    <!-- 子路由组件出口 -->
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  // 路由组件会复用的情况?

  // 当路由参数变化时 /user/1切换到/user/2 原来的组件实例会被复用.
  // 因为两个路由渲染了同个组件 复用高效
  created() {
    // console.log(this.$route.params.id);
    // console.log(this.$router);
  },
  methods: {
    goBack(){
      this.$router.go(-2);
    },
    goHome() {
      // 编程式导航
      // this.$router.push('/');
      // this.$router.push('name');
      this.$router.push({
        path:'/'
      });
      // this.$router.push({
      //  name:'user',
      //  params:{id:2}
      // });
      // this.$router.push({
      //   path:"/register",
      //   query:{plan:'123'}
      // })
    }
  },
  props:['id','title'],
  // 响应路由参数的变化
  // watch:{
  //     $route:(to,from)=>{
  //         console.log(to.params.id);
  //         // 发起ajax 请求后端接口数据 数据驱动视图

  //     }
  // }
  beforeRouteUpdate(to, from, next) {
    console.log(to.params.id);
    // 一定要调用next,不然会阻塞整个路由  放行
    next();
  }
};
</script>

<style lang="scss" scoped>
</style>
Views/user.vue

3.2 vue-x

import Vue from 'vue'
// 1.导入模块
import Vuex from 'vuex'

// 2.使用当前的插件
Vue.use(Vuex)
import cart from './modules/cart'
import products from './modules/products'
import createLogger from 'vuex/dist/logger'

const store = new Vuex.Store({
  plugins: [createLogger()],
  state: { //当前的状态
    count: 0,
    username: '小马哥'
  },
  getters: {
    evenOrOdd(state) {
      return state.count % 2 === 0 ? '偶数' : '奇数';
    }
  },
  mutations: { //声明同步的方法
    increment(state) {
      // 修改状态
      state.count++
    },
    decrement(state) {
      state.count--
    },
    incrementAsync(state,amount) {
      state.count+=amount;
    }

  },
  actions: { //声明异步的方法
    // increment({commit}){
    //   // commit mutations中声明的方法
    // 修改状态的唯一方法是提交mutation
    //   commit('increment')
    // },
    // decrement({commit}) {
    //   commit('decrement')
    // }
    incrementAsync({ commit },{amount}) {
      setTimeout(() => {
        commit('incrementAsync',amount)
      }, 1000);
    }

  },
  modules: {
    cart,
    products
  }
})
export default store;
store/index.js
<template>
  <div class="about">
    <h1>This is an about page</h1>
    {{myCount}} - {{user}}
    {{evenOrOdd}}
    <button @click="increment">+1</button>
    <button @click="decrement">-1</button>
    <button @click="incrementAsync">+1异步</button>
    <ProductList></ProductList>
    <hr>
    <!-- <ShoppingCart></ShoppingCart> -->
    <shopping-cart></shopping-cart>

  </div>
</template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from "vuex";
import ProductList from '@/components/ProductList'
// import ShoppingCart from '@/components/ShoppingCart'
export default {
  components: {
    ProductList,
    // ShoppingCart
  },
  computed: {
    // count() {
    //   return this.$store.state.count;
    // },
    // username(){
    //   return this.$store.state.username;
    // }
    // ...mapState(['count','username'])
    ...mapState({
      myCount: "count",
      user: "username"
    }),
    // evenOrOdd() {
    //   return this.$store.getters.evenOrOdd;
    // }
    ...mapGetters(['evenOrOdd'])
  },
  methods: {
    incrementAsync() {
      // 在组件内部提交数据 载荷形式分发
      // this.$store.dispatch('incrementAsync',{
      //   amount:10
      // });
      this.$store.dispatch({
        type:'incrementAsync',
        amount: 10
      })
    },
    // ...mapActions(['incrementAsync']),

   increment() {
      // dispatch触发actions中声明的方法(异步)
      // this.$store.dispatch('increment');
      this.$store.commit("increment");
    },
    decrement() {
      // this.$store.dispatch('decrement');
      this.$store.commit("decrement");
    },
    ...mapMutations(['increment','decrement'])
  },
  // 带参数提交
   value(){
          this.$store.commit("changeHospital",this.value)
      }
};
</script>
views/Abount.vue

 

点击下载详情文档

 点击查看更多文档(f473 )

 

4 Xmall 商城项目实战

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import './plugins/element.js'
Vue.config.productionTip = false
import { getStore } from '@/utils/storage'

// 使用vue-lazylocad
import VueLazyload from 'vue-lazyload';
Vue.use(VueLazyload);

Vue.use(VueLazyload, {
  preLoad: 1.3,
  error: 'static/images/error.png',
  loading: 'static/images/load.gif',
  attempt: 1
})
// 挂载axios到vue的原型,由于继承性,所有的组件都可以使用this.$http
import axios from 'axios';
Vue.prototype.$http = axios;

// 设置公共的url
// axios.defaults.baseURL = 'http://localhost:3000';
// 我们自己服务器的url
axios.defaults.baseURL = 'http://49.235.88.178:3000';

//拦截请求 加入token
axios.interceptors.request.use(config => {
  const token = getStore('token');
  if (token) {
    // 表示用户已登录
    config.headers.common['Authorization'] = token;
  }
  return config
}, error => {
  return Promise.reject(error);
})
// 守卫
router.beforeEach((to, from, next) => {
  axios.post('/api/validate', {}).then(res => {
    // console.log(res.data)
    let data = res.data;
    if (data.state !== 1) {
      // 用户要登录
      if (to.matched.some(record => record.meta.auth)) {
        // 用户未登录 需要跳转登录页面
        next({
          path: '/login',
          query: {
            redirect: to.fullPath
          }
        })
      } else {
        next();
      }
    } else {
     
      // 保存用户的信息
      store.commit('ISLOGIN', data);
      
      if (to.path === '/login') {
        router.push({
          path: '/'
        })
      }
      next();
    }
  }).catch(error => {
    console.log(error);
  })



})
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')
main.js
import Vue from 'vue'
import VueRouter from 'vue-router'

// 解决路由命名冲突的方法
const routerPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
  return routerPush.call(this, location).catch(error => error)
}

// 异步组件加载
const Index = () => import('@/views/Index');
const Login = () => import('@/views/Login');
const Home = () => import('@/views/Home');
const Goods = () => import('@/views/Goods');
const Thanks = () => import('@/views/Thanks');
const GoodsDetail = () => import('@/views/GoodsDetail');
const User = () => import('@/views/User');
// import Index from '@/views/Index'
// import Login from '@/views/Login'
// import Home from '@/views/Home'
// import Goods from '@/views/Goods'
// import Thanks from '@/views/Thanks'
// import GoodsDetail from '@/views/GoodsDetail'
// import User from '@/views/User';
Vue.use(VueRouter)

const routes = [

  {
    path: "/",
    redirect: '/home',
    name: 'home',
    component: Index,
    children: [
      {
        path: "home",
        component: Home
      },
      {
        path: "goods",
        component: Goods
      },
      {
        path: "thanks",
        component: Thanks
      },
      {
        path: 'goodsDetail',
        name: 'goodsDetail',
        component: GoodsDetail
      }
    ]
  },
  {
    path: '/login',
    name: "login",
    component: Login
  },
  {
    path:'/user',
    name:'user',
    component:User,
    meta:{
      // 需要守卫
      auth:true
    }
  }
 
  
]

const router = new VueRouter({
  mode: 'history',
  routes
})

export default router
router/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)
import { setStore, getStore } from '@/utils/storage'

export default new Vuex.Store({
  state: {
    login: false,//是否登录
    userInfo: null,//用户信息
    cartList: [],//加入购物车商品
    showCart: false
  },
  mutations: {
    // 网页初始化时从本地缓存获取购物车数据
    INITBUYCART(state) {
      let initCart = getStore('buyCart');
      if (initCart) {
        state.cartList = JSON.parse(initCart)

      }
    },
    SHOWCART(state, { showCart }) {
      state.showCart = showCart;
    },
    ISLOGIN(state, info) {
      state.userInfo = info;
      state.login = true;
      setStore('userInfo', info);
    },
    ADDCART(state, { productId, salePrice, productName, productImageBig, productNum = 1 }) {
      let cart = state.cartList;
      let goods = {
        productId,
        salePrice,
        productName,
        productImageBig
      }
      let falg = true;
      if (cart.length) {
        cart.forEach(item => {
          if (item.productId === productId) {
            if (item.productNum >= 0) {
              falg = false;
              item.productNum += productNum;
            }
          }
        })
      }
      if (!cart.length || falg) {
        goods.productNum = productNum;
        cart.push(goods);
      }
      state.cartList = cart;
      setStore('buyCart', cart);
    }

  },
  actions: {
  },
  modules: {
  }
})
store/index.js
<template>
    <div>
        <m-header></m-header>
        <!-- 子路由的出口-->
        <router-view></router-view>
    </div>
</template>

<script>
    import MHeader from '@/common/MHeader';
    export default {
        components: {
            MHeader,
        },
    }
</script>

<style lang="scss" scoped>

</style>
views/index.vue
<template>
  <div class="login">
    <div class="box">
      <span>使用账号 登录官网</span>
      <el-form
        :model="ruleForm"
        status-icon
        :rules="rules"
        ref="ruleForm"
        label-width="100px"
        class="demo-ruleForm"
      >
        <el-form-item label="账号" prop="user">
          <el-input type="text" v-model="ruleForm.user" autocomplete="off" placeholder="请输入账号"></el-input>
        </el-form-item>
        <el-form-item label="密码" prop="pass">
          <el-input type="password" v-model="ruleForm.pass" autocomplete="off" placeholder="请输入密码"></el-input>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="submitForm('ruleForm')">登录</el-button>
          <el-button>返回</el-button>
        </el-form-item>
      </el-form>
    </div>
  </div>
</template>

<script>
import { setStore, getStore, removeStore } from "@/utils/storage";
export default {
  data() {
    let validateUser = (rule, value, callback) => {
      if (value === "") {
        callback(new Error("请输入账号"));
      } else {
        callback();
      }
    };
    let validatePass = (rule, value, callback) => {
      if (value === "") {
        callback(new Error("请输入密码"));
      } else {
        callback();
      }
    };
    return {
      ruleForm: {
        user: "",
        pass: ""
      },
      rules: {
        user: [{ validator: validateUser, trigger: "blur" }],
        pass: [{ validator: validatePass, trigger: "blur" }]
      },
      cart: []
    };
  },
  mounted() {
    //缓存当前购物车中的数据
    this.login_addCart();
  },
  methods: {
    login_addCart() {
      let cartArr = [];
      let localCart = JSON.parse(getStore("buyCart"));
      console.log(localCart);

      if (localCart && localCart.length) {
        localCart.forEach(item => {
          cartArr.push({
            userId: getStore("id"),
            productId: item.productId,
            productNum: item.productNum
          });
        });
      }
      this.cart = cartArr;
    },
    submitForm(formName) {
      this.$refs[formName].validate(async valid => {
        if (valid) {
          //   获取用户名和密码
          // let {user,pass} = this.ruleForm;
          let res = await this.$http.post("/api/login", this.ruleForm);
          if (res.data.code === 200) {
            let { username, token, id } = res.data;
            //  持久化 存储
            setStore("token", token);
            setStore("id", id);
            console.log(this.cart);

            if (this.cart && this.cart.length) {
              this.cart.forEach(async item => {
                let res = await this.$http.post("/api/addCart", item);
                if (res.data.success === true) {
                  //.......
                }
                removeStore("buyCart");
                this.$router.push({ path: "/" });
              });
            } else {
              this.$router.push({path: "/"});
            }
          }
        } else {
          console.log("error submit!!");
          return false;
        }
      });
    }
  }
};
</script>

<style lang="scss" scoped>
.login {
  position: relative;
  overflow: visible;
  background: #ededed;
  .box {
    width: 450px;
    border: 1px solid #dadada;
    border-radius: 10px;
    position: absolute;
    top: 200px;
    left: 50%;
    padding: 50px 50px 50px 10px;
    margin-left: -225px;
    box-shadow: 0 9px 30px -6px rgba(0, 0, 0, 0.2),
      0 18px 20px -10px rgba(0, 0, 0, 0.04),
      0 18px 20px -10px rgba(0, 0, 0, 0.04),
      0 10px 20px -10px rgba(0, 0, 0, 0.04);
    text-align: center;
    form {
      margin-top: 30px;
    }
    span {
      color: #333;
      font-weight: 400;
    }
  }
}
</style>
Views/Login/index.vue

 

补充

-- 持久化储存

export const setStore = (name, content) => {
    if (!name) return;
    if (typeof content !== 'string') {
        content = JSON.stringify(content);
    }
    window.localStorage.setItem(name, content);
}
export const getStore = name => {
    if (!name) return;
    return window.localStorage.getItem(name);
}
export const removeStore = name => {
    if(!name) return;
    window.localStorage.removeItem(name);
}
utils/storage.js
<template>
  <div class="login">
    <div class="box">
      <span>使用账号 登录官网</span>
      <el-form
        :model="ruleForm"
        status-icon
        :rules="rules"
        ref="ruleForm"
        label-width="100px"
        class="demo-ruleForm"
      >
        <el-form-item label="账号" prop="user">
          <el-input type="text" v-model="ruleForm.user" autocomplete="off" placeholder="请输入账号"></el-input>
        </el-form-item>
        <el-form-item label="密码" prop="pass">
          <el-input type="password" v-model="ruleForm.pass" autocomplete="off" placeholder="请输入密码"></el-input>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="submitForm('ruleForm')">登录</el-button>
          <el-button>返回</el-button>
        </el-form-item>
      </el-form>
    </div>
  </div>
</template>

<script>
import { setStore, getStore, removeStore } from "@/utils/storage";
export default {
  data() {
    let validateUser = (rule, value, callback) => {
      if (value === "") {
        callback(new Error("请输入账号"));
      } else {
        callback();
      }
    };
    let validatePass = (rule, value, callback) => {
      if (value === "") {
        callback(new Error("请输入密码"));
      } else {
        callback();
      }
    };
    return {
      ruleForm: {
        user: "",
        pass: ""
      },
      rules: {
        user: [{ validator: validateUser, trigger: "blur" }],
        pass: [{ validator: validatePass, trigger: "blur" }]
      },
      cart: []
    };
  },
  mounted() {
    //缓存当前购物车中的数据
    this.login_addCart();
  },
  methods: {
    login_addCart() {
      let cartArr = [];
      let localCart = JSON.parse(getStore("buyCart"));
      console.log(localCart);

      if (localCart && localCart.length) {
        localCart.forEach(item => {
          cartArr.push({
            userId: getStore("id"),
            productId: item.productId,
            productNum: item.productNum
          });
        });
      }
      this.cart = cartArr;
    },
    submitForm(formName) {
      this.$refs[formName].validate(async valid => {
        if (valid) {
          //   获取用户名和密码
          // let {user,pass} = this.ruleForm;
          let res = await this.$http.post("/api/login", this.ruleForm);
          if (res.data.code === 200) {
            let { username, token, id } = res.data;
            //  持久化 存储
            setStore("token", token);
            setStore("id", id);
            console.log(this.cart);

            if (this.cart && this.cart.length) {
              this.cart.forEach(async item => {
                let res = await this.$http.post("/api/addCart", item);
                if (res.data.success === true) {
                  //.......
                }
                removeStore("buyCart");
                this.$router.push({ path: "/" });
              });
            } else {
              this.$router.push({path: "/"});
            }
          }
        } else {
          console.log("error submit!!");
          return false;
        }
      });
    }
  }
};
</script>

<style lang="scss" scoped>
.login {
  position: relative;
  overflow: visible;
  background: #ededed;
  .box {
    width: 450px;
    border: 1px solid #dadada;
    border-radius: 10px;
    position: absolute;
    top: 200px;
    left: 50%;
    padding: 50px 50px 50px 10px;
    margin-left: -225px;
    box-shadow: 0 9px 30px -6px rgba(0, 0, 0, 0.2),
      0 18px 20px -10px rgba(0, 0, 0, 0.04),
      0 18px 20px -10px rgba(0, 0, 0, 0.04),
      0 10px 20px -10px rgba(0, 0, 0, 0.04);
    text-align: center;
    form {
      margin-top: 30px;
    }
    span {
      color: #333;
      font-weight: 400;
    }
  }
}
</style>
views/Login/index.vue

-- 指定端口启动

// 使用vue-cli创建出来的vue工程, Webpack的配置是被隐藏起来了的
// 如果想覆盖Webpack中的默认配置,需要在项目的根路径下增加vue.config.js文件
 
module.exports={
    devServer:{
        port :8999, // 端口号的配置
        open:true // 自动打开浏览器
    }
}
vue.config.js

-- 搭建一个node服务器(express)

1 新建文件夹server ->2 新建app.js(启动文件)->3 npm init --yes-> 4 npm i express cors body-parse(安装需要的包) ->5 启动:node app.js   建议:(nodemon app.js(需要安装nodemon))

const express = require('express');
const app = express();
const fs = require('fs');

//分页
/**
 * 
 * @param {*当前页的数量} pageSize
 * @param {*当前页} currentPage 
 * @param {*当前数组} arr 
 * 
 * 总32条
 * 8
 * 1 2
 */
function pagination(pageSize, currentPage, arr) {
    let skipNum = (currentPage - 1) * pageSize;
    let newArr = (skipNum + pageSize >= arr.length) ? arr.slice(skipNum, arr.length) : arr.slice(skipNum, skipNum + pageSize);
    return newArr;
}

// 升序还是降序
/**
 * 
 * @param {*排序的属性} attr 
 * @param {*true表示升序排序 false表示降序排序} rev 
 */

function sortBy(attr, rev) {
    if (rev === undefined) {
        rev = 1;
    } else {
        rev = rev ? 1 : -1;
    }
    return function (a, b) {
        a = a[attr];
        b = b[attr];
        if (a < b) {
            return rev * -1;
        }
        if (a > b) {
            return rev * 1;
        }
        return 0;
    }
}
// 范围(价格区间)
function range(arr, gt, lte) {
    return arr.filter(item => item.salePrice >= gt && item.salePrice <= lte)
}

const cors = require('cors');
const jwt = require('jsonwebtoken')
const bodyParser = require('body-parser');
const cartListJSON = require('./db/cartList.json');

app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }))
app.get('/api/goods/home', (req, res) => {
    fs.readFile('./db/home.json', 'utf8', (err, data) => {
        if (!err) {
            res.json(JSON.parse(data));
        }
    })
})
app.get('/api/goods/allGoods', (req, res) => {
    // 获取的是前端地址栏上的查询参数
    const page = parseInt(req.query.page);
    const size = parseInt(req.query.size);
    const sort = parseInt(req.query.sort);
    const gt = parseInt(req.query.priceGt);
    const lte = parseInt(req.query.priceLte);
    const cid = req.query.cid;
    let newData = []
    fs.readFile('./db/allGoods.json', 'utf8', (err, data) => {
        let { result } = JSON.parse(data);
        let allData = result.data;
        // 分页显示
        newData = pagination(size, page, allData);
        if (cid === '1184') { //品牌周边
            newData = allData.filter((item) => item.productName.match(RegExp(/Smartisan/)))
            if (sort === 1) { //价格由低到高
                newData = newData.sort(sortBy('salePrice', true))
            } else if (sort === -1) { //价格由高到低
                newData = newData.sort(sortBy('salePrice', false))
            }
        } else {
            if (sort === 1) { //价格由低到高
                newData = newData.sort(sortBy('salePrice', true))
            } else if (sort === -1) { //价格由高到低
                newData = newData.sort(sortBy('salePrice', false))
            }
            if (gt && lte) {
                // 过滤 10~1000
                newData = range(newData, gt, lte)
            }
            // 32 

        }
        if (newData.length < size) {
            res.json({
                data: newData,
                total: newData.length
            })
        } else {
            res.json({
                data: newData,
                total: allData.length
            })
        }
    })
})
// 商品详情的数据
app.get('/api/goods/productDet', (req, res) => {
    const productId = req.query.productId;
    console.log(productId);
    fs.readFile('./db/goodsDetail.json', 'utf8', (err, data) => {
        if (!err) {
            let { result } = JSON.parse(data);
            let newData = result.find(item => item.productId == productId)
            res.json(newData)
        }
    })
})

// 模拟一个登陆的接口
app.post('/api/login', (req, res) => {
    console.log(req.body.user);
    // 登录成功获取用户名
    let username = req.body.user
    //一系列的操作
    res.json({
        // 进行加密的方法
        // sing 参数一:加密的对象 参数二:加密的规则 参数三:对象
        token: jwt.sign({ username: username }, 'abcd', {
            // 过期时间
            expiresIn: "3000s"
        }),
        username,
        state: 1,
        file: '/static/images/1570600179870.png',
        code: 200,
        address: null,
        balance: null,
        description: null,
        email: null,
        message: null,
        phone: null,
        points: null,
        sex: null,
        id: 62
    })
})

// 登录持久化验证接口 访问这个接口的时候 一定要访问token(前端页面每切换一次,就访问一下这个接口,问一下我有没有登录/登陆过期)
// 先访问登录接口,得到token,在访问这个,看是否成功
app.post('/api/validate', function (req, res) {
    let token = req.headers.authorization;
    console.log(token);

    // 验证token合法性 对token进行解码
    jwt.verify(token, 'abcd', function (err, decode) {
        if (err) {
            res.json({
                token:token,
                msg: '当前用户未登录'
            })
        } else {
            // 证明用户已经登录
            res.json({
                token: jwt.sign({ username: decode.username }, 'abcd', {
                    // 过期时间
                    expiresIn: "3000s"
                }),
                username: decode.username,
                msg: '已登录',
                address: null,
                balance: null,
                description: null,
                email: null,
                file: "/static/images/1570600179870.png",
                id: 62,
                message: null,
                phone: null,
                points: null,
                sex: null,
                state: 1,
            })
        }
    })
})


app.post('/api/addCart', (req, res) => {
    let { userId, productId, productNum } = req.body;
    fs.readFile('./db/allGoods.json', (err, data) => {
        let { result } = JSON.parse(data);
        if (productId && userId) {
            let { cartList } = cartListJSON.result.find(item => item.id == userId)
            // 找到对应的商品
            let newData = result.data.find(item => item.productId == productId);
            newData.limitNum = 100;

            let falg = true;
            if (cartList && cartList.length) {
                cartList.forEach(item => {
                    if (item.productId == productId) {
                        if (item.productNum >= 1) {
                            falg = false;
                            item.productNum += parseInt(productNum);
                        }
                    }
                })
            }
            if (!cartList.length || falg) {  //购物车为空
                newData.productNum = parseInt(productNum)
                cartList.push(newData);
            }

            // 序列化

            fs.writeFile('./db/cartList.json', JSON.stringify(cartListJSON), (err) => {
                if (!err) {
                    res.json({
                        code: 200,
                        message: "success",
                        result: 1,
                        success: true,
                        timestamp: 1571296313981,
                    })
                }
            })
        }

    })

})

app.post('/api/cartList', (req, res) => {
    let { userId } = req.body;
    fs.readFile('./db/cartList.json', (err, data) => {
        let { result } = JSON.parse(data);
        let newData = result.find(item => item.id == userId);
        res.json({
            code: 200,
            cartList: newData,
            success: true,
            message: 'success'
        })
    })
})

app.listen(3000);
app.js

 

点击下载xmall项目(gitee获取) 

[TOC]

### 课程目标

- 运用vue+vue-router+vuex+element-ui搭建网站
- 对项目进行需求分析和模块划分以及功能划分
- 实现首页+全部+品牌周边页面渲染
- 查看商品详情页制作、商品排序以及分页功能实现
- 使用token+jwt实现网站用户登录退出 (后台)
- 使用meta元信息实现路由权限控制
- 实现加入购物车、图片懒加载功能
- 实现数据持久化存储用户数据和购物车数据
- 项目优化以及如何打包上线整个流程

### 项目初始化

- `vue create xmall_front`

- 项目目录如下

  ![image-20191111095211353](assets/image-20191111095211353.png)

- ![image-20191111095850507](assets/image-20191111095850507.png)

```js
cd xmall_front
npm run server
//访问https://localhost:8080
```

效果如下:

![image-20191111100105022](assets/image-20191111100105022.png)

### 安装依赖

- 安装sass:`npm install -D sass-loader node-sass` 

- 安装element-ui第三方组件库:`vue add element `

![image-20191111101032460](assets/image-20191111101032460.png)

- 安装图片懒加载插件:`npm i vue-lazyload -S`
- 安装请求库:`npm i axios -S`

### 路由配置

```js
import Vue from 'vue'
import VueRouter from 'vue-router'

// 解决路由命名冲突的方法
const routerPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
  return routerPush.call(this, location).catch(error => error)
}

// 异步组件加载
const Index = () => import('@/views/Index');
const Login = () => import('@/views/Login');
const Home = () => import('@/views/Home');
const Goods = () => import('@/views/Goods');
const Thanks = () => import('@/views/Thanks');
const GoodsDetail = () => import('@/views/GoodsDetail');
const User = () => import('@/views/User');
// import Index from '@/views/Index'
// import Login from '@/views/Login'
// import Home from '@/views/Home'
// import Goods from '@/views/Goods'
// import Thanks from '@/views/Thanks'
// import GoodsDetail from '@/views/GoodsDetail'
// import User from '@/views/User';
Vue.use(VueRouter)

const routes = [

  {
    path: "/",
    redirect: '/home',
    name: 'home',
    component: Index,
    children: [
      {
        path: "home",
        component: Home
      },
      {
        path: "goods",
        component: Goods
      },
      {
        path: "thanks",
        component: Thanks
      },
      {
        path: 'goodsDetail',
        name: 'goodsDetail',
        component: GoodsDetail
      }
    ]
  },
  {
    path: '/login',
    name: "login",
    component: Login
  },
  {
    path:'/user',
    name:'user',
    component:User,
    meta:{
      // 需要守卫
      auth:true
    }
  }
 
  
]

const router = new VueRouter({
  mode: 'history',
  routes
})

export default router

```



### 组件模板样例

/components/Shelf.vue

```vue
<template>
  <div class="gray-box">
    <div class="title">
        <h2>{{title}}</h2>
    </div>
    <div>
        <!-- 具名插槽 -->
        <slot name='content'></slot>
    </div>
  </div>
</template>

<script>
export default {
    props:['title']
};
</script>

<style lang="scss" scoped>
.gray-box {
  position: relative;
  margin-bottom: 30px;
  overflow: hidden;
  background: #fff;
  border-radius: 8px;
  border: 1px solid #dcdcdc;
  border-color: rgba(0, 0, 0, 0.14);
  box-shadow: 0 3px 8px -6px rgba(0, 0, 0, 0.1);
  .title {
    padding-left: 30px;
    position: relative;
    z-index: 10;
    height: 60px;
    padding: 0 10px 0 24px;
    border-bottom: 1px solid #d4d4d4;
    border-radius: 8px 8px 0 0;
    box-shadow: rgba(0, 0, 0, 0.06) 0 1px 7px;
    background: #f3f3f3;
    background: -webkit-linear-gradient(#fbfbfb, #ececec);
    background: linear-gradient(#fbfbfb, #ececec);
    line-height: 60px;
    font-size: 18px;
    color: #333;
    display: flex;
    justify-content: space-between;
    align-items: center;
    h2 {
      font-size: 18px;
      font-weight: 400;
      color: #626262;
      display: inline-block;
    }
  }
}
</style>
```

/components/MallGoods.vue

```css
<template>
  <el-row class="good-item">
    <el-col>
      <el-card :body-style="{padding: 0}">
        <div class="good-img">
          <a>
            <img src="" alt>
          </a>
        </div>
        <h6 class="good-title">商品标题</h6>
        <h3 class="sub-title ellipsis">子标题</h3>
        <div class="good-price pr">
          <div class="ds pa">
            <a href>
              <el-button type="default" size="medium">查看详情</el-button>
            </a>
            <a href>
              <el-button type="primary" size="medium">加入购物车</el-button>
            </a>
          </div>
          <p>
            <span style="font-size:14px">¥</span>
            20.00
          </p>
        </div>
      </el-card>
    </el-col>
  </el-row>
</template>
<style lang="scss" scoped>
.good-img {
  display: flex;
  justify-content: center;
  a {
    display: block;
    img {
      margin: 50px auto 10px;
      width: 206px;
      height: 206px;
      display: block;
    }
  }
}
.good-price {
  margin: 15px 0;
  height: 30px;
  text-align: center;
  line-height: 30px;
  color: #d44d44;
  font-family: Arial;
  font-size: 18px;
  font-weight: 700;
  display: flex;
  justify-content: space-around;
  padding-bottom: 60px;
  a {
    margin-right: 5px;
  }
  .ds {
    display: none;
  }
}
.good-price:hover .ds {
  display: block;
}
.good-title {
  line-height: 1.2;
  font-size: 16px;
  color: #424242;
  margin: 0 auto;
  padding: 0 14px;
  text-align: center;
  overflow: hidden;
}
h3 {
  text-align: center;
  line-height: 1.2;
  font-size: 12px;
  color: #d0d0d0;
  padding: 10px;
}
.good-item {
  background: #fff;
  width: 25%;
  transition: all 0.5s;
  height: 410px;
  &:hover {
    transform: translateY(-3px);
    box-shadow: 1px 1px 20px #999;
    .good-price p {
      display: none;
    }
    .ds {
      display: flex;
    }
  }
}
.el-card {
  border: none;
}
</style>
```

/components/Shelf.vue

```vue
<template>
  <el-input-number v-model="num" @change="handleChange" :min="1" :max="10" label="描述文字"></el-input-number>
</template>

<script>
export default {
  data() {
    return {
      num: 1
    };
  },
  methods: {
    handleChange(value) {
      this.$emit('handleValue',value)
    }
  }
};
</script>

<style lang="scss" scoped>
</style>
```



/Goods/index.vue

```vue
<template>
  <div class="goods">
    <div class="nav">
      <div class="w">
        <a>标题</a>
        <div class="price-interval">
          <input type="number" class="input" placeholder="价格" v-model="min">
          <span style="margin: 0 5px">-</span>
          <input type="number" placeholder="价格" v-model="max">
          <el-button type="primary" size="small" style="margin-left: 10px;">确定</el-button>
        </div>
      </div>
    </div>
    <div>
      <div class="goods-box w">
        所有商品
      </div>
      <div class="w">
        分页
      </div>
    </div>
  </div>
</template>

<script>

export default {
  data() {
    return {
      max: "",
      min: "",
    };
  },
};
</script>

<style lang="scss" scoped>
@import "../../assets/style/mixin";
@import "../../assets/style/theme";

.nav {
  height: 60px;
  line-height: 60px;
  > div {
    display: flex;
    align-items: center;
    a {
      padding: 0 30px 0 0;
      height: 100%;
      @extend %block-center;
      font-size: 12px;
      color: #999;
      &.active {
        color: #5683ea;
      }
      &:hover {
        color: #5683ea;
      }
    }
    input {
      @include wh(80px, 30px);
      border: 1px solid #ccc;
    }
    input + input {
      margin-left: 10px;
    }
  }
  .price-interval {
    padding: 0 15px;
    @extend %block-center;
    input[type="number"] {
      border: 1px solid #ccc;
      text-align: center;
      background: none;
      border-radius: 5px;
    }
  }
}

.goods-box {
  overflow: hidden;
  > div {
    float: left;
    border: 1px solid #efefef;
  }
}

.no-info {
  padding: 100px 0;
  text-align: center;
  font-size: 30px;
  display: flex;
  flex-direction: column;
  .no-data {
    align-self: center;
  }
}

.img-item {
  display: flex;
  flex-direction: column;
}

.el-pagination {
  align-self: flex-end;
  margin: 3vw 10vw 2vw;
}

.section {
  padding-top: 8vw;
  margin-bottom: -5vw;
  width: 1218px;
  align-self: center;
}

.recommend {
  display: flex;
  > div {
    flex: 1;
    width: 25%;
  }
}
</style>
```

/GoodsDetails/index.vue

``` vue
<template>
  <div class="w store-content">
    <div class="gray-box">
      <div class="gallery-wrapper">
        <div class="gallery">
          <div class="thumbnail">
            <ul>
              <li>
                <img src=''>
              </li>
            </ul>
          </div>
          <div class="thumb">
            <div class="big">
              <img src=''>
            </div>
          </div>
        </div>
      </div>
      <!--右边-->
      <div class="banner">
        <div class="sku-custom-title">
          <h4>{{product.productName}}</h4>
          <h6>
            <span>{{product.subTitle}}</span>
            <span class="price">
              <em>¥</em>
              <i>{{product.salePrice.toFixed(2)}}</i>
            </span>
          </h6>
        </div>
        <div class="num">
          <span class="params-name">数量</span>
          <BuyNum @handlerValue="productNum"></BuyNum>
        </div>
        <div class="buy">
          <el-button
            type="primary"
          >加入购物车</el-button>
          <el-button type="danger">现在购买</el-button>
        </div>
      </div>
    </div>
    <!--产品信息-->
    <div class="item-info">
      
    </div>
  </div>
</template>

<script>
export default {
  name: "goodsDetails",
  
};
</script>

<style lang="scss" scoped>
@import "../../assets/style/mixin";

.store-content {
  clear: both;
  width: 1220px;
  min-height: 600px;
  padding: 0 0 25px;
  margin: 0 auto;
}

.gray-box {
  display: flex;
  padding: 60px;
  margin: 20px 0;
  .gallery-wrapper {
    .gallery {
      display: flex;
      width: 540px;
      .thumbnail {
        li:first-child {
          margin-top: 0px;
        }
        li {
          @include wh(80px);
          margin-top: 10px;
          padding: 12px;
          border: 1px solid #f0f0f0;
          border: 1px solid rgba(0, 0, 0, 0.06);
          border-radius: 5px;
          cursor: pointer;
          &.on {
            padding: 10px;
            border: 3px solid #ccc;
            border: 3px solid rgba(0, 0, 0, 0.2);
          }
          img {
            display: block;
            @include wh(100%);
          }
        }
      }
      .thumb {
        .big {
          margin-left: 20px;
        }
        img {
          display: block;
          @include wh(440px);
        }
      }
    }
  }
  // 右边
  .banner {
    width: 450px;
    margin-left: 10px;
    h4 {
      font-size: 24px;
      line-height: 1.25;
      color: #000;
      margin-bottom: 13px;
    }
    h6 {
      font-size: 14px;
      line-height: 1.5;
      color: #bdbdbd;
      display: flex;
      align-items: center;
      justify-content: space-between;
    }
    .sku-custom-title {
      overflow: hidden;
      padding: 8px 8px 18px 10px;
      position: relative;
    }
    .params-name {
      padding-right: 20px;
      font-size: 14px;
      color: #8d8d8d;
      line-height: 36px;
    }
    .num {
      padding: 29px 0 8px 10px;
      border-top: 1px solid #ebebeb;
      display: flex;
      align-items: center;
    }
    .buy {
      position: relative;
      border-top: 1px solid #ebebeb;
      padding: 30px 0 0 10px;
    }
  }
}

.item-info {
  .gray-box {
    padding: 0;
    display: block;
  }
  .img-item {
    width: 1220px;
    // padding: 1vw;
    text-align: center;
    img {
      width: 100%;
      height: auto;
      display: block;
    }
  }
}

.no-info {
  padding: 200px 0;
  text-align: center;
  font-size: 30px;
}

.price {
  display: block;
  color: #d44d44;
  font-weight: 700;
  font-size: 16px;
  line-height: 20px;
  text-align: right;
  i {
    padding-left: 2px;
    font-size: 24px;
  }
}
</style>
```

/User/index.vue

```vue
<template>
   <div class="layout-container">
    <m-header>
      <div slot="nav"></div>
    </m-header>
    <div class="w">
      <div class="content">
     
        
      </div>
    </div>


  </div>
</template>

<script>
import MHeader from '@/common/MHeader'
    export default {
        components: {
            MHeader,
        },
    }
</script>

<style lang="scss" scoped>
@import "../../assets/style/mixin";

  .w {
    padding-top: 40px;
  }

  .content {
    display: flex;
    height: 100%;
  }

  .account-sidebar {
    width: 210px;
    border-radius: 6px;
    .avatar {
      padding-top: 20px;
      border-radius: 10px;
      text-align: center;
      img {
        width: 168px;
        height: 168px;
      }
      h5 {
        font-size: 18px;
        line-height: 48px;
        font-weight: 700;
      }
    }
    .account-nav {
      padding-top: 15px;
      li {
        position: relative;
        height: 48px;
        border-top: 1px solid #EBEBEB;
        line-height: 48px;
        &:hover {
          a {
            position: relative;
            z-index: 1;
            height: 50px;
            background-color: #98AFEE;
            line-height: 50px;
            color: #FFF;
          }

        }
        a {
          display: block;
        }
        &.current {
          a {
            position: relative;
            z-index: 1;
            height: 50px;
            background-color: #98AFEE;
            line-height: 50px;
            color: #FFF;
          }
        }

      }
    }
  }

  .account-content {
    margin-left: 20px;
    flex: 1;
  }
</style>
```

/Login/inde.vue

```vue
<template>
  <div class="login">
    <div class="box">
      <span>使用账号 登录官网</span>
      <el-form
        :model="ruleForm"
        status-icon
        :rules="rules"
        ref="ruleForm"
        label-width="100px"
        class="demo-ruleForm"
      >
        <el-form-item label="账号" prop="user">
          <el-input type="text" v-model="ruleForm.user" autocomplete="off" placeholder="请输入账号"></el-input>
        </el-form-item>
        <el-form-item label="密码" prop="pass">
          <el-input type="password" v-model="ruleForm.pass" autocomplete="off" placeholder="请输入密码"></el-input>
        </el-form-item>
        <div class="geetest"></div>
        <el-form-item>
          <el-button type="primary" @click="submitForm('ruleForm')">登录</el-button>
          <el-button>返回</el-button>
        </el-form-item>
      </el-form>
    </div>
  </div>
</template>

<script>

export default {
  
};
</script>

<style lang="scss" scoped>
.login {
  position: relative;
  overflow: visible;
  background: #ededed;
  .box {
    width: 450px;
    border: 1px solid #dadada;
    border-radius: 10px;
    position: absolute;
    top: 200px;
    left: 50%;
    padding: 50px 50px 50px 10px;
    margin-left: -225px;
    box-shadow: 0 9px 30px -6px rgba(0, 0, 0, 0.2),
      0 18px 20px -10px rgba(0, 0, 0, 0.04),
      0 18px 20px -10px rgba(0, 0, 0, 0.04),
      0 10px 20px -10px rgba(0, 0, 0, 0.04);
    text-align: center;
    form {
      margin-top: 30px;
    }
    span {
      color: #333;
      font-weight: 400;
    }
  }
}
</style>
```

/utils/storage.js

```js
/**
 * 
 * @param {key} name 
 * @param {value} content 
 */
export const setStore = (name, content) => {
    if (!name) return;
    if (typeof content !== 'string') {
        content = JSON.stringify(content);
    }
    window.localStorage.setItem(name, content);
}
export const getStore = name => {
    if(!name)  return;
    return window.localStorage.getItem(name)
}
export const removeStore = name =>{
    if(!name) return  
    window.localStorage.removeItem(name)
}
```

### 仓库存储vuex

```js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)
import { setStore, getStore } from '@/utils/storage'

export default new Vuex.Store({
  state: {
    login: false,//是否登录
    userInfo: null,//用户信息
    cartList: [],//加入购物车商品
    showCart: false
  },
  mutations: {
    // 网页初始化时从本地缓存获取购物车数据
    INITBUYCART(state) {
      let initCart = getStore('buyCart');
      if (initCart) {
        state.cartList = JSON.parse(initCart)

      }
    },
    SHOWCART(state, { showCart }) {
      state.showCart = showCart;
    },
    ISLOGIN(state, info) {
      state.userInfo = info;
      state.login = true;
      setStore('userInfo', info);
    },
    ADDCART(state, { productId, salePrice, productName, productImageBig, productNum = 1 }) {
      let cart = state.cartList;
      let goods = {
        productId,
        salePrice,
        productName,
        productImageBig
      }
      let falg = true;
      if (cart.length) {
        cart.forEach(item => {
          if (item.productId === productId) {
            if (item.productNum >= 0) {
              falg = false;
              item.productNum += productNum;
            }
          }
        })
      }
      if (!cart.length || falg) {
        goods.productNum = productNum;
        cart.push(goods);
      }
      state.cartList = cart;
      setStore('buyCart', cart);
    }

  },
  actions: {
  },
  modules: {
  }
})
```

### 接口配置

请同学们自行封装对应的模块

### 上线部署

www.pyhonav.cn

阿里云买台服务器

**登录服务器**

`用户名:ssh root@123.206.16.61`

`密码:xxxx`

------

**安装node二进制文件**

`cd /tmp/`

`wget https://nodejs.org/download/release/v10.15.3/node-v10.15.3-linux-x64.tar.xz `

**解压node**

`xz -d node-v10.15.3-linux-x64.tar.xz ` :去除掉.xz后缀

`tar -xf node-v10.15.3-linux-x64.tar `

**配置环境变量**

```js
ln -s /opt/node-v10.15.3-linux-x64/bin/node  /usr/local/sbin/ 
ln -s /opt/node-v10.15.3-linux-x64/bin/npm  /usr/local/sbin/
```

**安装pm2进程管理工具**

```js
npm install pm2 -g
```



**部署Node后端**

- `git pull https://www.github.com/xiaomage/server`

  - 模拟本地文件上传到服务器
  - 本地终端运行:`scp ./server.zip root@123.206.16.61:/tmp`
  - 服务器终端运行:`unzip server.zip && cd server && npm install && pm2 start app.js`

  

**部署Vue前端项目**

前端打包文件:

```js
npm run build
```

- 本地终端运行:`scp ./dist.zip root@123.206.16.61:/tmp`

- 服务器终端运行:`unzip dist.zip`

**部署nginx**

找到nginx的安装目录

```js
//以我的服务器为例:nginx目录
cd /opt/ngx112/conf 
vim nginx.conf

//修改配置文件如下
 server {
        listen       80; //端口号
        server_name  www.pythonav.cn; //域名
        location / {
                 try_files $uri $uri/ /index.html;  #匹配所有的路由
                 root /tmp/dist; //填写前端的根目录
            index  index.html index.htm;
        }
    }
```

输入nginx的启动命令

```
nginx 第一次输入是启动
nginx -s reload  #平滑重启,重新读取配置文件,不重启进程
nginx -s stop
```



![image-20191118151652251](assets/image-20191118151652251.png)
文档说明
posted @ 2021-05-27 11:55  风hua  阅读(127)  评论(0编辑  收藏  举报