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;
width: 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>
启动项目,访问
项目运行流程
执行npm run dev,执行代码package.json中scripts的dev
找到webpack.dev.conf.js(配置index.html文件),加载启动
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、组件传递参数(通过路由)
取路由参数的方法:
- a、通过在路由上添加key/value串使用this.$route.query来取参数,例如:/router1?id=123 ,/router1?id=456 可以通过this.$route.query.id获取参数id的值。
- b、通过将参数作为路由一部分进行传参数使用this.$route.params来获取,例如:定义的路由为/router1/:id ,请 求/router1/123时可以通过this.$route.params.id来获取,此种情况用this.$route.query.id是拿不到的。
使用路径参数
方式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));
}
}
}