一、
1.项目结构分配、模块的定义、组件封装调用(先定义、然后导入 注册 使用)、路由拦截、cookie理解、token权限、路由权限管理、动态路由、打包部署、vuex状态管理、接口文档
对于跳转,要先设置路由确保组件可以正常使用、路由可以正常跳转,再充实页面
对于左边导航与上边面包屑的联动,可以通过this.$route.matched获取该路由的所有信息,然后在每一个路由中写一个meta对应路由的内容,在面包屑那块通过遍历当前路由信息展示meta的值
对于导航栏的多级菜单,可以通过递归进行展示
父子组件传值的时候,子组件中通过props接收父组件传过来的值
二、流程
1.脚手架创建项目,并且 关闭eslint校验以防写代码时没错也报错
node_modules:放置项目依赖的地方。
public:一般放置一些共用的静态资源,打包上线的时候,public文件夹里面资源原封不动打包到dist文件夹里面。
src:程序员源代码文件夹:
assets:经常放置一些静态资源(公用的图片(即很多组件都用此图)),assets文件夹里面资源webpack会进行打包为一个模块(js文件夹里面)
components:一般放置非路由组件(如共用的组件)
App.vue:唯一的根组件
main.js:入口文件【程序最先执行的文件】
babel.config.js:babel配置文件
package.json:项目描述、项目依赖、项目运行
README.md:项目说明文件
2.在开发项目的时候:
非路由组件:
- 书写静态页面(HTML + CSS)
- 拆分组件
- 获取服务器的数据动态展示
- 完成相应的动态业务逻辑
路由组件
- 创建组件:Vue.component(tagName, options)
var 组件内容 = Vue.extend({template: '<div>自定义全局组件,使用Vue.extend</div>'})Vue.component("组件名称",组件内容)
- 在router中创建并配置具体路由
- 在main.js中引入进行全局注册路由
- 在app.vue中:<!-- 路由组件出口的地方、路由组件展示 --> <router-view></router-view>
- 在需要的地方引入标签
3.mockjs模拟数据
使用Mock.js插件,生成随机数据,拦截 Ajax 请求。
- 前后端分离:让前端攻城师独立于后端进行开发。
- 增加单元测试的真实性:通过随机数据,模拟各种场景。
- 开发无侵入:不需要修改既有代码,就可以拦截 Ajax 请求,返回模拟的响应数据。
- 用法简单:符合直觉的接口。
- 数据类型丰富:支持生成随机的文本、数字、布尔值、日期、邮箱、链接、图片、颜色等。
- 方便扩展:支持支持扩展更多数据类型,支持自定义函数和正则。
4.购物车的管理
1.逻辑:点击加入购物车,将详情页的数据加入购物车中;
此时需要对state中的cartList进行修改,点击加入购物车就会执行store文件中actions对象里定义的加入购物车方法,actions对象中对加入的商品进行判断,若之前商品已存在,则数量加1,若商品不存在,则将商品添加进cartList。其返回一个promise对象。
2.组件:在购物车模块里可以查看详细信息 更新数量,全选反全选 取消加购 去结款
- 将加入购物车中的商品对象放到cartList中了,商品对象中包括需要在购物车中展示的详细信息。首先将通过store中的getters属性接收vuex中的数据,通过v-for来对cartList中的商品对象进行遍历,同时展示单个商品信息的组件通过props来接收父组件传来的单个商品对象;在计算属性computed中接收vuex中挂载的数据。
- 通过数组的filter方法找出选中的商品 ,然后通过数组的reduce方法对选中商品的价值总额进行计算;
- 全选的逻辑:若部分商品或者全部商品未被选中,则利用forEach使cartList中的每个商品为选中状态;若全部选中,则使cartList中的每个商品为未选中状态
3.数据交互:通过vuex状态管理机制来实现购物车的数据交互,创建store文件并挂载在vue实例上,在store文件中定义一个可以挂载数据的state,在其中定义一个数组cartList来存放商品信息,其他组件就可以获取并使用这个数据了。
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)export default new Vuex.Store({state: {carList: [] //购物车的商品},mutations: {// 加addCar(state, params) {let CarCon = state.carList;// 判断如果购物车是否有这个商品,有就只增加数量,否则就添加一个// some 只要有一个isHas为true,就为truelet isHas = CarCon.some((item) => {if (params.id == item.id) {item.num++;return true;} else {return false;}})if (!isHas) {let obj = {"id": params.id,"title": params.title,"price": params.price,"num": 1,}this.state.carList.push(obj)}},// 减reducedCar(state,params){let len=state.carList.length;for(var i=0;i<len;i++){if(state.carList[i].id==params.id){state.carList[i].num--if(state.carList[i].num==0){state.carList.splice(i,1);break;}}}},//移出deleteCar(state,params){let len=state.carList.length;for(var i=0;i<len;i++){if(state.carList[i].id==params.id){state.carList.splice(i,1);break;}}},// 初始化购物车,有可能用户一登录直接进入购物车// initCar(state, car) {// state.carList = car// },},actions: {// 加addCar({ commit }, params) {// console.log(params) //点击添加传过来的参数// 使用setTimeout模拟异步获取购物车的数据setTimeout(function () {let result = 'ok'if (result == 'ok') {// 提交给mutationscommit("addCar", params)}}, 100)},// 减reducedCar({ commit }, params) {// console.log(params) //点击添加传过来的参数// 使用setTimeout模拟异步获取购物车的数据setTimeout(function () {let result = 'ok'if (result == 'ok') {// 提交给mutationscommit("reducedCar", params)}}, 100)},// 移出deleteCar({ commit }, params) {// console.log(params) //点击添加传过来的参数// 使用setTimeout模拟异步获取购物车的数据setTimeout(function () {let result = 'ok'if (result == 'ok') {// 提交给mutationscommit("deleteCar", params)}}, 100)}// initCar({ commit }) {// setTimeout(function () {// let result = 'ok'// if (result == 'ok') {// // 提交给mutations// commit("initCar", [{// "id": 20193698,// "title": '我是购物车原来的',// "price": 30,// "num": 100,// }])// }// }, 100)// }},getters: {//返回购物车的总价totalPrice(state) {let Carlen = state.carList;let money = 0;if (Carlen.length != 0) {Carlen.forEach((item) => {money += item.price * item.num})return money;} else {return 0;}},//返回购物车的总数carCount(state) {return state.carList.length}},
})list.vue<template><!-- 商品列表 --><div id="listBox"><!-- --><router-link :to="{path:'/car'}" style="line-height:50px">跳转到购物车</router-link><el-table :data="tableData" border style="width: 100%"><el-table-column fixed prop="id" align="center" label="商品id"></el-table-column><el-table-column prop="title" align="center" label="商品标题"></el-table-column><el-table-column prop="price" align="center" label="商品价格"></el-table-column><el-table-column label="操作" align="center"><template slot-scope="scope"><el-button @click="addCar(scope.row)" type="text" size="small">加入购物车</el-button></template></el-table-column></el-table></div>
</template><script>
export default {name: "listBox",data() {return {tableData: [] //商品列表};},methods: {// 初始化商品列表initTable(){this.$gAjax(`../static/shopList.json`).then(res => {console.log(res)this.tableData=res;})["catch"](() => {});},// 加入购物车addCar(row){// console.log(row)// 提交给store里面actions 由于加入购物车的数据要同步到后台this.$store.dispatch('addCar',row)}},mounted () {this.initTable()}
};
</script>
<style>
#listBox {width: 900px;margin: 0 auto;
}
</stylecart.vue
<template><!-- 购物车 --><div id="carBox"><!-- 商品总数 --><h2 style="line-height:50px;font-size:16px;font-weight:bold">合计:总共{{count}}个商品,总价{{totalPrice}}元</h2><p v-if="count==0">空空如也!·······</p><div v-else><el-table :data="carData" border style="width: 100%"><el-table-column fixed prop="id" align="center" label="商品id"></el-table-column><el-table-column prop="title" align="center" label="商品标题"></el-table-column><el-table-column prop="price" align="center" label="商品价格"></el-table-column><el-table-column label="操作" align="center"><template slot-scope="scope"><el-button @click="reduceFun(scope.row)" type="text" size="small">-</el-button><span >{{scope.row.num}}</span><el-button @click="addCar(scope.row)" type="text" size="small">+</el-button><el-button @click="deleteFun(scope.row)" type="text" size="small">删除</el-button></template></el-table-column></el-table></div></div>
</template><script>
export default {name: "carBox",data() {return {};},computed: {//购物车列表carData() {return this.$store.state.carList;},//商品总数count() {return this.$store.getters.carCount;},//商品总价totalPrice() {return this.$store.getters.totalPrice;}},methods: {// 增加数量addCar(row){this.$store.dispatch('addCar',row)},// 减数量reduceFun(row){this.$store.dispatch('reducedCar',row)},// 删除deleteFun(row){this.$store.dispatch('deleteCar',row)}// 用户首次登录请求购物车的数据// initCar(){// this.$store.dispatch('initCar')// }},created () {// this.initCar();},mounted() {}
};
</script><style>
#carBox {width: 900px;margin: 0 auto;
}
</style>
三、知识点记录
路由跳转的两种方式:
- 声明式导航:router-link,可以进行路由的跳转<router-link to="/login">登录</router-link>
- 编程式导航:利用组件实例的 $router.push | replace,可以进行路由跳转
编程式导航:声明式导航能做的,编程式导航都能;但是编程式导航除了可以进行路由跳转,还可以做一些其他的业务逻辑。
路由元信息:
将任意信息附加到路由上,如过渡名称、谁可以访问路由等。这些事情可以通过$route 的 meta属性 来实现,并且它可以在路由地址和导航守卫上都被访问到。
路由参数传递
params参数: 属于路径当中的一部分,需要注意,在配置路由的时候,需要占位
query参数: 不属于路径当中的一部分,类似于ajax中的queryString /home?k=v&kv=,不需要占位
路由传递参数(对象写法) path是否可以结合 params参数一起使用? 即:
this.$router.push({ path: '/search', params: { keyword: this.keyword }, query: { k: this.keyword.toUpperCase() }, });
答:报错,不能。
如何指定 params参数 可传可不传? 即:
this.$router.push({ name: "search", query: { k: this.keyword.toUpperCase() }, });
答:配置路由时,path上加个 ? 号,代表可传参数也可不传;若不加 ? ,则URL会出现问题。
params参数 可以传递也可以不传递,但是如果传递是空串,如何解决?
可以使用 undefined 来解决params参数可以传递也可不传递(空的字符串)
路由组件能不能传递 props数据?
可以采用三种写法
{path: "/search/:keyword",component: Search,meta: { show: true },// 对象形式路由传递参数name: "search",// 路由组件能不能传递 props数据?// 1、布尔值写法,但是这种方法只能传递params参数// props: true,// 2、对象写法:额外给路由组件传递一些props// props: { a: 1, b: 2 },// 函数写法(常用):可以params参数、query参数,通过props传递给路由组件props: ($route) => {return {keyword: $route.params.keyword, k: $route.query.k};}},
为什么进行axios 二次封装
为了请求拦截器、响应拦截器。
请求拦截器:在发请求之前可以处理一些业务;
响应拦截器:当服务器数据返回以后,可以处理一些事情。
// 对于axios进行二次封装
import axios from "axios"// 利用axios对象的方法create,去创建一个axios实例
// 这里的request 就是 axios,在这里配置一下
const request = axios.create({// 配置对象// 基础路径,发请求的时候,路径当中会默认有/api,不用自己写了baseURL: "/api",// 请求超时5stimeout: 5000,
})// 请求拦截器:在发请求之前,请求拦截器可以检测到,在请求发出之前做一些事情;
requests.interceptors.request.use((config) => {// config:配置对象,其有一个重要属性:header请求头})
// 响应拦截器:当服务器数据返回以后,可以处理一些事情。
requests.interceptors.response.use(((res) => {// 服务器响应成功的回调函数return res.data;
}, (error) => {// 服务器响应失败的回调函数return Promise.reject(new Error('faile'));
}))// 对外暴露
export default requests;
API接口统一管理
若项目很小,可以在组件的生命周期函数中发请求
但项目大,组件多,若有更改,将麻烦。所以API接口统一管理。比如跨域的代理可以统一管理
nprogress进度条的使用
在响应拦截器使用
// 请求拦截器:
requests.interceptors.request.use((config) => {// config:配置对象,其有一个重要属性:header请求头// 进度条开始动nprogress.start();return config;})
// 响应拦截器:
requests.interceptors.response.use((res) => {// 服务器响应成功的回调函数// 进度条结束nprogress.done();return res.data;
}, (err) => {// 服务器响应失败的回调函数return Promise.reject(new Error('faile'));
})
vuex 模块式开发
vuex 是官方提供的插件, 状态管理库,集中式管理项目中组件共用的数据 。
切记,并不是全部项目都需要 Vuex,如果项目很小,完全不需要Vuex,如果项目很大,组件很多、数据很多,数据维护很费劲,用Vuex
图片懒加载
// 引入图片懒加载插件
import VueLazeload from 'vue-lazyload';
// 引入懒加载默认图片(即真实图片没加载好之前,加载时显示的图片)
import tp from '@/assets/images/1.png';
// 注册插件
Vue.use(VueLazeload, {// 懒加载默认图片,(即真实图片没加载好之前,加载时显示的图片)loading: tp,
})<!-- v-lazy自定义指令图片懒加载 -->
<img v-lazy="good.defaultImg" />