只执行一次的命令
1.安装vscode扩展插件
vetur和Vue 3 Snippets
2.打开vscode终端gitbash--安装vue-cli脚手架
npm install -g @vue/cli@4
3.查看vue脚手架是否安装成功
vue --version
4.创建一个新项目
vue create fresh
(fresh是项目的名称--自定义)
5.执行命令
npm run serve
6.devtool在谷歌浏览器上下载插件
下载极简插件地址: https://chrome.zzzmh.cn/#/index 搜索vue找到需要下载的插件,安装在谷歌浏览器上
7.安装less模块,允许使用less编写样式
npm install less less-loader@6.0.0 --save-dev
(在style里面编写less样式时,style标签后面需要添加样式格式,也就是lang="less")
配置简单的路由
1.安装路由插件
npm i vue-router@3.0
2.在src下创建自定义文件夹views,在此创建各个模块的组件
模块的内容可被修改
3.在src文件夹中创建新自定义文件夹router,
在router文件夹中创建一个自定义文件inex.js
3.1:在index.js文件中配置并导出路由模块
//导入模块
import Vue from "vue";
import Router from "vue-router";
//使用模块
Vue.use(Router);
// 路由数组
const routes = [
{
//根目录地址加/goods就是商品页面的地址
path: "/goods",
//这个页面的在文件中的地址
component: () => import("../views/goods/goodsList.vue")
},
{
path: "/type",
component: () => import("../views/goods/type.vue")
},
{
path: "/cart",
component: () => import("../views/cart/cart.vue")
},
{
path: "/my",
component: () => import("../views/my/my.vue")
}
];
const router = new Router({
routes
});
//导出路由模块
export default router;
3.2:挂载路由
在自动生成的main.js文件夹输入内容
// main.js 代码
import Vue from 'vue'
import App from './App.vue'
//导入这个模块路由
import router from './router/index'
?
Vue.config.productionTip = false
?
new Vue({
// 把router挂载到vue实例
router,
render: h => h(App),
}).$mount('#app')
3.3:配置子路由出口(把子路由对应的组件渲染在什么地方)
App.vue是根路由文件,在不跳转到其它页面的情况下,显示的是最首页的页面内容
配置子路由
在App.vue文件中输入路由出口
<template>
<div id="app">
<!-- 路由出口 -->
<router-view></router-view>
</div>
</template>
这个时候,当输入商品页面的接口时,就可以输入/goods,就能跳转到商品页面
4.底部栏,路由跳转
在App.vue文件中输入静态和样式
<template>
<div id="app">
<!-- 子路由出口(子路由对应的组件展示在这里) -->
<router-view></router-view>
<footer>
//利用to='地址'实现点击该链接,跳转到这个路由对应的页面
//router-link就相当与a标签
<router-link to="/goods">首页</router-link>
<router-link to="/type">分类</router-link>
<router-link to="/cart">购物车</router-link>
<router-link to="/my">我的</router-link>
</footer>
</div>
</template>
<style lang="less">
body {
background: #f4f4f4;
}
footer {
position: fixed;
bottom: 0;
height: 50px;
background: #fff;
display: flex;
justify-content: space-around;
align-items: center;
width: 100%;
}
</style>
5.active-class
active-class是vue-router模块的router-link组件中的属性,用来做选中样式的切换;
//目前在App.vue页面中
<footer>
<router-link active-class="active" to="/goods">首页</router-link>
<router-link active-class="active" to="/type">分类</router-link>
<router-link active-class="active" to="/cart">购物车</router-link>
<router-link active-class="active" to="/my">我的</router-link>
</footer>
6.history模式
vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。
如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面
使用history需要后端支持, vue-cli创建的devServer可以支持
注意:history模式需要服务器的支持
//index.js
const router = new VueRouter({
mode: 'history', // 默认hash
routes: [...]
})
7. redirect重定向
当访问 '/', 我们使用redirect使它默认跳到 '/product'这个路由地址页面
//index.js
{
path: '/',
redirect: '/product'
},
8.404配置
假如用户访问了一个没有的路由, 我们让它跳转到404页面
在index.js文件中添加,并且在components文件夹中添加一个文件,NotFound(自定义的,但是要和下面的名字一样)
//index.js
{
path: '*',
component:()=>import('@/components/NotFound')
}
9.配置子路由
9.1.首先,创建存放子路由的文件夹
在需要有子页面的模块中新建一个自定义文件夹,用来存放子页面的路由.,如在goods模块里面新建一个children文件夹,将goods有关的文件存放在这里.
9.2.子路由编写内容
分别子路由的页面中编写内容,方便查看,(如:商品详情)
9.3配置子路由
在上面新建配置路由的文件index.js中来配置子路由
配置子路由的内容,子路由有一个chilrden数组,存放子页面的路由接口,其中path:'list'里面不需要有'/',因为是子路由,会自动的拼接成'/goods/list'路由,下面的component中的是子页面的存放地址
在到goods模块里面的goods.vue页面里面去配置出口
//goods.vue
<template>
<div>
<!-- 子路由出口 -->
<router-view></router-view>
<!-- 子路由跳转 -->
<router-link to="/goods/list"></router-link>
<router-link to="/goods/detail"></router-link>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>
其中可以看到goods模块文件夹中有一个分类子页面,没有配置出口,是因为它出现在了底部栏中,(不过也可以和goodslist它们配置在同一个地方),但是这里为了方便,我们配置在了外面
将goods模块的分类type.vue的子页面配置在了外面,就是在index.js文件中.没有子页面的模块,就只要配置自己的路由接口就可以了
10.编写静态
10.1:标签元素和less样式的位置
使用less格式编写样式的时候,必须添加lang='less'
编写静态标签元素代码时,必须把内容都用一个div包起来
10.2:支持less格式操作
安装插件,编写代码
npm install less less-loader@6.0.0 --save-dev
11.配置公共样式和字体图标
1:安装插件
npm i vant@latest-v2 -S
2:生成公共样式和字体图标的样式
字体图标尽量下载到本地使用
样式一般都放到src/assets文件夹下,新建一个自定义style文件夹,将公共样式和字体图标放入进去,并且注意将字体图标单独放一个文件件.
3.导入所有的样式
在style文件夹中新建一个自定义文件index.js,将需要的样式导入到这个文件中,然后外面使用时,就只要导入这个index.js文件就可以了
//index.js
//如果有多个也可以导入进来
import '../style/font/iconfont.css';
import '../style/common.css';
4.外部导入所有的样式(包括安装的插件样式)
在main.js根文件中来导入样式(注意:地址要对),这样就可以被其它的页面所使用了
图1:导入公共样式和字体图标的样式
图2:导入插件的样式
//main.js
import Vue from 'vue';
import App from './App.vue';
import router from './router/index';
import Vant from 'vant';
//公共样式和字体图标
import './assets/style/index.js';
import 'vant/lib/index.css';
//使用Vant插件样式
Vue.use(Vant);
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App),
}).$mount('#app')
5.使用公共样式
12.父子组件通信
知识点
父传子:父组件通过(绑定)属性的方式传数据给子组件,子组件使用props接收数据
子传父:父组件在子组件上绑定一个自定义事件,子组件通过$emit触发该自定义事件,同时可以传入数据
1:组件的概念
可以将页面的某一部分做成一个组件,比如页面的导航栏,底部的tab栏等,组件可以重复使用
组件分为:局部组件和全局组件
2:全局组件
全局组件写在main.js页面中,哪里使用就直接使用不需要导入
通过Vue.component()
来定义全局组件
定义全局组件必须放在new vue()
//main.js
//全局组件
//Hello就是这个全局组件名,(自定义的)
Vue.component('Hello',{
data(){
return{
title:'华夏中国'
}
},
template:`<h2>{{title}}</h2>`
})
哪一个页面需要使用这个全局组件,就直接使用组件就可以了,不需要导入
//方式1
<Hello></Hello>
//方式2
<Hello/>
使用全局组件,需要在文件中新建一个文件vue.config.js(固定的)进行配置才能生效
注意:当配置了这个就需要重新启动服务才可以,否则会一直报错
const path = require('path');
module.exports = {
lintOnSave: false,
configureWebpack: (config) => {
config.resolve = {
extensions: [".js", ".vue", ".json", ".css"],
alias: {
vue$: "vue/dist/vue.esm.js",
"@": path.resolve("src")
}
};
}
};
3:局部组件
局部变量需要导入才能使用,尽量将公共局部组件写在自动生成的components文件夹中
如:在components文件夹中新建一个局部组件Footer.vue(这是底部导航栏,一般都是大部分页面需要的).然后在这个Footer.vue文件中编写内容即可
<template>
<div>
<footer class="fixed w100p bg-fff flex jc-sa aic f14">
<router-link active-class="active" to="/goods">
<p class="flex fdc jc-c aic">
<i class="iconfont icon-home"></i>
<span>首页</span>
</p>
</router-link>
<router-link active-class="active" to="/type">
<p class="flex fdc jc-c aic">
<i class="iconfont icon-fenlei"></i>
<span>分类</span>
</p>
</router-link>
<router-link active-class="active" to="/cart">
<p class="flex fdc jc-c aic">
<i class="iconfont icon-gouwuchexian"></i>
<span>购物车</span>
</p>
</router-link>
<router-link active-class="active" to="/my">
<p class="flex fdc jc-c aic">
<i class="iconfont icon-wode"></i>
<span>我的</span>
</p>
</router-link>
<router-link active-class="active" to="/demo" href="./个人中心.html">
<p class="flex fdc jc-c aic">
<i class="iconfont icon-wode"></i>
<span>demo</span>
</p>
</router-link>
</footer>
</div>
</template>
<script>
export default {
}
</script>
<style lang="less">
footer{
bottom: 0;
height: 50px;
}
footer p .iconfont{
font-size: 20px;
}
a{
text-decoration: none;
color: #999;
}
.active{
color: red;
}
</style>
使用局部组件Footer
在使用局部组件的时候,需要先导入局部组件再使用components去注册组件才能使用,哪个页面使用该局部组件,就导入再注册就可使用
注意:注册多个组件,使用逗号分隔开
//假如是购物车页面需要的底部导航栏组件
//cart.vue
<template>
<div>
<!-- 使用组件 -->
<Footer />
</div>
</template>
<script>
//导入底部导航栏组件
import Footer from '@/components/Footer.vue';
export default {
components:{
//注册组件
Footer,
}
}
</script>
4:父传子
父就相当与最外层来展示的页面,子就是父页面内部的各个小模块,这些小模块通过props来接收父页面传过来的数据
父组件通过属性传数据给子组件
子组件通过props接收父组件传过来的数据
注意:1.通过普通属性传递的数据类型是字符串
2.通过:(冒号)绑定属性传递的数据类型是所传数据的类型
例子1:父传子
父页面
<template>
<div>
<h1>demo父组件</h1>
<hr>
//使用局部组件时传过去的数据
//:money="money"利用了冒号绑定事件来获取data传来的数据
<Son msg="hello world" :money="money" :count="true"/>
</div>
</template>
?
<script>
//导入局部组件
import Son from './Son.vue';
export default {
//注册局部组件
components: {
Son
},
?
data() {
return {
money: 2000
}
}
};
</script>
子页面
<template>
<div>
<h4>这是子组件</h4>
<!-- 使用接收的数据 -->
<p>{{msg}}</p>
<p>{{money}}</p>
<p>{{typeof count}}</p>
</div>
</template>
?
<script>
export default {
// 接收msg,money,count属性
props: ['msg','money','count']
}
</script>
例子2:父传子的实践
把生鲜首页拆分为多个子组件
注意:一个组件一个页面
<template>
<div>
//轮播图的组件
<Swiper/>
//秒杀的组件
<Miaosha/>
//商品分类的组件
<TypeList/>
//底部导航栏的组件
<Footer/>
</div>
</template>
?
<script>
//导入各个组件
import Footer from '@/components/Footer.vue';
import Swiper from "./goodList/Swiper.vue";
import Miaosha from "./goodList/Miaosha.vue";
import TypeList from "./goodList/TypeList.vue";
export default {
//注册各个组件
components: {
Footer,
Swiper,
Miaosha,
TypeList
}
};
</script>
5:子传父
父组件在子组件上绑定了一个自定义事件(事件名称我们自己定义,就是vue本身没有的事件)
- 自定义事件再绑定一个函数
- 这个函数也可以是自定义的
- 子组件使用$emit触发(调用)该事件,并把数据以参数的形式传给父组件
例子1:一个简单的例子
父组件页面
<template>
<div>
<h3>父组件</h3>
<hr />
<Son @aaa="say"/>
</div>
</template>
?
<script>
import Son from "./Son";
export default {
components: {
Son,
},
data() {
return {
};
},
methods: {
say(data) {
alert(data)
}
}
};
</script>
子组件页面
<template>
<div>
<h4>子组件</h4>
<button @click="handleClick">点击</button>
</div>
</template>
?
<script>
export default {
methods: {
handleClick() {
this.$emit("aaa", "我是子组件");
}
}
};
</script>
例子2:自定义导航栏组件
//Header.vue
<template>
<div class="nav flex jc-sb aic pl-15 pr-15">
<!-- 触发左边的事件 -->
<p @click="$emit('click-left')">
<van-icon v-if="typeof leftArrow === 'string'" name="arrow-left" />
<span>{{ leftText }}</span>
</p>
<span>{{ title }}</span>
<!-- 触发右边的事件 -->
<span @click="handleClick">{{ rightText }}</span>
</div>
</template>
?
<script>
export default {
props: ["left-text", "title", "right-text", "left-arrow"],
?
methods: {
handleClick() {
// 做点事情
console.log(11111111111111);
// 触发事件的同时给父组件传了个对象
this.$emit("click-right", {username: '张三', age:18});
}
}
};
</script>
?
<style lang="less" scoped>
.nav {
height: 50px;
border: 1px solid;
}
</style>
//demo.vue
//哪个页面需要就导入
<template>
<div>
<NavBar @click-left="onClickLeft" @click-right="onClickRight" left-text="返回" title="购物车" right-text="编辑" left-arrow/>
</div>
</template>
?
<script>
import NavBar from "@/components/NavBar.vue";
export default {
components: {
NavBar
},
?
data() {
return {
money: 2000
};
},
?
methods: {
onClickLeft() {
alert("点击左边");
},
onClickRight(data) {
alert(data.username);
}
}
};
</script>
13:配置axios拦截器
-
对ajax请求进行拦截
- 在请求头添加token
-
对ajax响应数据进行拦截
- 统一处理请求失败的情况,这样就不需要在每个组件里处理失败的情况
- 有些接口需要登录才能访问,在没登录的情况下跳转到登录页面
-
需要配置axios拦截器
在src文件夹下新建一个自定义文件夹api,在api文件夹下新建一个自定义文件ruquest.js
//ruquest.js
import axios from "axios";
import Vue from "vue";
import { Toast } from "vant";
Vue.use(Toast);
?
const service = axios.create({
baseURL: "http://huruqing.cn:3003",
timeout: 50000, // 请求超时时间(因为需要调试后台,所以设置得比较大)
});
?
// request 对请求进行拦截
service.interceptors.request.use(
(config) => {
// 开启loading
Toast.loading({
message: "加载中...",
forbidClick: true,
loadingType: "spinner",
});
// 请求头添加token
config.headers["token"] =
"gg12j3h4ghj2g134kj1g234gh12jh34k12h34g12kjh34kh1g";
return config;
},
(error) => {
Promise.reject(error);
}
);
?
// response 响应拦截器
service.interceptors.response.use(
(response) => {
Toast.clear();
const res = response.data;
if (res.code == 666) {
return res;
} else {
// 成功连接到后台, 但是没有返回正确的数据
Toast.fail(res.msg);
}
},
(error) => {
Toast.clear();
// 跟后台连接失败
Toast.fail("网络异常,请稍后再试");
}
);
?
export default service;