Vue系列教程(三)之vue-cli脚手架的使用
阅读原文时间:2023年07月08日阅读:1

一、Vue-cli的环境准备

目的:(1)快速管理依赖 (2)确定项目结构

1.安装node.js

Node.js是一个可以让前端运行在服务器上的一个工。

下载:https://nodejs.org/zh-cn/

测试node.js是否安装成功:cmd下输入node -v

npm config set prefix F:\node\node_global
npm config set cache F:\node\node_cache

2. 使用npm安装vue-cli

npm install -g @vue/cli 3.x
npm install –g vue-cli 2.x
npm: node.js包管理工具
install: 安装
vue-cli:要安装的工具
-g:全局安装

3. 配置使用国内源

npm配置国内镜像

npm config set registry https://registry.npm.taobao.org
npm config set disturl https://npm.taobao.org/dist
npm config set electron_mirror https://npm.taobao.org/mirrors/electron/

npm查看当前镜像

npm config get registry

yarn配置国内镜像

yarn config set registry https://registry.npm.taobao.org
yarn config set disturl https://npm.taobao.org/dist
yarn config set electron_mirror https://npm.taobao.org/mirrors/electron/

yarn查看当前镜像

yarn get registry

二、使用vue-cli下载项目骨架搭建项目

  • vue create 项目名称

  • 安装项目依赖  npm install

    注:3.x版本之后创建项目时就会自动安装

  • ### 使用开发模式运行项目

npm run dev  2.x

npm run serve   3.x

此时可以在浏览器中:http://localhost:8080 访问首页

三、Vue组件的组成部分

.1. vue文件的组成部分

2.在vue组件中使用多个vue组件搭建一个页面

  • 全局注册

import Vue from 'vue'
import App from './App.vue'
import Header from "./components/Header";
import Content from "./components/Content";
import Footer from "./components/Footer";

Vue.component('MyHeader', Header);
Vue.component('MyContent', Content);
Vue.component('MyFooter', Footer);

Vue.config.productionTip = false;

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

  • 本地注册

在组件的内部注册另外一个组件,成为一个标签,这个标签只在该组件内部使用,而不能在其他组件内部使用。

四、Vue组件之间的参数传递

  • 父传子

App.vue


export default {
name: 'App',
components: {
'MyHeader': Header,
'MyContent': Content,
'MyFooter': Footer,
},
data(){
return {
title: "Hello Vue!"
}
}
}

 content.vue

通过子组件的props属性,来指明可以接收的参数,父组件通过在标签中写明参数的键值对来传递参数。

props表示一个组件的参数部分,有两种写法:

(1)props:[参数列表]

比如:props:[‘MyProp1’,’MyProp2’]

(2)props:[参数名1:{type:String,required:true,default:”xx”},参数名2:{}]

  • 子传父

App.vue

content.vue

  • 以事件发射的方式子传父

子组件中,使用this.$emit(‘键', ‘值’)

父组件中,在子组件标签中使用@键=”msg=$event”

Content.vue

doClick(){
this.$emit("newName", "hello js");
}
}

App.vue

@newName="msg=$event">

五、Vue使用Axios

  • 安装axios

npm install --save axios vue-axios

  • 在main.js使用axios

import Vue from 'vue'
import axios from 'axios'
import VueAxios from 'vue-axios'
Vue.use(VueAxios, axios)

import Vue from 'vue'
import axios from 'axios'
Vue.prototype.axios = axios

1. get请求

方式一
this.axios({
method:'get',
url:'http://127.0.0.1:8000/register',
params:{
"mail": this.mail,
"password": this.password,
},
}).then(function (response) {
console.log(response.data);
});
方式二
const { data: res, status: status } = await this.axios.get('users/getUserList', {
params: this.queryInfo
});
if (status !== 200) {
return this.$message.error('获取用户列表失败!')
}
this.userlist = res.data.users;
this.total = res.data.total

2. post请求

发送post请求: 解决axios无法传递data中的参数问题

默认情况下发送axios时请求头中的内容类型为:

Content-Type:application/json;charset=UTF-8

而如果服务端需要的是:

Content-Type:application/x-www-form-urlencoded

因此,使用axios的qs内置库中的方法进行内容类型的转换。

import Qs from 'qs'
this.axios({
method:'post',
url:'http://localhost:8081/regist',
transformRequest: [function (data) {
return Qs.stringify(data)
}],
data:{
email:this.email
}
}).then(function (response) {
alert(response.data.message)
})
方式二
const { data: res, status: status } = await this.axios.post('users/addOneUser', user_form);
if (status !== 200) {
this.$message.error('添加用户失败!')
}
this.$message.success('添加用户成功!')
this.UserDialogVisible = false;
this.getUserList()

 3.pu

