1、创建工程
vue init webpack hello-vue
安装依赖
# 进入工程目录 cd hello-vue # 安装 vue-router npm install vue-router --save-dev # 安装 element-ui(在vue项目中) npm i element-ui -S # 安装 SASS 加载器 npm install sass-loader node-sass --save-dev # 安装依赖 npm install
NPM 相关命令说明
npm install moduleName
:安装模块到项目目录下npm install -g moduleName
:-g 的意思是将模块安装到全局,具体安装到磁盘哪个位置,要看 npm config prefix 的位置npm install -save moduleName
:--save 的意思是将模块安装到项目目录下,并在 package 文件的 dependencies 节点写入依赖,-S
为该命令的缩写npm install -save-dev moduleName
:--save-dev 的意思是将模块安装到项目目录下,并在 package 文件的 devDependencies 节点写入依赖,-D
为该命令的缩写
启动工程
npm run dev
第一个 ElementUI 页面 (登录页)
主流框架
iView
iview 是一个强大的基于 Vue 的 UI 库,有很多实用的基础组件比 elementui 的组件更丰富,主要服务于 PC 界面的中后台产品。使用单文件的 Vue 组件化开发模式 基于 npm + webpack + babel 开发,支持 ES2015 高质量、功能丰富 友好的 API ,自由灵活地使用空间。
备注:属于前端主流框架,选型时可考虑使用,主要特点是移动端支持较多
ElementUI
Element 是饿了么前端开源维护的 Vue UI 组件库,组件齐全,基本涵盖后台所需的所有组件,文档讲解详细,例子也很丰富。主要用于开发 PC 端的页面,是一个质量比较高的 Vue UI 组件库。
备注:属于前端主流框架,选型时可考虑使用,主要特点是桌面端支持较多
创建视图
创建首页视图
在 views
目录下创建一个名为 Main.vue
的视图组件;该组件在当前章节无任何作用,主要用于登录后展示登录成功的跳转效果;
<template> <div> 首页 </div> </template> <script> export default { name: "Main" } </script> <style scoped> //表示样式是这个组件私有的 </style>
创建登录页视图
在 views
目录下创建一个名为 Login.vue
的视图组件,其中 el-*
的元素为 ElementUI 组件;
<template> <div> <el-form ref="loginForm" :model="form" :rules="rules" label-width="80px" class="login-box"> <h3 class="login-title">欢迎登录</h3> <el-form-item label="账号" prop="username"> <el-input type="text" placeholder="请输入账号" v-model="form.username"/> </el-form-item> <el-form-item label="密码" prop="password"> <el-input type="password" placeholder="请输入密码" v-model="form.password"/> </el-form-item> <el-form-item> <el-button type="primary" v-on:click="onSubmit('loginForm')">登录</el-button> </el-form-item> </el-form> <el-dialog title="温馨提示" :visible.sync="dialogVisible" width="30%" :before-close="handleClose"> <span>请输入账号和密码</span> <span slot="footer" class="dialog-footer"> <el-button type="primary" @click="dialogVisible = false">确 定</el-button> </span> </el-dialog> </div> </template> <script> export default { name: "Login", data() { return { form: { username: '', password: '' }, // 表单验证,需要在 el-form-item 元素中增加 prop 属性 rules: { username: [ {required: true, message: '账号不可为空', trigger: 'blur'} ], password: [ {required: true, message: '密码不可为空', trigger: 'blur'} ] }, // 对话框显示和隐藏 dialogVisible: false } }, methods: { onSubmit(formName) { // 为表单绑定验证功能 this.$refs[formName].validate((valid) => { if (valid) { // 使用 vue-router 路由到指定页面,该方式称之为编程式导航 this.$router.push("/main"); } else { this.dialogVisible = true; return false; } }); } } } </script> <style lang="scss" scoped> .login-box { border: 1px solid #DCDFE6; 350px; margin: 180px auto; padding: 35px 35px 15px 35px; border-radius: 5px; -webkit-border-radius: 5px; -moz-border-radius: 5px; box-shadow: 0 0 25px #909399; } .login-title { text-align: center; margin: 0 auto 40px auto; color: #303133; } </style>
创建路由
在 router
目录下创建一个名为 index.js
的 vue-router 路由配置文件
import Vue from 'vue' import Router from 'vue-router' import Login from "../views/Login" import Main from '../views/Main' Vue.use(Router); export default new Router({ routes: [ { // 登录页 path: '/login', name: 'Login', component: Login }, { // 首页 path: '/main', name: 'Main', component: Main } ] });
配置路由
修改入口代码
修改 main.js
入口代码
import Vue from 'vue' import VueRouter from 'vue-router' import router from './router' // 导入 ElementUI import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' import App from './App' // 安装路由 Vue.use(VueRouter); // 安装 ElementUI Vue.use(ElementUI); new Vue({ el: '#app', // 启用路由 router, // 启用 ElementUI render: h => h(App) });
修改 App.vue
组件代码
<template> <div id="app"> <router-view/> </div> </template> <script> export default { name: 'App', } </script>
2、配置嵌套路由
什么是嵌套路由
嵌套路由又称子路由,在实际应用中,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件。
一般的我们使用路由跳转,从一个页面跳转到另一个页面,此时这两个页面的布局就没有任何联系。
而我们使用嵌套路由,当组件A,点击链接跳转组件B,只是改变了页面A的部分内容,用组件B插入到页面A的某个地方
创建嵌套视图组件
用户信息组件
在 views/user
目录下创建一个名为 Profile.vue
的视图组件;该组件在当前章节无任何作用,主要用于展示嵌套效果;
<template> <div> 个人信息 </div> </template> <script> export default { name: "UserProfile" } </script> <style scoped> </style>
用户列表组件
在 views/user
目录下创建一个名为 List.vue
的视图组件;该组件在当前章节无任何作用,主要用于展示嵌套效果;
<template> <div> 用户列表 </div> </template> <script> export default { name: "UserList" } </script> <style scoped> </style>
配置嵌套路由
修改 router
目录下的 index.js
路由配置文件,代码如下:
import Vue from 'vue' import Router from 'vue-router' import Login from "../views/Login" import Main from '../views/Main' // 用于嵌套的路由组件 import UserProfile from '../views/user/Profile' import UserList from '../views/user/List' Vue.use(Router); export default new Router({ routes: [ { // 登录页 path: '/login', name: 'Login', component: Login }, { // 首页 path: '/main', name: 'Main', component: Main, // 配置嵌套路由,点击不同的链接,下面这些组件,就会添加到Main组件的<router-view>中 children: [ {path: '/user/profile', component: UserProfile}, {path: '/user/list', component: UserList}, ] } ] });
说明:主要在路由配置中增加了 children
数组配置,用于在该组件下设置嵌套路由
修改首页视图
接着上一节的代码,我们修改 Main.vue
视图组件,此处使用了 ElementUI 布局容器组件,代码如下:
<template> <div> <el-container> <el-aside width="200px"> <el-menu :default-openeds="['1']"> <el-submenu index="1"> <template slot="title"><i class="el-icon-caret-right"></i>用户管理</template> <el-menu-item-group> <el-menu-item index="1-1"> <router-link to="/user/profile">个人信息</router-link> </el-menu-item> <el-menu-item index="1-2"> <router-link to="/user/list">用户列表</router-link> </el-menu-item> </el-menu-item-group> </el-submenu> <el-submenu index="2"> <template slot="title"><i class="el-icon-caret-right"></i>内容管理</template> <el-menu-item-group> <el-menu-item index="2-1">分类管理</el-menu-item> <el-menu-item index="2-2">内容列表</el-menu-item> </el-menu-item-group> </el-submenu> </el-menu> </el-aside> <el-container> <el-header style="text-align: right; font-size: 12px"> <el-dropdown> <i class="el-icon-setting" style="margin-right: 15px"></i> <el-dropdown-menu slot="dropdown"> <el-dropdown-item>个人信息</el-dropdown-item> <el-dropdown-item>退出登录</el-dropdown-item> </el-dropdown-menu> </el-dropdown> <span>Lusifer</span> </el-header> <el-main> <router-view /> </el-main> </el-container> </el-container> </div> </template> <script> export default { name: "Main" } </script> <style scoped lang="scss"> .el-header { background-color: #B3C0D1; color: #333; line-height: 60px; } .el-aside { color: #333; } </style>
说明:
- 在
<el-main>
元素中配置了<router-view />
用于展示嵌套路由 - 主要使用
<router-link to="/user/profile">个人信息</router-link>
展示嵌套路由内容
3、组件传递参数(通过路由)
使用路径参数
方式1
修改路由配置
{path: '/user/profile/:id',component: UserProfile}
- 说明:主要是在
path
属性中增加了:id
这样的占位符
template
<router-link to="/user/profile/1">个人信息</router-link>
profile组件中获取
页面获取 {{$route.params.id}} js获取 this.$route.params.id
方式2
修改路由配置
{path: '/user/profile/:id', name:'UserProfile', component: UserProfile}
- 给组件设置名字设置 name,可以用于传递参数
template
<router-link :to="{name: 'UserProfile', params: {id: 1}}">个人信息</router-link>
在profile组件中获取
页面获取 {{$route.params.id}} js获取 this.$route.params.id
使用 props
的方式
修改路由配置
{path: '/user/profile/:id', name:'UserProfile', component: UserProfile, props: true}
说明:主要增加了 props: true
属性
传递参数
上面的方式1和方式2的传递都可以;
接收参数
js
export default { props: ['id'], name: "UserProfile" }
获取参数
页面获取 {{ id }}
通过js传递参数
比如通过登录页面登录成功后,登录到首页,将用户名传递到首页
1、配置路由
routes: [ { path: '/main/:username', //修改 name: 'Main', component:Main, children: [ {path: '/user/profile/:id', name:'UserProfile', component: UserProfile,props:true}, {path: '/user/list', component: UserList}, ] }, { path: '/login', name: 'login', component:Login } ]
登录组件点击事件
methods: { onSubmit(x) { this.$router.push({"name":"Main",params:{username:"小明"}}); this.$router.push({path: "/studentSelectCourse",query:{userCode:row.userCode}}); } }
VUE首页
{{$route.params.username}} //获取的是/main/:username {{$route.query.name}} //获取的是/main/xx?name=xx
3、父组件修改子组件的数据
方式:组件间传递数据(通过组件嵌套)
注意传递值和传递引用的区别,我们传递的数组,对象都是传递的引用
父组件:在调用后面传递参数
<template> <div> <Password :myusers = users></Password> <Password myusers = users></Password> <!--如果不绑定属性,这种传递的就是一个单纯的字符串--> </div> </template> <script lang="ts"> import {Component,Vue} from "vue-property-decorator" import Password from "@/views/Password/Password.vue" @Component({ components:{ Password }, data(){ return{ users:["z","x"] } } }) export default class Home extends Vue{ } </script> <style scoped> </style>
子组件:使用props接受参数,props的用法和data中的return效果一样
props的更多设置,例如对传递的参数进行设置 , props:{myusers:{required:true,type:Array}},
<template> <div class="password"> password <ul> <li v-for="(item,index) in myusers" :key="index">{{item}}</li> </ul> </div> </template> <script lang="ts"> import {Component,Vue} from "vue-property-decorator" @Component({ components:{}, props:["myusers"], data(){ return{} } }) export default class Password extends Vue{ } </script> <style scoped> </style>
3、子组件修改父组件的数据
方式:注册事件
子组件接受了父组件传递来的一个值title,我们对这个值进行修改,虽然不会影响到父组件的title值,但是在浏览器中会报一个warn
<template> <div class="password"> {{title}} <button @click="title='new 标题'">修改title</button> </div> </template> <script lang="ts"> import {Component,Vue} from "vue-property-decorator" @Component({ components:{}, props:["title"], data(){ return{ } } }) export default class Password extends Vue{ } </script> <style scoped> </style>
如果希望子组件可以修改父组件的数据(修改Prop中的数据)
父组件中调用子组件中绑定一个事件
<template> <div> <header>{{title}}</header> <!--在子组件(Password)中绑定事件--> <Password @ChangeTitle="ChangeTitle"></Password> </div> </template> <script lang="ts"> import {Component,Vue} from "vue-property-decorator" import Password from "@/views/Password/Password.vue" @Component({ components:{ Password }, data(){ return{ title:"标题" } }, methods:{ ChangeTitle(arg){ this.$data.title=arg } } }) export default class Home extends Vue{ } </script> <style scoped> </style>
子组件通过某一个方法可以注册事件
<template> <div class="password"> <button @click="updateTitle">修改title</button> </div> </template> <script lang="ts"> import {Component,Vue} from "vue-property-decorator" @Component({ components:{}, data(){ return{ } }, methods:{ updateTitle(){ console.log("emit") //注册事件 //第一个参数:事件的名字(比如我们的点击事件@click),第二个参数:传递的值,使用","在后面添加多个参数 this.$emit("ChangeTitle","new 标题") } } }) export default class Password extends Vue{ } </script> <style scoped> </style>
4、组件重定向
不同的路径访问同一个组件
配置重定向
修改路由配置
{ path: '/main', name: 'Main', component: Main }, { path: '/goHome', redirect: '/main' }
说明:这里定义了两个路径,一个是 /main
,一个是 /goHome
,其中 /goHome
重定向到了 /main
路径,由此可以看出重定向不需要定义组件;
重定向到组件
设置对应路径即可
<router-link to="/goHome">回到首页</router-link>
带参数的重定向
修改路由配置
{ // 首页 path: '/main/:username', name: 'Main', component: Main }, { path: '/goHome/:username', redirect: '/main/:username' }
重定向到组件
<router-link to="/goHome/Lusifer">回到首页</router-link>
5、路由模式与 404
路由模式
路由模式有两种
- hash:路径带
#
符号,如http://localhost/#/login
- history:路径不带
#
符号,如http://localhost/login
修改路由配置,代码如下:
export default new Router({ mode: 'history', routes: [ ] });
处理 404
创建一个名为 NotFound.vue
的视图组件,代码如下:
<template> <div> 页面不存在,请重试! </div> </template> <script> export default { name: "NotFount" } </script> <style scoped> </style>
修改路由配置,代码如下:
{ path: '*', component: NotFound }
6、路由钩子与异步请求
路由中的钩子函数
beforeRouteEnter
:在进入路由前执行beforeRouteLeave
:在离开路由前执行
案例代码如下:
export default { props: ['id'], name: "UserProfile", beforeRouteEnter: (to, from, next) => { console.log("进入个人信息页"); next(); }, beforeRouteLeave: (to, from, next) => { console.log("离开个人信息页"); next(); } }
参数说明:
to
:路由将要跳转的路径信息from
:路径跳转前的路径信息next
:路由的控制参数next()
跳入下一个页面next('/path')
改变路由的跳转方向,使其跳到另一个路由next(false)
返回原来的页面next((vm)=>{})
仅在 beforeRouteEnter 中可用,vm 是组件实例
在钩子函数中使用异步请求
安装 Axios
npm install axios -s
引用 Axios(修改main.js)
import axios from 'axios' Vue.prototype.axios = axios;
在 beforeRouteEnter
中进行异步请求,案例代码如下:
export default { props: ['id'], name: "UserProfile", beforeRouteEnter: (to, from, next) => { console.log("准备进入个人信息页"); // 注意,一定要在 next 中请求,因为该方法调用时 Vue 实例还没有创建,此时无法获取到 this 对象,在这里使用官方提供的回调函数拿到当前实例 next(vm => { vm.getData(); //调用getData函数 }); }, beforeRouteLeave: (to, from, next) => { console.log("准备离开个人信息页"); next(); }, methods: { getData: function () { this.axios({ method: 'get', url: 'http://localhost:8080/data.json' }).then(function (repos) { console.log(repos); }).catch(function (error) { console.log(error); }); } } }
Vuex 快速入门
我们后台开发页面,通过session来存储用户信息,判断用户是否登录,前端如何保存呢?
简介
Vuex 是一个专为 Vue.js 应用程序开发的 状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
目标
继续之前 vue-router 章节做的案例项目,我们通过完善登录功能将用户信息保存至 Vuex 中来体会它的作用;
安装
在项目根目录执行如下命令来安装 Vuex
npm install vuex--save //如果安装不上,使用cnpm
我们利用路由钩子 beforeEach
来判断用户是否登录,期间会用到 sessionStorage
存储功能
登录成功后设置登录状态
修改 Login.vue,当用户登录成功后,跳转登录页面前,存储用户的状态
在表单验证成功方法内增加如下代码:
methods: { onSubmit(formName) { // 为表单绑定验证功能 this.$refs[formName].validate((valid) => { if (valid) { // 设置用户登录成功 sessionStorage.setItem('isLogin', 'true'); // 使用 vue-router 路由到指定页面,该方式称之为编程式导航 this.$router.push("/main"); } else { this.dialogVisible = true; return false; } }); } }
判断用户是否登录/以及注销功能
修改 main.js(充当拦截器)
利用路由钩子 beforeEach
方法判断用户是否成功登录,关键代码如下:
import router from './router' // 在跳转前执行 router.beforeEach((to, form, next) => { //拦截上面配置的路由 // 获取用户登录状态 let isLogin = sessionStorage.getItem('isLogin'); // 注销 if (to.path == '/logout') { // 清空 sessionStorage.clear(); // 跳转到登录 next({path: '/login'}); } // 如果请求的是登录页 else if (to.path == '/login') { if (isLogin != null) { // 跳转到首页 next({path: '/main'}); } } // 如果为非登录状态 else if (isLogin == null) { // 跳转到登录页 next({path: '/login'}); } // 下一个路由 next(); });
使用vuex存储用户登录信息
配置 vuex
创建 Vuex 配置文件
在 src
目录下创建一个名为 store
的目录并新建一个名为 index.js
文件用来配置 Vuex,代码如下:
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex); // 全局 state 对象,用于保存所有组件的公共数据 const state = { // 定义一个 user 对象 // 在组件中是通过 this.$store.state.user 来获取 user: { username: '' } }; //实时监听 state 值的最新状态,注意这里的 getters 可以理解为计算属性 const getters = { // 在组件中是通过 this.$store.getters.getUser 来获取,页面上使用{{$store.getters.getUser.username}}来获取 getUser(state) { return state.user; } }; // 定义改变 state 初始值的方法,这里是唯一可以改变 state 的地方,缺点是只能同步执行 const mutations = { // 在组件中是通过 this.$store.commit('updateUser', user); 方法来调用 mutations updateUser(state, user) { state.user = user; } }; // 定义触发 mutations 里函数的方法,可以异步执行 mutations 里的函数 const actions = { // 在组件中是通过 this.$store.dispatch('asyncUpdateUser', user); 来调用 actions asyncUpdateUser(context, user) { context.commit('updateUser', user); } }; export default new Vuex.Store({ state, getters, mutations, actions });
修改 main.js
增加刚才配置的 store/index.js
,关键代码如下:
import Vue from 'vue' import Vuex from 'vuex' import store from './store' Vue.use(Vuex); new Vue({ el: '#app', store });
解决浏览器刷新后 Vuex 数据消失问题
问题描述
Vuex 的状态存储是响应式的,当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。但是有一个问题就是:vuex 的存储的数据只是在页面的中,相当于我们定义的全局变量,刷新之后,里边的数据就会恢复到初始化状态。但是这个情况有时候并不是我们所希望的。
解决方案
监听页面是否刷新,如果页面刷新了,将 state 对象存入到 sessionStorage 中。页面打开之后,判断 sessionStorage 中是否存在 state 对象,如果存在,则说明页面是被刷新过的,将 sessionStorage 中存的数据取出来给 vuex 中的 state 赋值。如果不存在,说明是第一次打开,则取 vuex 中定义的 state 初始值。
修改代码
在 App.vue
中增加监听刷新事件
export default { name: 'App', mounted() { window.addEventListener('unload', this.saveState); }, methods: { saveState() { sessionStorage.setItem('state', JSON.stringify(this.$store.state)); } } }
修改 store/index.js
中的 state
const state = sessionStorage.getItem('state') ? JSON.parse(sessionStorage.getItem('state')) : { user: { username: '' } };
模块化
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
创建 user
模块
在 store
目录下创建一个名为 modules
的目录并创建一个名为 user.js
的文件,代码如下:
const user = { // 因为模块化了,所以解决刷新问题的代码需要改造一下 state: sessionStorage.getItem('userState') ? JSON.parse(sessionStorage.getItem('userState')) : { user: { username: '' } }, getters: { getUser(state) { return state.user; } }, mutations: { updateUser(state, user) { state.user = user; } }, actions: { asyncUpdateUser(context, user) { context.commit('updateUser', user); } } }; export default user;
修改 store/index.js
import Vue from 'vue' import Vuex from 'vuex' import user from './modules/user' Vue.use(Vuex); export default new Vuex.Store({ modules: { // this.$store.state.user user } });
备注:由于组件中使用的是 getters
和 actions
处理,所以调用代码不变
修改 App.vue
export default { name: 'App', mounted() { window.addEventListener('unload', this.saveState); }, methods: { saveState() { // 模块化后,调用 state 的代码修改为 this.$store.state.user(需要定位到user这个对象) sessionStorage.setItem('userState', JSON.stringify(this.$store.state.user)); } } }