element-ui 安装
npm i element-ui -S
安装 css 之类的相关的依赖
npm install sass-loader@7.3.1 node-sass --save-dev
引入使用
标红色的部分是引入的代码. 在项目的 main.js 里面
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' import router from './router' import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' Vue.use(ElementUI) Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, components: {App}, template: '<App/>', render: h => h(App) })
简单的登录页面实现以及校验功能
主要是使用 element-ui 的form 组件功能的验证以及一些消息提示
<template> <el-form ref="form" :model="form" :rules="rules" class="login-box"> <h3 class="login-title">欢迎登录</h3> <el-form-item label="账号" prop="name"> <el-input type="text" aria-placeholder="请输入用户名" v-model="form.name"></el-input> </el-form-item> <el-form-item label="密码" prop="password"> <el-input type="password" aria-placeholder="请输入密码" v-model="form.password"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="submitForm('form')">登录</el-button> </el-form-item> </el-form> </template> <script> export default { name: "Login", data() { return { form: { name: "", password: "" }, rules: { name: [ {required: true, message: '请输入用户名称', trigger: 'blur'}, ], password: [ {required: true, message: '请输入密码', trigger: 'blur'}, ] } } }, methods: { submitForm(formName) { this.$refs[formName].validate((valid) => { if (valid) { this.$router.push("/main") } else { this.$message({ message: '用户名或者密码未填', type: 'warning' }); return false; } }); }, } } </script> <style scoped> .login-box { width: 350px; margin: 150px auto; border: 1px solid #DCDFE6; padding: 20px; border-radius: 5px; box-shadow: 0 0 30px #DCDFE6; } .login-title { text-align: center; } </style>
展示效果
校验失败的提示效果, 成功后就自动跳转到 /main 页面, 即首页页面的组件
首页简单实现
布局
基于官方的示例, 实现一个简单的左侧导航栏, 右侧内容展示, 头部展示一个title 的一个布局
相关代码
main.vue
main 组件还是用 element-ui 的相关方法来拼
<template> <div> <el-container> <el-aside width="200px"> <el-menu :default-openeds="['1']"> <el-submenu index="1"> <template slot="title"><i class="el-icon-message"></i>羊驼产品</template> <el-menu-item-group> <el-menu-item index="1-1"> <router-link to="/member/type">羊驼型号</router-link> </el-menu-item> <el-menu-item index="1-2"> <router-link to="/member/list">羊驼列表</router-link> </el-menu-item> </el-menu-item-group> </el-submenu> <el-submenu index="2"> <template slot="title"><i class="el-icon-message"></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>admin</span> </el-header> <el-main> <router-view/> </el-main> </el-container> </el-container> </div> </template> <script> export default { name: "Main" } </script> <style scoped> .el-header { background-color: #B3C0D1; color: #333; line-height: 60px; } .el-aside { color: #333; } </style>
子路由
index.js 子路由相关代码
/main 下的子路由用 children 来作为关键字传入
import Vue from 'vue' import Router from 'vue-router' import Login from "../views/Login"; import Main from "../views/Main"; import MemberList from "../views/Member/MemberList"; import MemberType from "../views/Member/MemberType"; Vue.use(Router) export default new Router({ routes: [ { path: '/login', name: 'Login', component: Login }, { path: '/main', name: 'Main', component: Main, children: [ { path: '/member/list', name: 'MemberList', component: MemberList }, { path: '/member/type', name: 'MemberType', component: MemberType } ] }, ] })
组件间参数传递方式
路径匹配
link to 的时候传递参数
<router-link to="/member/type/1">羊驼型号</router-link>
路由路径的时候
指定参数
{
path: '/member/type/:id',
name: 'MemberType',
component: MemberType
}
组件内参数的使用
通过 $route.params.id 进行调用
<template> <div>羊驼类型 ID={{$route.params.id}}</div> </template>
展示效果
porps 方式
link to 部分改为对象
name 固定参数传递的是跳转组件的 name
params 固定参数表示传入的参数, 格式为对象, 里面为具体传入的参数
<router-link :to="{name:'MemberType', params: {id: 3}}">羊驼型号</router-link>
路由的部分
添加一个 props 为 true
{
path: '/member/type/:id',
name: 'MemberType',
component: MemberType,
props: true
}
组件内参数的使用
声明出来一个 props , 然后模板里面就可以简单的直接用 参数了
<template> <div>羊驼类型 ID={{id}}</div> </template> <script> export default { name: "MemberType", props: ["id"] } </script>
展示效果
porps 方法内的使用
应用场景示例
这里登陆后的跳转时, 把登录后的用户名传递过来在首页显示出来, 之前是写死的 admin
代码
login 的跳转代码调整为对象
submitForm(formName) { this.$refs[formName].validate((valid) => { if (valid) { this.$router.push({name: "Main", params: {username: this.form.name}}) } else { this.$message({ message: '用户名或者密码未填', type: 'warning' }); return false; } }); },
路由里面打开 props
{ path: '/main/', name: 'Main', component: Main, props: true, children: [ { path: '/member/list', name: 'MemberList', component: MemberList }, { path: '/member/type/:id', name: 'MemberType', component: MemberType, props: true } ] },
main 组件里面接受使用
<span>{{username}}</span>
export default { name: "Main", props: ["username"] }
实现效果
login 的时候用户名为 yangtuo
登录后
路由重定向
main 需要接受一个 :username 的参数
{
path: '/main/:username',
name: 'Main',
....
路由设置, 将一个 username 的参数重定向传递给 main
{
path: '/remain/:username',
redirect: '/main/:username'
}
多加一个按钮来指向这个路径, 这里假设传递一个 admin123的参数
<el-menu-item index="1-3"> <router-link to="/goHome/admin123">回到首页</router-link> </el-menu-item>
展示效果
路由模式处理
默认的这种单文件之间的组件跳转的页面 url 里面会有个 #
相关处理
在路由里面的 mode 使用 历史模式即可去除
export default new Router({
mode: "history",
....
效果展示
统一 404 处理
代码
<template> <div> 假设一个很好看的 404 提示页面</div> </template> <script> export default { name: "NotFound" } </script> <style scoped> </style>
{
path: "*",
name: 'NotFound',
component: NotFound
}
效果展示
路由钩子函数进行异步请求
钩子函数的定义
所有的钩子函数
相关的调用过程可以用此代码调试
<!DOCTYPE html> <html> <head> <title></title> <script type="text/javascript" src="https://cdn.jsdelivr.net/vue/2.1.3/vue.js"></script> </head> <body> <div id="app"> <p>{{ message }}</p> </div> <script type="text/javascript"> var app = new Vue({ el: '#app', data: { message : "xuxiao is boy" }, beforeCreate: function () { console.group('beforeCreate 创建前状态===============》'); console.log("%c%s", "color:red" , "el : " + this.$el); //undefined console.log("%c%s", "color:red","data : " + this.$data); //undefined console.log("%c%s", "color:red","message: " + this.message) }, created: function () { console.group('created 创建完毕状态===============》'); console.log("%c%s", "color:red","el : " + this.$el); //undefined console.log("%c%s", "color:red","data : " + this.$data); //已被初始化 console.log("%c%s", "color:red","message: " + this.message); //已被初始化 }, beforeMount: function () { console.group('beforeMount 挂载前状态===============》'); console.log("%c%s", "color:red","el : " + (this.$el)); //已被初始化 console.log(this.$el); console.log("%c%s", "color:red","data : " + this.$data); //已被初始化 console.log("%c%s", "color:red","message: " + this.message); //已被初始化 }, mounted: function () { console.group('mounted 挂载结束状态===============》'); console.log("%c%s", "color:red","el : " + this.$el); //已被初始化 console.log(this.$el); console.log("%c%s", "color:red","data : " + this.$data); //已被初始化 console.log("%c%s", "color:red","message: " + this.message); //已被初始化 }, beforeUpdate: function () { console.group('beforeUpdate 更新前状态===============》'); console.log("%c%s", "color:red","el : " + this.$el); console.log(this.$el); console.log("%c%s", "color:red","data : " + this.$data); console.log("%c%s", "color:red","message: " + this.message); }, updated: function () { console.group('updated 更新完成状态===============》'); console.log("%c%s", "color:red","el : " + this.$el); console.log(this.$el); console.log("%c%s", "color:red","data : " + this.$data); console.log("%c%s", "color:red","message: " + this.message); }, beforeDestroy: function () { console.group('beforeDestroy 销毁前状态===============》'); console.log("%c%s", "color:red","el : " + this.$el); console.log(this.$el); console.log("%c%s", "color:red","data : " + this.$data); console.log("%c%s", "color:red","message: " + this.message); }, destroyed: function () { console.group('destroyed 销毁完成状态===============》'); console.log("%c%s", "color:red","el : " + this.$el); console.log(this.$el); console.log("%c%s", "color:red","data : " + this.$data); console.log("%c%s", "color:red","message: " + this.message) } }) </script> </body> </html>
钩子函数的参数 , to. from. next. 分别表示 要去的路由, 从那个路由来的, 以及控制路由的参数
export default { name: "MemberType", props: ["id"], beforeRouteEnter: (to, from, next) => { }, beforeRouteLeave: (to, from, next) => { }, }
axios 相关准备
异步请求需要 axios 安装
cnpm install axios -s
引用 axios
import axios from "axios";
Vue.prototype.axios = axios
配合钩子函数使用进行异步请求
后端准备
既然是异步请求则需要一个后端, 这里后端起一个 django 随便编写一些简单的数据进行回应
前后端的跨域问题让 django 的后端来处理. 从而让前段完全不需要操作好了
安装跨域相关处理的包
pip install django-cors-headers
添加进去 app中
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'user.apps.UserConfig', 'rest_framework', 'django_filters', 'coreapi', 'corsheaders' ]
然后注册中间件, 注意放的位置, 放错了就不会生效了
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
settings.py 里面对跨域的限制的相关配置
ALLOWED_HOSTS = ["*"] # 增加跨域忽略 CORS_ALLOW_CREDENTIALS = True CORS_ORIGIN_ALLOW_ALL = True CORS_ALLOW_METHODS = ( 'DELETE', 'GET', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'VIEW', ) CORS_ALLOW_HEADERS = ( 'XMLHttpRequest', 'X_FILENAME', 'accept-encoding', 'authorization', 'content-type', 'dnt', 'origin', 'user-agent', 'x-csrftoken', 'x-requested-with', )
前端部分
羊驼类型组件调整的时候, 会在页面渲染前来进行axios 请求数据
<template> <div>羊驼类型 ID={{id}}</div> </template> <script> export default { name: "MemberType", props: ["id"], beforeRouteEnter: (to, from, next) => { next(vm => { vm.getData() }) }, beforeRouteLeave: (to, from, next) => { }, methods: { getData: function () { this.axios({ method: 'get', url: 'http://127.0.0.1:8000/yangtuo/type/', }).then(function (repos) { console.log(repos) }).catch(function (error) { console.log(error) }) } } } </script> <style scoped> </style>
可以看到请求到的数据就在这里打印出来了
Vuex 解决状态问题
Vuex 是 vue 中的状态管理控制器, 用于集中管理所有组件的状态,
安装使用
cnpm install vuex --save
main.js
import Vuex from 'vuex'
Vue.use(Vuex)
应用场景
比如登录态的判断, 如果没登录访问任何页面都跳转到登录页进行登录
场景实现
基于 sessionStorage
首先, login的组件里面在登录成功之后在 session 里面设定一个 登录标识
submitForm(formName) { this.$refs[formName].validate((valid) => { if (valid) { sessionStorage.setItem('isLogin', 'true') this.$router.push({name: "Main", params: {username: this.form.name}}) } else { this.$message({ message: '用户名或者密码未填', type: 'warning' }); return false; } }); },
然后, 在main.js 里面调用钩子函数 beforeEach
相关的代码, 因为需要全局生效. 所以在 main.js 中
// 路由跳转前 router.beforeEach((to, from, next) => { let isLogin = sessionStorage.getItem('isLogin') console.log(isLogin) console.log(to.path) // 登出, 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
sessionStroage 只能简单的存储一些键值对, 而实际用户对象的存储则不方便
使用 Vuex 即可解决这个问题
配置
在 src 根目录下创建一个 store 文件来保存 Vuex 的配置文件 index.js 内容如下
import Vue from "vue" import Vuex from 'vuex' Vue.use(Vuex) // 全局的 state 对象, 用于保存所有组件的公共数据 const state = { user: { name: "" } } // 监听state 对象的值的最新状态 (计算属性) const getters = { getUser(state) { return state.user } } // 唯一一个可以修改 state 值得方法 (同步执行) const mutations = { updateUser(state, user) { state.user = user } } // 异步执行 mutations 方法 const actions = { asyncUpdateUser(context, user) { context.commit("updateUser", user) } } export default new Vuex.Store({ state, getters, mutations, actions })
注册
main.js 里面进行注册
import store from "./store"; new Vue({ el: '#app', router, store, components: {App}, template: '<App/>', render: h => h(App) })
使用
login 组件里面当登录成功时, 可以调用 dispatch 方法去调用 异步更新 state 里面的 user 对象
submitForm(formName) { this.$refs[formName].validate((valid) => { if (valid) { sessionStorage.setItem('isLogin', ''); this.$store.dispatch('asyncUpdateUser', {name: this.form.name}) this.$router.push({name: "Main", params: {username: this.form.name}}); } else { this.$message({ message: '用户名或者密码未填', type: 'warning' }); return false; } }); },
state 的内容使用
<span>{{$store.getters.getUser.name}}</span>
效果示例
登录成功后 右上角的这个登录用户的显示可以直接在 state 里面去取
刷新重置问题处理
但是目前存在一个问题, 就是刷新之后就没有了, 目前的示例都是在一个单页面上进行
每次刷新之后, state 就会恢复默认值
解决思路
可以监听刷新动作的发生, 然后一旦刷新就保存当前的 state 信息进 sessionStorage 中
以及 state 生成的时候页判断下 sessionStorage 中是不是已存在. 存在直接用即可
App.vue 里面加这两个
mounted() { window.addEventListener('unload', this.saveState); }, methods: { saveState() { sessionStorage.setItem('state', JSON.stringify(this.$store.state)); } }
声明的地方这样调整即可
const state = sessionStorage.getItem('state') ? JSON.parse(sessionStorage.getItem('state')) : { user: { name: "" } }
模板化
上述的示例是个单文件应用, 只用了一个 user 状态
如果存在很多的时候, 最好每个状态都分别用一个文件区分开. 进行模板化处理
以上示例进行封装的形式如下
在 store里面建立 modules 的文件夹下, 对 user 单独起一个 js
user.js
内容的变化就是放进了对象里面, 要做一些调整, 并且暴露出去
const user = { // 全局的 state 对象, 用于保存所有组件的公共数据 state : sessionStorage.getItem('state') ? JSON.parse(sessionStorage.getItem('state')) : { user: { name: "" } }, // 监听state 对象的值的最新状态 (计算属性) getters : { getUser(state) { return state.user } }, // 唯一一个可以修改 state 值得方法 (同步执行) mutations : { updateUser(state, user) { state.user = user } }, // 异步执行 mutations 方法 actions : { asyncUpdateUser(context, user) { context.commit("updateUser", user) } } } export default user
index.js
这里则需要对暴露出去的 user 进行引用
import Vue from "vue" import Vuex from 'vuex' import user from "./modules/user"; Vue.use(Vuex) export default new Vuex.Store({ modules: { user } })
使用上没有变化
只有原生使用的时候需要调整到 去 user 对象中, (为解决刷新问题的时候使用的)
之前是直接存进去的是 state 中, 现在是放在 user 里面了.
App.vue
<template> <div id="app"> <router-view/> </div> </template> <script> export default { name: 'App', mounted() { window.addEventListener('unload', this.saveState); }, methods: { saveState() { // sessionStorage.setItem('state', JSON.stringify(this.$store.state)); sessionStorage.setItem('state', JSON.stringify(this.$store.state.user)); } } } </script> <style> </style>
--------------------orz 竣工