const { data: res, status: status } = await this.axios.put('users/updateUser', user_form);
if (status !== 200) {
this.$message.error('更新用户信息失败!')
}
// 隐藏添加用户对话框
this.UserDialogVisible = false;
this.$message.success('更新用户信息成功!')
this.getUserList()

 4.delete

const { data: res, status: status } = await this.axios.delete('users/deleteUser', {
params:{"id": id},
});
if (status !== 200) return this.$message.error('删除用户失败!')
this.$message.success('删除用户成功!');
this.getUserList()

5. axios拦截器

axios.interceptors.request.use(config => {
NProgress.start()
config.headers.Authorization = Storage.sessionGet("token");
return config
})

axios.interceptors.response.use( response => {
NProgress.done()
if (response.status >= 250 && response.status <= 260) { Storage.sessionRemove('token') // 删除已经失效或过期的token(不删除也可以,因为登录后覆盖) router.replace({path: '/Login'}).catch(err => {err})
} else if (response.data.token) {
Storage.sessionSet('token', response.data.token)
}
return response
})

六、解决跨域请求问题

1) 什么是跨域?

跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器施加的安全限制。

所谓同源是指,域名,协议,端口均相同,只要有一个不同,就是跨域。不明白没关系,举个栗子:

http://www.123.com/index.html 调用 http://www.123.com/server.php (非跨域)
http://www.123.com/index.html 调用 http://www.456.com/server.php (主域名不同:123/456,跨域)
http://abc.123.com/index.html 调用 http://def.123.com/server.php (子域名不同:abc/def,跨域)
http://www.123.com:8080/index.html 调用 http://www.123.com:8081/server.php (端口不同:8080/8081,跨域)
http://www.123.com/index.html 调用 https://www.123.com/server.php (协议不同:http/https,跨域)
请注意:localhost和127.0.0.1虽然都指向本机,但也属于跨域。

浏览器执行javascript脚本时,会检查这个脚本属于哪个页面,如果不是同源页面,就不会被执行。

2)为何要研究跨域问题?

因为浏览器的同源策略规定某域下的客户端在没明确授权的情况下,不能读写另一个域的资源。而在实际开发中,前后端常常是相互分离的,并且前后端的项目部署也常常不在一个服务器内或者在一个服务器的不同端口下。前端想要获取后端的数据,就必须发起请求,如果不做一些处理,就会受到浏览器同源策略的约束。后端可以收到请求并返回数据,但是前端无法收到数据。

3)为何浏览器会制定同源策略

之所以有同源策略,其中一个重要原因就是对cookie的保护。cookie 中存着sessionID 。黑客一旦获取了sessionID,并且在有效期内,就可以登录。当我们访问了一个恶意网站 如果没有同源策略 那么这个网站就能通过js 访问document.cookie 得到用户关于的各个网站的sessionID 其中可能有银行网站 等等。通过已经建立好的session连接进行攻击,比如CSRF攻击。这里需要服务端配合再举个例子,现在我扮演坏人 我通过一个iframe 加载某宝的登录页面 等傻傻的用户登录我的网站的时候 我就把这个页面弹出 用户一看 阿里唉大公司 肯定安全 就屁颠屁颠的输入了密码 注意 如果没有同源策略 我这个恶意网站就能通过dom操作获取到用户输入的值 从而控制该账户所以同源策略是绝对必要的.还有需要注意的是同源策略无法完全防御CSRF。

4)跨域会阻止什么操作?

浏览器是从两个方面去做这个同源策略的,一是针对接口的请求,二是针对Dom的查询

5)多种跨域方法

跨域可以大概分为两种目的

前后端分离时,前端为了获取后端数据而跨域

为不同域下的前端页面通信而跨域

为前后端分离而跨域

ü  Cross Origin Resource Share (CORS)

CORS是一个跨域资源共享方案,为了解决跨域问题,通过增加一系列请求头和响应头,规范安全地进行跨站数据传输。CORS背后的基本思想是使用自定义的HTTP头部允许浏览器和服务器相互了解对方,从而决定请求或响应成功与否.通过过滤器在response中返回头部,使服务器和浏览器可互通。

Access-Control-Allow-Origin:指定授权访问的域

Access-Control-Allow-Methods:授权请求的方法(GET, POST, PUT, DELETE,OPTIONS等)

适合设置单一的(或全部)授权访问域,所有配置都是固定的,特简单。也没根据请求的类型做不同的处理

