Vue2电商前台项目(四):完成Detail详情页模块业务
目录
一、项目开发的步骤
静态=>请求(api)=>vuex=>动态组件
二、配置路由规则+滚动行为
1.配置路由规则
我们引入静态组件之后,得在router里给detail配置路由
import Detail from '../pages/Detail'
{
path:'/detail',
component:Detail,
meta:{show:true}
}
点击图片就进行路由跳转显示产品,所以需要params传参,就得占位
path:'/detail/:skuId',
把装有图片的a标签换成router-link
<router-link :to="`/detail/${good.id}`">
<img :src="good.defaultImg" />
</router-link>
2.滚动行为
现在点击之后滚轮默认在你前一页待的地方,所以我们得设置一下滚轮位置
我们先把路由信息分开写到routers.js里,index再调用看着比较方便
import routes from './routes'
export default new VueRouter({
//配置路由
routes
//kv 一致省略v
scrollBehavior(to, from, savedPosition) {
return { y: 0 }//代表滚动条在最上方
}
})
滚动行为scrollBehavior(to, from, savedPosition),可以设置x、y坐标
三、请求详情页数据并展示数据
1.api接口
//获取信息详情的api
export const reqGoodsInfo = (skuId) => {
return requests({ url: `http://gmall-h5-api.atguigu.cn/api/item/${skuId}`, method: 'get'})
}
2.写Vuex获取数据
还需要一个新的仓库服务detail,新建一个文件夹detail/index.js
import { reqGoodsInfo } from "@/api"
const state = {
goodInfo: {}
}
const mutations = {
GETGOODINFO(state, goodInfo) {
state.goodInfo = goodInfo
}
}
const actions = {
//获取产品信息
async getGoodInfo({ commit }, skuId) {
let result = await reqGoodsInfo(skuId)
if (result.code == 200) {
commit('GETGOODINFO', result.data)
}
}
}
const getters = {}
export default {
state,
actions,
mutations,
getters
}
小仓库建完了之后需要回到大仓库进行合并
import detail from './detail/index'
modules:{
home,
search,
detail
}
3.派发actions
用户点击图片之后触发action,所以应该是detail组件挂载完毕之后触发action
mounted() {
//派发action
console.log(this.$route.params)
this.$store.dispatch('getGoodInfo',this.$route.params.skuId)
},
4.展示动态数据
去仓库当中简化数据,这两个都是对象类型的
const getters = {
categoryView(state){
return state.goodInfo.categoryView||{}
//不加||{}会有个报错,因为数据没回来的时候,{}.categoryView是undefined
//捞回来之后才有
},
skuInfo(state){
return state.goodInfo.skuInfo||{}
}
}
捞数据:
import {mapGetters} from 'vuex'
computed:{
...mapGetters(['categoryView','skuInfo'])
}
然后就能直接用了
<div class="conPoin">
<span v-show="categoryView.category1Name">{{categoryView.category1Name}}</span>
<span v-show="categoryView.category2Name">{{categoryView.category2Name}}</span>
<span v-show="categoryView.category3Name">{{categoryView.category3Name}}</span>
</div>
<h3 class="InfoName">{{skuInfo.skuName}}</h3>
<p class="news">{{skuInfo.skuDesc}}</p>
四、放大镜展示数据
1.Detail父组件把图片数据传给放大镜子组件
上面我们实现了detail组件从仓库中拿数据,我们需要的数据有categoryView(放大镜上面的几个标题)和skuInfo(放大镜右侧手机的介绍)中的,我们要是直接捞的话得state.detail.goodInfo.category[x].category1Id,这个时候就用到getters来简化数据了,mapGetters之后再直接调就可以了。
现在的放大镜组件(Zoom)是detail 的儿子组件,现在zoom需要的数据已经被它父亲detail捞过来了,我们想用就继承过来就行,我们用到的是对象下的skuImageList数组
<!--放大镜效果-->
<Zoom :skuImageList="skuImageList"/>
computed:{
、、、
skuImageList(){
return this.skuInfo.skuImageList||[]
//为了上面写着方便,要不然上面就是skuInfo.skuImageList
}
}
zoom组件继承:
<img :src="imgObj.imgUrl" />
、、、
props:['skuImageList'],
computed:{
imgObj(){
return this.skuImageList[0] || {}
//这里是为了zoom组件写着方便
}
}
所以组getters里写从仓库里继承来的数据的简写,组件里用仓库中的写...mapGetters
组件里面再想简化就写在computed里,自己的简化写在自己组件里
2.解决数据未请求到时获取其属性报错的问题
就是上面提到的,如果没有加||{ }或者[ ],我们在获取数据时第一时间是没有获取到的,后面的代码直接.取它后面的东西肯定是undefined,过一定的时间取到了才会显示,为了避免这样的错误不让它出现undefined,return的时候都加上||
五、放大镜下边的轮播图展示数据
1.添加轮播图图片
现在我们来写detail的另一个儿子组件,跟zoom传的东西是一样的
<!-- 小图列表 -->
<ImageList :skuImageList="skuImageList"/>
继承之后去遍历那些小图
<div class="swiper-slide" v-for="(slide,index) in skuImageList" :key="slide.id">
<img :src="slide.imgUrl">
</div>
2.售卖属性
售卖属性现在还在仓库里面呢,老规矩捞过来
//售卖属性
spuSaleAttrList(state){
return state.goodInfo.spuSaleAttrList||[]
}
...mapGetters(['categoryView','skuInfo','spuSaleAttrList']),
然后先去vue里看detail组件里有没有数据!!
有数据了之后回到属性和属性值那里,结构一样用v-for
<dl v-for="spuSaleAttr in spuSaleAttrList" :key="spuSaleAttr.id">
<dt class="title">{{spuSaleAttr.saleAttrName}}</dt>
<dd changepirce="0" :class="{active:spuSaleAttrValue.isChecked==1}" v-for="spuSaleAttrValue in spuSaleAttr.spuSaleAttrValueList" :key="spuSaleAttrValue.id">
{{spuSaleAttrValue.saleAttrValueName}}
</dd>
</dl>
这里结构比较复杂,一层套一层的,要看清你选的是谁,最后高亮设置为动态的,就完事
六、产品售卖属性的排他操作
点哪个哪个有高亮,得把点击的那个对象还有它所在的数组传参过来,排他就得传他还有他的兄弟们,拿到他父亲就拿到兄弟了
@click="changeActive(spuSaleAttrValue,spuSaleAttr.spuSaleAttrValueList)"
然后开始计算,把数组里面所有的ischecked都=0,只有被点击的那个ischecked=1
methods: {
//产品售卖属性值高亮
changeActive(spuAttrValue,arr){
arr.forEach(item => {
item.isChecked=0
});
spuAttrValue.isChecked=1
}
},
七、放大镜效果实现
我们的轮播图已经引了swiper,main.js也引了swiper的样式了,然后new Swiper写轮播图,这次的数据是父组件传过来的所以数据肯定有了,但是上面有个v-for所以就用nextTick
<div class="swiper-container" ref="cur">
watch: {
skuImageList(newValue, oldValue) {
this.$nextTick(() => {
new Swiper(this.$refs.cur, {
// 如果需要前进后退按钮
navigation: {
nextEl: ".swiper-button-next",
prevEl: ".swiper-button-prev",
},
});
});
},
},
分页器啥的都不用,但是现在的轮播图每次变换都只有一张图片,我们想实现大盒子里放三张,每次点击前进后退按钮就变换三张,通过slider容器
new Swiper(this.$refs.cur, {
slidesPerView: 3, //页面同时显示几个
slidesPerGroup: 3, //一次切换几个
同时点击小图的时候还会有边框,这里我们不用hover,用js写,点谁谁加个类,来个高光
<img :src="slide.imgUrl" :class="{active:currentIndex==index}" @click="changeCurrentIndex(index)"/>
methods: {
changeCurrentIndex(index){
this.currentIndex=index
}
},
然后实现点击哪个小图zoom组件里的大图List就显示小图,他俩是兄弟关系,用全局事件
changeCurrentIndex(index){
this.currentIndex=index
//通知自己的兄弟当前索引值为几
this.$bus.$emit('getIndex',this.currentIndex)
}
来zoom里接收:
data(){
return {
currentIndex:0
}
},
computed:{
imgObj(){
return this.skuImageList[this.currentIndex] || {}
}
},
mounted() {
//获取索引,它是收方用on
this.$bus.$on('getIndex',(index)=>{
this.currentIndex=index
})
},
之前返回的图片是写死的,默认第一个,现在得设置一个data默认为0,点击哪个索引号就给data
最后让遮盖层随着鼠标的移动而移动,大图也换成和遮盖图一样的图片
<!-- 鼠标移动事件 -->
<div class="event" @mousemove="handler"></div>
<div class="big">
<img :src="imgObj.imgUrl" ref="big"/>
</div>
<!-- 遮罩层 -->
<div class="mask" ref="mask"></div>
methods: {
handler(event){
let mask=this.$refs.mask
let big=this.$refs.big
let left=event.offsetX-mask.offsetWidth/2
let top=event.offsetY-mask.offsetHeight/2
//约束范围
if(left<=0) left=0
if(left>=mask.offsetWidth) left=mask.offsetWidth
if(top<=0) top=0
if(top>=mask.offsetHeight) top=mask.offsetHeight
mask.style.left=left+'px'
mask.style.top=top+'px'
//大图是mask图的二倍大小
big.style.left=-2*left+'px'
big.style.top=-2*top+'px'
}
},
八、购买产品个数的加减操作
1.收集数量并控制个数>0
回到detail组件里的购买个数。个数是响应式的而且默认值为2,根据经验写在data里
data(){
return {
//购买产品的个数
skuNum:1
}
},
这个数据后续我们要拿到,收集数据:ref或者v-model
<input autocomplete="off" class="itxt" v-model="skuNum"/>
两个按钮加减:直接一个三元运算符控制数量>=1,妙蛙
<a href="javascript:" class="plus" @click="skuNum++">+</a>
<a href="javascript:" class="mins" @click="skuNum>1?skuNum--:skuNum=1">-</a>
2.校验用户自己输入的值
得控制用户输入的是数字而且用户输入之后得把它输入的赋值给skuNum
(1)直接给表单加type="number",确实除了数字而且是整型的其他都加不了了,结果表单右边又来了一个数字加减框
(2)本来我想直接上parseInt,但是这样它把字母在前的识别为NAN,数字在前的就取了前面的那个数字,我们的需求是只要有字母都不合格
let values=parseInt(e.target.value)
if(values<1||values==NaN) values=1
this.skuNum=values
(3)乘1过滤掉文字或者字母,乘出来是NaN
changeSkuNum(e) {
let value = e.target.value * 1;
if (value < 1 || isNaN(value)) {
this.skuNum = 1;
} else {
this.skuNum = parseInt(value);
}
},
更多推荐
所有评论(0)