在根目录下建立jsconfig.json文件,将如下代码放进去。
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
},
"exclude": ["node_modules", "dist"]
}
将Header和Footer这种始终非路由组件,放在components文件夹下,建立相应文件夹,内部装有Header.vue文件。
至于路由组件,在src文件夹下建立pages文件夹,将对应路由组件放在此文件夹下。
1.在移动代码时,要注意样式和图片。关于样式,因为本项目利用less书写样式,在引入时,加上lang="less"。对于图片,在相应文件夹下建立images文件夹。
2.不要忘记将reset.css,移动到public文件夹下,并引入,才能此样式生效。
//利用route中的meat属性
{
path:'/home',
component:Home,
meta:{show:true}
}
定义一个方法,使用push,如果需要传参,在后面加相应参数即可(params记得占位)
this.$router.push({
name:'search',
params:{keyword:'你好啊'}
query:{k:this.keyword} // query传参是键值对的形式
})
在配置路由时,在占位的后面加上一个问号即可实现。
path:'/search/:keyword?'
传递空串的时候,用undefined解决问题。
this.$router.push({
name:'search',
params:{keyword:'' || undefined}
})
重写push方法,如果resolve或者reject没有回调函数,则添加一个回调即可。
import VueRouter from 'vue-router'
//重写push方法
let originPush = VueRouter.prototype.push
VueRouter.prototype.push = function(location,resolve,reject){
if(resolve && reject){
originPush.call(this,location,resolve,reject)
} else{
originPush.call(this,location,()=>{},()=>{})
}
}
//全局注册组建
import TypeNav from '@/components/TypeNav'
// 传递两个参数(组件的名字,哪一个组件)
Vue.component(TypeNav.name,TypeNav)
全局组件注册完成后,在引入时不需要import和声明components
在src文件夹下建立api文件夹,创建request.js文件。
1.设置baseURL和timeout
import axios from "axios";
const requests = axios.create({
baseURL:'/api',
timeout:5000 //访问大于5s取消
})
2.设置请求拦截器和响应拦截器。
//引入nprogress
import nprogress from "nprogress";
//引入nprogress样式
import 'nprogress/nprogress.css'
//设置请求拦截器,并且加上进度条。
requests.interceptors.request.use(function (config) {
nprogress.start()
return config;
}, function (error) {
return Promise.reject(error);
});
// 设置响应拦截器
requests.interceptors.response.use(function (response) {
nprogress.done()
return response.data;
}, function (error) {
return Promise.reject(error);
});
在api文件夹下建立index.js
import requests from './request'
//三级联动axios发请求
export const reqCategoryList = () =>
requests({ url: 'product/getBaseCategoryList', method: 'get' })
设置访问接口函数后,在main.js中引入
// 引入reqCategoryList
import {reqCategoryList} from '@/api'
进行跨域代理操作,实现本地接口与目标接口连接
//配置代理跨域
devServer: {
proxy: {
"/api": {
target: "http://39.98.123.211",
},
},
},
1.如果项目过大,state内数据过多不便查看,可以在store文件夹下设置小库再引入。引入时不再引入state,而是引入modules:{home,search}
//创建action,mutation,state,getter对象
const state = {
a:1
}
const actions = {}
const mutations = {}
const getters = {}
export default {
actions,
mutations,
state,
getters
}
import Vue from 'vue'
import Vuex from 'vuex'
//引入小仓库
import home from './home'
import search from './search'
Vue.use(Vuex)
export default new Vuex.Store({
actions,
mutations,
modules: {
home,
search,
},
getters
})
2.配置vuex,在home.js中完成请求数据。
//引入请求数据的函数,分别暴露一定要记得加{}
import {reqCategoryList} from '@/api'
//创建action,mutation,state,getter对象
const state = {
categoryList:[]
}
const actions = {
//reqCategoryList()请求回来是一个Promise函数,所以用async和await
async categoryList({commit}){
let result = await reqCategoryList()
if(result.code == 200){ //如果请求成功,result.code就是200
commit('CATEGORYLIST',result.data)
}
}
}
const mutations = {
CATEGORYLIST(state,categoryList){
state.categoryList = categoryList //将数据交给state
}
}
const getters = {
}
export default {
actions,
mutations,
state,
getters
}
3.在TypeNav中配置计算属性,再在页面中使用。
import { mapState } from "vuex"; //引入mapState要记得加{}!烦死了
computed: {
...mapState({
categoryList: (state) => state.home.categoryList,
}), //vuex会传递state到函数中,得到下面小库中的内容即可
},
4.对于1级、2级、3级标题进行遍历,得到所有标题
v-for="(c1, index) in categoryList" :key="c1.categoryId"
v-for="(c2, index) in c1.categoryChild" :key="c2.categoryId"
v-for="(c3, index) in c2.categoryChild" :key="c3.categoryId"
5.利用js设置hover效果:对于h标题添加@mouseenter和@mouseleave事件传递参数(index),设置this.currentIndex = index;(记得设置初始currentIndex = -1)
为父盒子div设置动态类:class="{ cur: currentIndex == index }"
最后设置cur的backgroudcolor,即可实现hover效果。
6.利用js实现2、3级标题的显示与隐藏:为2、3级标题设置动态style。
:style="{display:currentIndex == index? 'block' : 'none'}"
1.首先将每个a标签上都添加一个统一的自定义属性名,便于我们判断是否点击的是a标签。
:data-categoryName="c2.categoryName"
2.将每个a标签上也添加一个各自的id属性,便于我们判断点击的是几级标签。
:data-category2Id = "c2.categoryId"
3.给div加上goSearch方法,配置方法,注意在传递参数的时候要将params参数和query参数都考虑在内。
goSearch(event) {
// 得到点击事件的标签,比如点了h1标签a就是h1标签。
let a = event.target;
// 通过解构赋值得到自定义属性值,属性值都是小写的,这里要注意。
let {categoryname,category1id,category2id,category3id} = a.dataset
//因为点击的a标签上都有categoryname,所以做if判断
if(categoryname){
// 准备需要传参的数据
let location = {name:'search'}
let query = {categoryName:categoryname}
// 如果是点击1级标题就带一级id,之后同理
if(category1id){
query.category1Id = category1id
} else if(category2id){
query.category2Id = category2id
}else if(category3id){
query.category3Id = category3id
}
location.query = query
//判断一下有没有params参数,如果有也放在location里
if(this.$route.params){
location.params = this.$route.params
}
// 路由跳转
this.$router.push(location)
}
},
1.在search组件中,加上
2.对TypeNav中的商品列表进行显示与隐藏。给父级div添加v-show,让show初始为true
3.进行路由跳转地址判断,如果跳到其他组件,就让show变为false
mounted() {
if(this.$route.path !='/home'){
this.show = false
}
4.但是这样就一直隐藏了,不能让他显示出来了,所以我们要对大盒子添加@mouseenter和@mouseleave方法。
leaveShow() {
this.currentIndex = -1;
if (this.$route.path != "/home") {
this.show = false;
}
},
enterShow() {
this.show = true;
},
1.用transition将div包住,并给一个name属性
2.在style中,配置过度属性。
.sort-enter {
height: 0;
}
.sort-enter-to {
height: 461px;
}
.sort-enter-active {
transition: all 0.2s linear;
}
1.在src下新建mock文件夹,里面新建banner.json floor.json,将数据粘贴进去。因为需要用到images,所以在public中新建images文件夹,将需要的图片拷贝进去。
2.在mock文件夹下新建mockServe.js,配置mock。
import Mock from 'mockjs'
//引入banner和floor的json文件
//因为webpack对于图片是默认暴露的。
import banner from './banner.json'
import floor from './floor.json'
//使用mock来模拟接口,第一个参数是请求的接口,第二个参数是请求的数据。
Mock.mock('/mock/banner',{code:200,data:banner})
Mock.mock('/mock/floor',{code:200,data:floor})
3.在main.js中引入mockServe,接收参数
import '@/mock/mockServe'
4.封装mock,在api下新建mockAjax.js
import axios from "axios";
//引入nprogress,这是加载条
import nprogress from "nprogress";
//引入nprogress样式
import 'nprogress/nprogress.css'
const mockAjax = axios.create({
baseURL:'/mock',
timeout:5000 //访问大于5s取消
})
//设置请求拦截器
mockAjax.interceptors.request.use(function (config) {
nprogress.start()
return config;
}, function (error) {
return Promise.reject(error);
});
// 设置响应拦截器
mockAjax.interceptors.response.use(function (response) {
nprogress.done()
return response.data;
// return response;
}, function (error) {
return Promise.reject(error);
});
export default mockAjax
5.在index.js文件中,配置mock发送请求接口
//mock数据的axios发请求
import mockAjax from './mockAjax'
export const reqBannerList = () => mockAjax({url:'/banner'})
export const reqFloorList = () => mockAjax({url:'/floor'})
6.在ListContainer中的mounted中发送dispatch。
mounted() {
//发送获取bannerList的请求
this.$store.dispatch("getBannerList");
},
vuex store中引入reqBannerList,配置action和mutation
import {reqBannerList,reqFloorList} from '@/api' //引入请求接口函数
const state = {
bannerList:[],
floorList:[],
}
const actions = {
async getBannerList({commit}){
let result = await reqBannerList()
if(result.code == 200){
commit('BANNERLIST',result.data)
}
},
async getFloorList({commit}){
let result = await reqFloorList()
if(result.code == 200){
commit('FLOORLIST',result.data)
}
},
}
const mutations = {
BANNERLIST(state,bannerList) {
state.bannerList = bannerList
},
FLOORLIST(state,floorList) {
state.floorList = floorList
}
}
7.在组件中配置mapState,在banner处遍历就行了。
1.安装swiper,最好安装5代比较稳定
2.在main.js中引入swiper.css和swiper.js
import 'swiper/css/swiper.css' import 'swiper/js/swiper.js'
3.在组件中引入Swiper
import Swiper from "swiper";
4.初始化swiper(dom中需要有swiper的盒子才可以进行初始化)
var mySwiper = new Swiper(".swiper-container", {
loop: true, // 循环模式选项
// 如果需要分页器
pagination: {
el: ".swiper-pagination",
clickable:true //点击小圆点
},
// 如果需要前进后退按钮
navigation: {
nextEl: ".swiper-button-next",
prevEl: ".swiper-button-prev",
},
});
5.初始化swiper的位置非常的关键。如果在mounted中初始化,会导致没有反应,因为会在获取到数据之前初始化。
如果在watch中监听bannerList的变化,这样也不行。虽然这样bannerList一定是获取到数据了,但是由于v-for是异步操作,所以不能保证v-for已经完成。
所以我们使用watch+nextTick,让dom循环结束后执行该初始化swiper
watch: {
bannerList: {
handler(newValue, oldValue) {
this.$nextTick(() => {
var mySwiper.....
});
},
},
},
在Floor组件中进行遍历。
1.发请求:在api下的index.js中,添加获取search请求,post请求要传入一个对象进去。
export const reqSearchList = (params) => requests({url:'/list',method:'post',data:params})
2.完成vuex中的search.js,注意这里我们要传一个对象进来。
//引入请求数据的函数
import {reqSearchList} from '@/api'
//创建action,mutation,state,getter对象
const state = {
searchList:[],
}
const actions = {
async getSearchList({commit},params = {}){
let result = await reqSearchList(params)
if(result.code == 200){
commit('SEARCHLIST',result.data)
}
},
}
const mutations = {
SEARCHLIST(state,searchList) {
state.searchList = searchList
},
}
const getters = {
goodsList(state){
return state.searchList.goodsList || [] //如果没有访问成功,择返回空数组
},
attrsList(state){
return state.searchList.attrsList || []
},
trademarkList(state){
return state.searchList.trademarkList || []
},
}
export default {
actions,
mutations,
state,
getters
}
3.利用计算属性配置getters。
computed: {
...mapGetters(["goodsList", "trademarkList", "attrsList"]),
},
4.在页面中遍历显示,商品直接在search组件下即可,其他两个在子组件中,所以需要传参
5.在search组件中提交dispatch,但是这里要注意,因为我们要获取多次数据,但是如果像之前一样写在mounted中,就只能获取一次数据,所以我们将dispatch写为一个方法,在mounted中调用这个方法,即可开始就获取数据。(actions和mutations都在上面)
methods: {
getSearch() {
this.$store.dispatch("getSearchList", this.parameter);
},
mounted() {
this.getSearch();
},
6.如上可知,我们穿进去的对象,还没有配置,所以我们要在data中配置parameter对象,对象的内容就是获取过来的10个属性。
data() {
return {
parameter: {
category1Id: "",
category2Id: "",
category3Id: "",
categoryName: "",
keyword: "",
order: "",
pageNo: 1,
pageSize: 10,
props: [],
trademark: "",
},
};
},
7.如果直接把他们传进去,其实是没用的,因为他们都是初始值,根本不知到点了什么id,name,keyword。所以我们要把params和query参数都给合并到此对象中。用Object.assign。
那么这个合并过程要在什么位置发生呢?mounted显然不行,已经挂载了。created也不行,因为这里需要用到data中的数据,所以只能在beforeMount上。
beforeMount() {
//将query和params的参数合并到parameter参数中
Object.assign(this.parameter, this.$route.query, this.$route.params);
},
8.这样我们的parameter中就有了最新的参数,但是我门如何实现,点击不同内容跳转不同的数据内容呢?显然一次获取数据已经不够了,所以我们要多次获取。那么怎么能多次获取呢?用watch监听最合适不过了,一旦我们的$route变化,甭管是params还是query,只要变化都可以监听到,然后发送请求就行了。但是获取完要把数据清空,不然会带有无用数据。
watch: {
$route(newValue, oldValue) {
//将参数合并进来,将后两个合并到第一个。
Object.assign(this.parameter, this.$route.query, this.$route.params);
//只要参数变化,就获取数据
this.getSearch();
//但是访问2id的时候也会把3id带过去,不太好,所以先把id清空。
this.parameter.category1Id = "";
this.parameter.category2Id = "";
this.parameter.category3Id = "";
},
},
9.现在我们已经能实现点击哪跳转到新页面了,但是对于面包屑的处理我们还没有做。首先第一个就是点什么显示什么,也就是parameter中只要有categoryName或者keyword就会显示,所以使用v-if或v-show(因为切换次数会比较多所以选择v-show)。
{{ parameter.categoryName }}
{{ parameter.keyword }}
10.接下来就是面包屑的删除功能,点击叉实现删除。就是对i标签设置一个点击事件,将categoryName清空就行,但是这里注意,如果给一个 ' ',还是会传过去,占有内存,所以我们给一个undefined,这样就不会传过去。
其他的id也要设置为undefined,因为是无用的数据
这样我们实现了点击删除,但是地址栏里的数据还是存在的,所以我们要进行新的路由跳转,先判断有没有params参数,如果有也带上。
//删除分类名称
deleteParameter() {
this.parameter.categoryName = undefined;
//同上,清id,这里可以直接设置为undefined,这样就不会将没用的数据获取过来。
this.parameter.category1Id = undefined;
this.parameter.category2Id = undefined;
this.parameter.category3Id = undefined;
this.getSearch();
//路由跳转
if(this.$route.params){
this.$router.push({"name":"search",params:this.$route.params})
}
},
11.删除关键字的方法和上述方法基本一致,只不过我门在清楚关键字的时候,要将输入框的内容也给清除。因为输入框的内容是在Header组件中,和Search组件是兄弟组件,所以使用全局事件总线$bus最好。在main.js中设置全局事件总线。
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线
},
在Search中$emit,在Header中$on就行了。