func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method

    c.Header("Access-Control-Allow-Origin", "\*")  
    c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token")  
    c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE")      //服务器支持的所有跨域请求的方  
    c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")  
    c.Header("Access-Control-Allow-Credentials", "true")

    //放行所有OPTIONS方法  
    if method == "OPTIONS" {  
        c.AbortWithStatus(http.StatusNoContent)  
    }  
    // 处理请求  
    c.Next()  
}  

}

func main() {
r := gin.Default()
r.Use(Cors(),)
r.GET("/register", RegisterHandler)
_ = r.Run(":8000")
}

gin跨域中间件

  • JSONP 跨域

JSONP是前端解决跨域最实用的方法

原理就是html中 的link,href,src属性都是不受跨域影响的,link可以调用远程的css文件,href可以链接到随便的url上,图片的src可以随意引用图片,script的src属性可以随意引入不同源的js文件。JSONP既是利用了,那么就只能支持GET请求。其他请求无法实现

  • nginx 反向代理实现跨域

七、Vue路由

路由器的功能:在数据通信时选择通信的路线。

在vue中的路由,能够在一个vue组件中实现其他组件的相互切换。也就是说,可以通过路由模块,创建路由表,将制定的组件显示在路由视图中。

1.安装路由模块

npm install vue-router –s

2.设计路由界面

3. 创建静态路由表

router.js

import Home from "./components/Home";
import Products from "./components/Products";

export const routes = [
{
path: "/Home",
component: Home,
},
{
path: "/Products",
component: Products,
}
];

4.在mian.js中使用路由模块以及注册路由表

import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import {routes} from "./router";

Vue.config.productionTip = false;
Vue.use(VueRouter); // 使用路由模块
// 创建一个VueRouter模块实例
const router = new VueRouter({
routes: routes
});

new Vue({
router,
render: h => h(App),
}).$mount('#app');

5.创建路由链接和路由视图4.在mian.js中使用路由模块以及注册路由表

App.vue

6.路由之间的参数传递

1. 设参
{
path: "/Products/:id",
component: Products,
}

  1. 传参
    最新商品
    3.接参
    this.$route.params.id
    data(){
    return {
    id: this.$route.params.id
    }
    },

7.路由之间跳转的方式

1)通过html中的路由首页

2)通过js实现路由的跳转 this.$router.push("/Home").catch(err => {err});;

补充:组件样式表的作用范围

如果vue组件中的style标签没有带上scoped标签,那么这个style的样式将会作用在整个页面中,如果加上scoped标签,则样式只会作用在当前组件中。

8.嵌套路由(子路由)

路由显示的组件内部,又嵌套者路由,成为子路由。

1,嵌套路由表

export default new Router({
routes: [
{
path: "/Login",
name: "Login",
component: Login,
},
{
path: "/Backend",
name: "Backend",
component: Backend,
children: [
{
path: "/ProductList",
name: "ProductList",
component: ProductList,
},
{
path: "/ProductInfo",
name: "ProductInfo",
component: ProductInfo,
},
]
}
]
});

 backend.vue

说明:

(1)在元素中配置了用于展示嵌套路由

(2)主要使用 展示嵌套路由内容

9.路由重定向

export default new Router({
routes: [
{
path: "/Login",
name: "Login",
component: Login,
},
{
path: "/Logout",
redirect: "/Login",
},
]
})

 10 参数传递 

1.使用路径匹配的方式
1)修改路由配置
{path: '/user/profile/:id', name:'UserProfile', component: UserProfile}
2)传递参数
(1)router-link
个人信息
说明:此时我们将 to 改为了 :to ,是为了将这一属性当成对象使用,注意 router-link 中的 name 属性名称
一定要和 路由中的 name 属性名称 匹配,因为这样 Vue 才能找到对应的路由路径;
(2)js代码方式
this.$router.push({ name: 'UserProfile', params: {id: 1}});
3)接收参数
{{ $route.params.id }} 2.使用props方式
1)修改路由配置
{path: '/user/profile/:id', name:'UserProfile', component: UserProfile, props: true}
说明:主要增加了 props: true 属性
2)传递参数
同上
3)接收参数
export default {
props: ['id'],
name: "UserProfile"
}
模板中
{{id}}

11.路由钩子

路由钩子函数

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 是组件实例

生命周期执行顺序

准备进入个人信息页

  • ProductInfo.vue?ca1b:17 beforeCreate

  • ProductInfo.vue?ca1b:21 created

  • ProductInfo.vue?ca1b:24 beforeMount

  • ProductInfo.vue?ca1b:30 mounted

  • ProductInfo.vue?ca1b:13 准备离开个人信息页

  • ProductInfo.vue?ca1b:39 beforeDestory

  • ProductInfo.vue?ca1b:42 destory

