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?'真的':'假的'}} */ ```
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')
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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
<!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>
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')
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 // 同一个页面
<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>
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')
<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>
<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>
<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>
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;
<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>
点击查看更多文档(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')
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
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: { } })
<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>
<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>
补充
-- 持久化储存
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); }
<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>
-- 指定端口启动
// 使用vue-cli创建出来的vue工程, Webpack的配置是被隐藏起来了的 // 如果想覆盖Webpack中的默认配置,需要在项目的根路径下增加vue.config.js文件 module.exports={ devServer:{ port :8999, // 端口号的配置 open:true // 自动打开浏览器 } }
-- 搭建一个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);
点击下载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)
作者:华王
博客:https://www.cnblogs.com/huahuawang/