一. 项目介绍
1.vue前台项目
技术结构
技术架构:
vue + webpack + vuex + vue-router + axios + less. .
● 封装通用组件
● 登录注册
● token
● 守卫
● 购物车
● 支付
● 项目性能优化…
2.vue后台管理系统
技术结构
技术架构:
vue + webpack+ vuex + vue- router + axios + scss + elementUI…
● elementUI
● 菜单权限
● 按钮权限
● 数据可视化…
3.数据可视化
echarts数据可视化开源库
canvas画布.
svg矢量图
二. 项目初始化
)
1:vue-cli脚手架初始化项目。node + webpack + 淘宝镜像
- node_modules文件夹:项目依赖文件夹
- public文件夹:一般放置一些静态资源(图片),需要注意,放在public文件夹中的静态资源,webpack进行打包的时候,会原封不动打包到dist文件夹中。
- src文件夹(程序员源代码文件夹):
- assets文件夹:一般也是放置静态资源(一般放置多个组件共用的静态资源),需要注意,放置在assets文件夹里面静态资源,在webpack打包的时候,webpack会把静态资源当做一个模块,打包Js文件里面。
- components文件夹:一般放置的是非路由组件(全局组件)
- App.vue:唯一的根组件,vue当中的组件(.vue)
- main.js:程序入口文件,也是整个程序当中最先执行的文件
- babel.config.js:配置文件(babel相关)
- package.json文件:认为项目‘身份证',记录项目叫做什么、项目当中有哪些依赖、项目怎么运行。
- package-lock.json:缓存性文件
- README.md: 说明性文件
1.项日的其他配置
- 项目运行起来的时候,让浏览器自动打开—package.json
vue.config.json
devServer: {
host: "localhost",
port:8080,
}
- eslint校验功能关闭。
- src文件夹简写方法,配置别名。
jsconfig.json配置别名@提示[@代表的是src文件夹,这样将来文件过多,找的时候方便]
三.项目路由
1.项目路由的分析
vue- router
前端所谓路由: KV键值对。
key:URL(地址栏中的路径)
value:相应的路由组件
注意:项目上中下结构
- 路由组件:
Home首页路由组件、Search路由组件、login登 录路由、Refister注册路由 - 非路由组件:
Header [首页、搜索页]
Footer [在首页、搜索页],但是在登录|注册页面是没有
2. 完成非路由组件Header与Footer业务
1:书写静态页面(HTML + CSS)
2:拆分组件
3:获取服务器的数据动态展示
4:完成相应的动态业务逻辑
注意1:创建组件的时候,组件结构+组件的样式+图片资源
index.vue里面写模板
<template>
</template>
<script>
export default {
}
</script>
<style>
</style>
在App.vue里配置
<template>
<div id="app">
<Header/>
</div>
</template>
<script>
import Header from './components/Header'
export default {
name: 'App',
components: {
Header
}
}
</script>
3. 完成路由组件的搭建
路由history hash
npm i vue-router@3
配置路由
新建router文件
配置路由
// 配置路由的地方
import Vue from 'vue';
import VueRouter from 'vue-router';
// 使用插件
Vue.use(VueRouter);
// 引入路由组件
import Home from '@/pages/Home'
import Search from '@/pages/Search'
import Register from '@/pages/Register'
import Login from '@/pages/Login'
// 配置路由
export default new VueRouter({
// 配置路由
routes: [
{
path: "/home",
component:Home
},
{
path: "/search",
component:Search
},
{
path: "/register",
component:Register
},
{
path: "/login",
component:Login
}
]
})
到main.js里引入路由
总结
路由组件与非路由组件的区别?
1 :路由组件-般放 置在pages |views文件夹,非路由组件一般放置components文件夹中
2:路由组件一般需要在router文件夹中进行注册(使用的即为组件的名字),非路由组件在使用的时候,一般都是以标签的形式使用
3:注册完路由,不管路由路由组件、还是非路由组件身上都有$ route,$router 属性
$route:-般获取路由信息[路径、query、params等等]
$router:一般进行编程式导航进行路由跳转[push|replace]
路由的跳转?
路由的跳转有两种形式:
1.声明式导航router-link,可以进行路由的跳转
<router-link to="./login">登录</router-link>
<router-link to="./register" class="register">免费注册</router-link>
2.编程式导航push|replace,可以进行路由跳转
<button class="sui-btn btn-xlarge btn-danger" type="button"
@click="goSearch">搜索</button>
<script>
export default {
name:"",
methods:{
// 搜索按钮的回调函数:需要向search路由进行跳转
goSearch(){
this.$router.push('./search')
}
}
}
</script>
编程式导航:声明式导航能做的,编程式导航都能做,但是编程式导航除了可以进行路由跳转,还可以做一些其他的业务逻辑。
4. Footer组件显示与隐藏
显示或者隐藏组件:v-if|v-show
Footer组件:在Home、 Search显示Footer组件
Footer组件:在登录、注册时候隐藏的
1. 我们可以根据组件身上的$route获取当前路由的信息,通过路由路径判断Footer显示与隐藏。
2. 配置的路由的时候,可以给路由添加路由元信息[meta]
<Footer v-show="$route.meta.show"/>
{
path: "/home",
component: Home,
meta:{show:true}
},
5.路由传参
search/index.vue
<h1>params{{$route.params.keyword}}</h1>
<h1>query{{$route.query.k}}</h1>
//路由传递参数:
//第一种:字符串形式
//this.$router.push("/search/" + this.keyword+"?k="+this.keyword.toUpperCase());
//第二种:模板字符串
//this.$router.push( `/search/${this.keyword}?k=${this.keyword.toUpperCase()}`)
//第三种:对象
this.$router.push({name:"search",params:{keyword:this.keyword},query:{k:this.keyword.toUpperCase()}})
{
path :"/search/:keyword", //配置路由,声明接收params参数 ( 使用占位符声明接收params参数 )
component: Search,
meta:{show:true},
name :"search" //第三种需要配置
},
路由传参面试题
1:路由传递参数(对象写法)path是否可以结合params参数一起使用?
//答:路由跳转传参的时候,对象的写法可以是name、path形式,但是需要注意的是,path这种写法不能与params参数一起使用
this.$router.push({path:'/search',params:{keyword:this.keyword},query:{k:this.keyword.toUpperCase()}})
2:如何指定params参数可传可不传?
//如果路由要求传递params参数,但是你就不传递params参数,发现一件事情,URL会有问题的
http://localhost:8080/#/?k=QWE //实际错误
http://localhost:8080/#/search?k=QWE //想象传递
//如何指定params参数可以传递、或者不传递,在配置路由的时候,在占位的后面加上一个问号params可以传递或者不传递【path :"/search/:keyword?",】
3:params参数可以传递也可以不传递,但是如果传递是空串,如何解决?
//使用undefined解决: params 参数可以传递、不传递(空的字符串)
this.$router.push({path:'/search',params:{keyword:''||undefined},query:{k:this.keyword.toUpperCase()}})
4:路由组件能不能传递props数据?
{
path: "/search/ : keyword?",
component: Search,
meta : { show:true},
name: " search",
//路由组件能不能传递props数据?
//布尔值写法:只能传递params参数
//props:true,
//对象写法:额外的给路由组件传递一些props
// props:{a:1,b:2},
//函数写法:可以params参数、query参数,通过props传递给路由组件 页面获取{{keyword}} {{k}}
props:($route)=>({keyword:$route.params.keyword,k:$route.query.k})
}
6.重写push和replace
1:编程式路由跳转到当前路由(参数不变),多次执行会抛出NavigationDuplicated的警告错误?
– 路由跳转有两种形式:声明式导航、 编程式导航
–声明式导航没有这类问题的,因为vue-router底层已经处理好了
1.1为什么编程式导航进行路由跳转的时候,就有这种警告错误那?
“vue-router”: “^3.5.3”: 最新的vue -router引入promise
function push(){
return new Promise( (resolve , reject)=>{
})
1.2通过给push方法传递相应的成功、失败的回调函数,可以捕获到当前错误,可以解决。
1.3通过底部的代码,可以实现解决错误
this . $router . push( {name :”search", params : {keyword: this. keyword} , query : {k:this . keyword . toUpperCase()}},()=>{},()=>{}); I
这种写法:治标不治本,将来在别的组件当中push|replace,编程式导航还是有类似错误。
1.4
this:当前组件实例(search)
this. $ router属性:当前的这个属性,属性值VueRouter类的一个实例,当在入口文件注册路由的时候,给组件实例添加
r
o
u
t
e
r
∣
router |
router∣route属性
push: VueRouter类的一个实例
function VueRouter(){
}
VueRouter . prototype. push = function(){
}
let $router = new VueRouter();
重写
四. Home首页模块
全局组件步骤
1. TypeNav: 3级分类导航
- 三级联动组件完成:
由于三级联动,在Home、Search、 Detail, 把三级联动注册为全局组件。
好处:只需要注册一次,就可以在项目任意地方使用。
1.静态资源
2.main.js
// 三级联动组件----全局组件
import TypeNav from '@/pages/Home/TypeNav'
// 第一个参数:全局组件的名字 第二个参数:哪一个组件
Vue.component(TypeNav.name, TypeNav);
3.Home/index.vue
<div>
<!-- 三级联动已经注册为全局组件,不需要import -->
<TypeNav/>
</div>
其他静态组件
2. ListContainer: 包含轮播列表的容器
3. TodayRecommend: 今日推荐
4. Rank: 排行
5. Like: 猜你喜欢
6. Floor: 楼层
7. Brand: 品牌
五. 接口
1.postman测试接口
2.axios二次封装
a.安装axios
npm i --save axios
b.配置axios
api/request.js
// 对于axios进行二次封装
import axios from "axios";
// 1.利用axios对象的方法create,去创建一个axios实例
// 2.request就是axios,只是稍微配置一下
const requests = axios.create({
// 配置对象
// 基础路径,发请求的时候路径会出现api /api/home/list ---/home/list
baseURL:"/api",
// 代表请求超时的时间5s
timeout:5000,
})
// 请求拦截器:再发请求之前,请求拦截器可以检测到,可以在请求发出去之前做一些事情
requests.interceptors.request.use(config => {
// config:配置对象,对象里面有一个属性很重要,headers请求头
return config;
}
)
// 响应拦截器
requests.interceptors.response.use((res) => {
// 成功的回调函数:服务器响应数据回来以后,相应拦截器可以检测到,可以做一些事情
return res.data;
}, (error) => {
// 响应失败的回调
return Promise.reject(new Error("faile"))
}
);
// 对外暴露
export default axios;
总结
6:axios二次封装
XMLHttpRequest、fetch、 JQ、axios
6.1为什么需要进行二次封装axios?
请求拦截器、响应拦截器.请求拦截器,可以在发请求之前可以处理一些业务、响应拦截器,当服务器数据返回以后,可以处理一些事情
6.2在项目当中经常API文件夹[axios]
接口当中:路径都带有/api
baseURL :”/api"
6.3可以参考git |NPM关于axios文档
3.接口统一管理
a.接口统一管理
- 项目很小:完全可以在组件的生命周期函数中发请求
- 项目大: axios .get( ’ xxx’)
b.跨域问题
什么是跨域:协议、域名、端口号不同请求,称之为跨域
前端项目本地服务器:http://localhost:8080/#/home
后台服务器:http://gmall-h5-api.atguigu.cn
JSONP、CROS、代理
跨域问题
api/index.js
// 当前这个模块:API进行统一管理
import request from './request';
// 三级联动
// /api/product/getBaseCategoryList get请求 无参数
export const reqCategoryList = () => {
// 发请求:axios发请求返回结果Promise对象
return request({
url: '/api/product/getBaseCategoryList',
method:'get'
})
}
vue.config.js解决跨域问题
devServer: {
proxy: {
// 浏览器有/api,前台服务器3000找 http://gmall-h5-api.atguigu.cn要数据
// 没有/api,前台服务器不给转发
'/api': {
// 服务器与服务器之间没有跨域问题,前端浏览器有跨域问题
// 后台服务器地址
target: 'http://gmall-h5-api.atguigu.cn',
// pathRewrite:{'^/api':''},
},
},
},
nprogress进度条的使用
安装
npm i --save nprogress
main.js引入
六.vuex状态管理库
npm install --save vuex@3
1.vuex是什么?
vuex是官方提供一个插件,状态管理库,集中式管理项目中组件共用的数据。
切记,并不是全部项目都需要Vuex,如果项目很小,完全不需要Vuex,如果项目很大,组件很多、数据很多,数据维护很费劲,Vuex
state
mutations
actions
getters
modules
2.vuex基本使用
import Vue from "vue";
import Vuex from "vuex";
// 需要使用插件一次
Vue.use(Vuex);
// state:仓库存储数据的地方
const state = {};
// mutations:修改state的唯一手段
const mutations = {};
// action:处理action,可以书写自己的业务逻辑,也可以处理异步
const actions = {}
// getter:理解为计算属性,用于简化仓库数据,让组件获取仓库的数据更加方便
const getters = {}
// 对外暴露Store类的一个实例
export default new Vuex.Store({
state,
mutations,
actions,
getters
})
3.vuex实现模块式开发
如果项目过大,组件过多,接口也很多,数据也很多,可以让Vuex实现模块式开发
模拟state存储数据
{
home:{},
search:{}
}
store/home/index.js
// ---home模块的小仓库
const state = {};
const mutations = {};
const actions = {}
const getters = {}
export default {
state,
mutations,
actions,
getters
}
store/index.js
import Vue from "vue";
import Vuex from "vuex";
// 需要使用插件一次
Vue.use(Vuex);
import home from './home'
import search from './search'
// 对外暴露Store类的一个实例
export default new Vuex.Store({
// 实现Vuex仓库模块化开发存储数据
modules: {
home,
search
}
})
七.三级联动
1.动态获取数据
2.动态背景颜色
a.存储当前鼠标移动的值
data(){
return {
// 存储用户鼠标移上哪一个一级分类 索引从0开始,-1表示谁都没有移上
currentIndex:-1
} },
b.添加鼠标经过事件,添加类名,
<div class="item" v-for="(c1,index) in categoryList" :key="c1.categoryId"
//鼠标移上谁谁才有cur类名
:class="{cur:currentIndex===index}">
//添加鼠标经过事件
<h3 @mouseenter="changeIndex(index)">
<a href="">{{c1.categoryName}}</a>
</h3>
c.添加鼠标离开事件,给大div加,事件委派
<div @mouseleave="leaveIndex">
<h2 class="all">全部商品分类</h2>
<div class="sort">···<div/>
</div>
d.事件
methods:{
// 鼠标进入修改响应数据currentIndex属性
changeIndex(index){
// index:鼠标移上某一级分类的元素索引值
this.currentIndex = index;
},
// 一级分类鼠标移出事件回调
leaveIndex(){
this.currentIndex = -1;
}
},
2.二三级分类的显示与隐藏
<div class="item-list clearfix" :style="{display:currentIndex==index?'block':'none'}">
3.卡顿现象—防抖和节流
//正常情况(用户慢慢的操作):鼠标进入,每一个一级分类h3,都会触发鼠标进入事件
//非正常情况(用户操作很快):本身全部的一级分类都应该触发鼠标进入事件,但是经过测试,只有部分h3触发了
//就是由于用户行为过快,导致浏览器反应不过来。如果当前回调函数中有一些大量业务,有可能出现卡顿现象。
- 正常:事件触发非常频繁,而且每一次的触发,回调函数都要去执行(如果时间很短,而回调函数内部有计算,那么很可能出现浏览器卡顿)
- 节流:在规定的间隔时间范围内不会重复触发回调,只有大于这个时间间隔才会触发回调,把频繁触发变为少量触发
- 防抖:前面的所有的触发都被取消,最后一次执行在规定的时间之后才会触发,也就是说如果连续快速的触发只会执行一次
1.防抖:
- 用户操作很频繁,但是只是执行一次
- 例子:
2.节流:
- 用户操作很频繁,但是把频繁的操作变为少量操作【可以给浏览器有充裕的时间解析代码】
- 例子:
3.三级联动节流技术–lodash
component/TypeNav/index.vue
import throttle from 'lodash/throttle';
····
// 节流函数用箭头函数可能会出现,上下文this情况
changeIndex:throttle(function(index){
// 节流
// index:鼠标移上某一级分类的元素索引值
this.currentIndex = index;
},50),
4.三级联动路由跳转与传参
利用事件的委派+编程式导航实现路由跳转与传递参数
-
注意:
- 声明式导航(router-link)是组件
- 利用自定义属性区分a标签
路由跳转需要携带的参数:
路由中的query 和 params的传参方式区别和详解
goSearch(event){
//最好的解决方案: 编程式导航+事件委派
//存在一些问题:事件委派,是把全部的子节点[h3、dt、dl、 em]的事件委派给父亲节点
//点击a标签的时候,才会进行路由跳转[ 怎么能确定点击的一定是a标签]
//存在另外一个问题:即使你能确定点击的是a标签,如何区分是-级、二级、三级分类的标签。
//第一个问题:把子节点当中a标签,我加上自定义属性data-categoryName, 其余的子节点是没有的
let element = event.target;
//获取到 当前出发这个事件的节点[h3、a、dt、 dl],需要带有data-categoryname这样 节点[一定是a标签 ]
//节点有一个属性dataset属性,可以获取节点的自定义属性与属性值
let {categoryname,category1id,category2id,category3id} = element.dataset ;
//如果标签身上拥有categoryname . 定是a标签
if (categoryname){
let location = {name:'search'};
let query = {categoryName:categoryname};
//一级分类、一级分类、三级分类的a标签;
if(category1id){
query.category1Id = category1id;
}else if(category2id){
query.category2Id = category2id;
}
else{
query.category3Id = category3id;
}
// 整理完参数
location.query = query
// 路由跳转
this.$router.push(location)
}
}
5.开发search中TypeNav
a.列表的显示与隐藏
b.过渡动画
前提:组件/元素必须要有要有v-show/v-if指令
name=“sort” 下方命令 .sort-enter
c.优化
组件只要用到TypeNav就会发请求
发请求的目的是获取数据,希望只发一次请求
不管路由怎么跳转,根组件App.vue里的mounted(){}只执行一次,将 this.$store.dispatch(‘categoryList’);放入app组件中
app组件最先执行,在typenav要数据的时候,仓库里已经存在
d.参数合并
八.
开发Home首页当中的ListContainer组件与Floor组件?
https://docschina. org/
但是这里需要知道一件事情: 服务器返回的数据(接口)只有商品分类菜单分类数据,对于ListContainer组件与Floor组件数据服务器没有提供的。
mock数据(模拟) :如果你想mock数据,需要用到一个插件mockjs
1.安装mock.js
npm install mockjs
使用步骤:
1)在项目当中src文件夹中创建mock文件夹
2)第二步准备JSON数据(mock文 件夹中创建相应的JSON文件) ----格式化-一下,别留有空格(跑不起来的)
3)把mock数据需要的图片放置到public文件夹中public文件夹在打包的时候,会把相应的资源原封不动打包到dist文件夹
4)创建mockSerer.js通过mockjs插件实现模拟数据
十.search组件:
1.静态
2.动态获取数据:
问题1:
多次向服务器发请求??
写成方法:可以多次调用
在服务器发请求之前,将参数变化beforeMount
// 发请求之前捞到参数,带给服务器地址栏会带上请求的参数
合并参数,将query和params参数获取的值一起传给searchParams
Object.assign(searchParams,this.
r
o
u
t
e
.
q
u
e
r
y
,
t
h
i
s
.
route.query,this.
route.query,this.route.params)