路由文件示例

import Vue from "vue";
import Router from "vue-router"
import store from "../store"

// 路由懒加载
const Login = () => import(/* webpackChunkName: "Login_Home_Welcome_ResetPwd" */ '../view/Login.vue')
const Home = () => import(/* webpackChunkName: "Login_Home_Welcome_ResetPwd" */ '../view/Home.vue')
const Welcome = () => import(/* webpackChunkName: "Login_Home_Welcome_ResetPwd" */ '../view/personal/Welcome.vue')
const ResetPwd = () => import(/* webpackChunkName: "Login_Home_Welcome_ResetOwd" */ '../view/personal/ResetPwd.vue')

const Users = () => import(/* webpackChunkName: "Users" */ '../view/users/User.vue')

const Roles = () => import(/* webpackChunkName: "Auth" */ '../view/authority/Roles.vue')
const Menus = () => import(/* webpackChunkName: "Auth" */ '../view/authority/Menus.vue')
const API = () => import(/* webpackChunkName: "Auth" */ '../view/authority/API.vue')

const BaseList = () => import(/* webpackChunkName: "System" */ '../view/system/Base.vue')
const ClassList = () => import(/* webpackChunkName: "System" */ '../view/system/Class.vue')
const Course = () => import(/* webpackChunkName: "System" */ '../view/system/Course.vue')

const StudentList = () => import(/* webpackChunkName: "Students" */ '../view/student/StudentList.vue')
const StudentInfo = () => import(/* webpackChunkName: "Students" */ '../view/student/StudentInfo.vue')
const StuActivity = () => import(/* webpackChunkName: "Students" */ '../view/student/StuActivity.vue')
const Research = () => import(/* webpackChunkName: "Students" */ '../view/student/Research.vue')
const Grants = () => import(/* webpackChunkName: "Students" */ '../view/student/Grants.vue')
const Rewards = () => import(/* webpackChunkName: "Students" */ '../view/student/Rewards.vue')
const Jobs = () => import(/* webpackChunkName: "Students" */ '../view/student/Jobs.vue')

// const ScoreImport = () => import(/* webpackChunkName: "Scores" */ '../view/score/ScoreImport.vue')
const UploadExcel = () => import(/* webpackChunkName: "Scores" */ '../view/score/upload-excel.vue')
const ScoreList = () => import(/* webpackChunkName: "Scores" */ '../view/score/ScoreList.vue')
const ScoreSummary = () => import(/* webpackChunkName: "Scores" */ '../view/score/ScoreSummary.vue')
const ScoreFilter = () => import(/* webpackChunkName: "Scores" */ '../view/score/ScoreFilter.vue')

const Archives = () => import(/* webpackChunkName: "Party" */ '../view/party/Archives.vue')
const PartyFilter = () => import(/* webpackChunkName: "Party" */ '../view/party/partyFilter.vue')

const Messages = () => import(/* webpackChunkName: "Message" */ '../view/message/Messages.vue')
const Notices = () => import(/* webpackChunkName: "Message" */ '../view/message/Notices.vue')
const Tasks = () => import(/* webpackChunkName: "Message" */ '../view/message/Tasks.vue')

const Error = () => import(/* webpackChunkName: "Error" */ '../view/404.vue')

// 注册路由
Vue.use(Router);

// 配置路由
const router = new Router({
// mode: "history",
routes: [
{ path: "/", redirect: "/home"},
{ path: "/Login", component: Login},
{
path: "/home",
component: Home,
redirect: "/welcome",
children: [
{path: "/welcome", component: Welcome},
{path: "/reset_pwd", component: ResetPwd},
{path: "/users", component: Users, meta: {requireAuth: true, title: "用户列表"}},
{path: "/roles", component: Roles, meta: {requireAuth: true, title: "角色列表"}},
{path: "/menus", component: Menus, meta: {requireAuth: true, title: "菜单列表"}},
{path: "/api", component: API, meta: {requireAuth: true, title: "API列表"}},
{path: "/base", component: BaseList, meta: {requireAuth: true, title: "基础配置"}},
{path: "/class", component: ClassList, meta: {requireAuth: true, title: "班级列表"}},
{path: "/course", component: Course, meta: {requireAuth: true, title: "课程管理"}},

            {path: "/students", component: StudentList, meta: {requireAuth: true, title: "学生列表"}},  
            {path: "/studentInfo/:id",name: "studentInfo", component: StudentInfo, meta: {requireAuth: true, title: "学生信息"}},  
            {path: "/stuActivity", component: StuActivity, meta: {requireAuth: true, title: "学生活动"}},  
            {path: "/stuResearch", component: Research, meta: {requireAuth: true, title: "科研管理"}},  
            {path: "/rewards", component: Rewards, meta: {requireAuth: true, title: "获奖经历"}},  
            {path: "/grants", component: Grants, meta: {requireAuth: true, title: "助学金"}},  
            {path: "/jobs", component: Jobs, meta: {requireAuth: true, title: "学生就业"}},

            // {path: "/scoreImport", component: ScoreImport, meta: {requireAuth: true, title: "成绩导入"}},  
            {path: "/scoreImport", component: UploadExcel, meta: {requireAuth: true, title: "成绩导入"}},  
            {path: "/scoreList", component: ScoreList, meta: {requireAuth: true, title: "成绩列表"}},  
            {path: "/scoreSummary", component: ScoreSummary, meta: {requireAuth: true, title: "成绩汇总"}},  
            {path: "/scoreFilter", component: ScoreFilter, meta: {requireAuth: true, title: "成绩筛选"}},

            {path: "/archives", component: Archives, meta: {requireAuth: true, title: "政治档案"}},  
            {path: "/partyFilter", component: PartyFilter, meta: {requireAuth: true, title: "党员筛选"}},

            {path: "/messages", component: Messages, meta: {requireAuth: true, title: "消息列表"}},  
            {path: "/notices", component: Notices, meta: {requireAuth: true, title: "通知列表"}},  
            {path: "/tasks", component: Tasks, meta: {requireAuth: true, title: "任务列表"}},

            {path: "/404", component: Error}  
        \]  
    },  
    { path: "\*", redirect: "/404"},  
\]  

});

router.beforeEach((to, from, next) => {
if (to.path === "/Login") return next()
if (to.meta.requireAuth){
if (!store.getters.menu_path_map.hasOwnProperty(to.path) ) {
if (!store.getters.menu_path_map.hasOwnProperty("/" + to.name)) {
return next('/404')
}
}
}
const tokenStr = window.sessionStorage.getItem("token");
if (!tokenStr) return next("/Login")
next()
})

export default router

router,js

import Vue from 'vue'
import App from './App.vue'
import router from "./router";
import store from "./store";
import axios from "axios"
import VueAxios from "vue-axios"
import Storage from "./plugins/storage"
import "./assets/fonts/iconfont.css"
import "element-ui/lib/theme-chalk/index.css"
import "./assets/css/global.css"
import ElementUI from "element-ui"

// 添加进度条效果
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'

// 简单配置
NProgress.inc(0.2)
NProgress.configure({ easing: 'ease', speed: 500, showSpinner: false, trickle: false })

// 配置请求根路径
axios.defaults.baseURL = "http://127.0.0.1:8000/api/v1"
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'

axios.interceptors.request.use(config => {
NProgress.start()
config.headers.Authorization = Storage.sessionGet("token");
return config
})

axios.interceptors.response.use( response => {
NProgress.done()
if (response.status >= 250 && response.status <= 260) { Storage.sessionRemove('token') // 删除已经失效或过期的token(不删除也可以,因为登录后覆盖) router.replace({path: '/Login'}).catch(err => {err})
} else if (response.data.token) {
Storage.sessionSet('token', response.data.token)
}
return response
})

Vue.filter("datetimeFormat", function(time){
let oldDate = new Date(time)
let year = oldDate.getFullYear();
let month = oldDate.getMonth()+1;
let day = oldDate.getDate();
let hour = oldDate.getHours();
let minute = oldDate.getMinutes();
let second = oldDate.getSeconds();
return year+"-"+month+"-"+day+" "+hour+":"+minute+":"+second;
})

Vue.filter("dateFormat", function(time){
if (time == '0001-01-01T00:00:00Z') return ''
let oldDate = new Date(time)
let year = oldDate.getFullYear();
let month = oldDate.getMonth()+1;
let day = oldDate.getDate();
let hour = oldDate.getHours();
let minute = oldDate.getMinutes();
let second = oldDate.getSeconds();
return year+"-"+month+"-"+day
})

Vue.config.productionTip = false
Vue.prototype.domain = "http://127.0.0.1:8000"
Vue.prototype.storage = Storage
Vue.use(VueAxios, axios)
Vue.use(ElementUI)

new Vue({
router,
store,
render: h => h(App),
}).$mount('#app')

main.js