Skip to content
On this page

实战篇 5-商品详情页面开发


商品详情页面开发

上一章我们已经学习了列表页的开发,从列表页面里选择感兴趣的商品,进入商品详情页,查看商品的相关信息。本章我们主要讲商品详情页的开发。

商品详情页在电商应用里扮演一个重要的角色,它可以是简单的也可以是复杂的。说重要是因为它是黄金购物流程上的重要一环,首页、搜索页、列表页、营销活动最后的落地页都是商品详情页;简单的可以是由几张商品图和文字说明及加一个「加入购物车」按钮或者一个「去结算」按钮就算详情页了,复杂的详情页,包含页面展示商品图、商品视频、商品属性、商品介绍、评论、商品的关联商品、商品套餐、预售、抢购、历史浏览、推荐购买等,非常复杂。

页面布局

本项目的商品详情页比较简单,只有展示商品图、商品视频、商品属性、商品介绍,没有预售和抢购状态,也没有评论,我们先看一下效果图。

按模块分有商品主图、介绍、店铺信息、加车浮层四个模块,店铺信息模块比较简单,我们不讲解,其余三个模块我们逐个讲解。

商品主图模块

商品主图是一个轮播模块,有商品图和视频组成,其中商品图又有长图和方图。小程序有个 Swiper 组件,可以方便地实现该需求。

<Swiper className='detail_swiper'
  indicator-dots='true'
  indicator-color='#ddd'
  indicator-active-color='#232323'
  current='0'
  interval='3000'
  duration='300'
  circular='true'
  onChange={this.swpierChange} >

    {srcs.map((item) => {
      return <SwiperItem className='detail_swiper_item'>
          <Image src={item} onClick={this.swiperClick} mode='widthFix' />
        </SwiperItem>
      })}
      {mainVideoImgUrl && <SwiperItem className='detail_swiper_item detail_swiper_item_video' >
        {this.state.isPlayVideo ? <Video className={ isIpx ? 'detail_video detail_video_ipx' : 'detail_video' } src={mainVideoPath} autoplay='true' show-fullscreen-btn='true' /> : <Image className='detail_video_cover' onClick={this.playVideo} src={mainVideoImgUrl} mode='widthFix' />}
      </SwiperItem>}
</Swiper>

这个模块宽高是 750x960,在 iPhone X 下,是全屏的宽度,按等比例缩放在高度也是恰到好处可以看到商品的标题和价格,但是在非 iPhone X 机器下高度就差不多一屏了,首屏看不到商品的标题和价格,为了更好地体验,两边留白 40px ,宽高为 670x860。

轮播模块放视频,会存在一些问题,从文档里 Video 组件 说明的底部的 Bug&Tip 可以看到:

  1. Tip: Video 组件是由客户端创建的原生组件,它的层级是最高的,不能通过 z-index 控制层级。
  2. Tip: 请勿在 scroll-view、Swiper、picker-view、movable-view 中使用 Video 组件。
  3. Tip: CSS 动画对 Video 组件无效。

文档里没有说为什么不能在 Swiper 里用,所以只能自己尝试了,试了一下才发现,如果在 Swiper 里使用就会导致视频一直存在,也是符合 Tip 1 里所说的 Video 组件是由客户端创建的原生组件,它的层级最高,所以已经超出了 Swiper 组件本身了。

那我们有没有办法实现业务方的需求呢?我们看一下视频在 Swiper 下的 Bug,发现即使视频一直存在也是只存在于 Swiper 区域,那么我们控制它显示和隐藏是不是就可以达到目的。最后的实现是渲染主图轮播的时候,在显示视频的那一帧放入一张视频封面,点击封面的时候,播放视频,切换轮播的时候关闭视频显示封面。

// jsx
<Swiper className='detail_swiper' onChange={this.swpierChange} >
  {mainVideoImgUrl && <SwiperItem className='detail_swiper_item detail_swiper_item_video' >
    {this.state.isPlayVideo ? <Video className={ isIpx ? 'detail_video detail_video_ipx' : 'detail_video' } src={mainVideoPath} autoplay='true' show-fullscreen-btn='true' /> : <Image className='detail_video_cover' onClick={this.playVideo} src={mainVideoImgUrl} mode='widthFix' />}
  </SwiperItem>}
</Swiper>

// js 切换的时候把视频关了
swpierChange = (e) => {
  if (this.state.isPlayVideo) {
    this.setState({isPlayVideo: false})
  }
}

商品介绍

商品介绍分四个小模块:商品描述、编辑笔记、尺寸说明、服务说明,需要控制它保证只有一个模块的状态是打开的,这个比较简单。

this.state = {
  detailInfoRow: [
    {
      name: 'desc',
      title: '商品描述',
      open: true
    },
    {
      name: 'editor',
      title: '编辑笔记',
      open: false
    },
    {
      name: 'size',
      title: '尺寸说明',
      open: false
    },
    {
      name: 'service',
      title: '服务说明',
      open: false
    }
  ]
}

// jsx
{detailInfoRowCopy.map((item, idx) => {
  return <View className='detail_intro'>
    {item.show && <View className={'detail_intro_row ' + item.name}>
      <View className='detail_intro_hd' data-id={item.name} onClick={this.toggleFold.bind(this, idx)}>
        <Text className='detail_intro_title'>
          {item.title}
        </Text>
        <Text className='detail_intro_title_icon'>
          {item.open ? '-':'+'}
        </Text>
      </View>
      {item.open && idx < 3 && <View className='detail_intro_bd'>
        ...内容
      </View>}
  </View>
})}

// 控制显隐
toggleFold = (idx, e) => {
  this.state.detailInfoRow[idx].open = !this.state.detailInfoRow[idx].open
  this.setState({})
}

加车浮层

加车浮层看着简单,要展示的内容还是挺丰富的,我们先来看效果图:

了解一下模块的交互,然后我们挑几个要点来讲:

  • 点击加入购物车唤起浮层
  • 浮层有商品信息区:显示小图、价格、商品编号
  • 浮层有地区及运费,自营运费固定,非自营运费由接口取得
  • 优惠券信息,显示当前商品可用优惠券,有多张优惠券时,最多展示 2行,点击券说明下拉展示全部优惠券,每个券占一行,每行展示满减金额及使用限制及限制说明文案。无可用券则隐藏,可用优惠券这一区域
  • 销售属性,按类目显示参数名称「颜色」「尺码」等;单 SKU 时显示 SKU 的参数并选中;SPU 时当前 SKU 的参数为选中状态,从搜索或列表进入时默认选中主 SKU,其他 SKU 的参数可选和不可选,无货或参数不全时不可选;点击可点参数时相当于切换 SKU,层内商品图、价格、编号 、运费、优惠券数据刷新
  • 尺码有三种状态:有货未选择:“黑字白底”按钮,有货已选择:“白字黑底”按钮,无货未选择:“深灰字浅灰底”按钮
  • 关闭加车浮层,判断层内 SKU 与层外是否一至,不一至则刷新

浮层

唤起加车浮层的时候,我们在页面里使用一个 View 来做背景半透明的蒙层,当点击浮层以外的区域就把浮层关闭,其实浮层以外的区域就是蒙层,我们可以在蒙层上设置点击事件,点击就关闭浮层。

唤起浮层出现蒙层,然后上下滑动页面,页面还是会滚动的,因为页面的内容大于一屏,存在滚动条,要处理这个问题需要在根节点使用 ScrollView 组件把内容包起来,然后当唤起浮层的时候设置 scrollYfalse。这样当出现浮层的时候,整个页面就不能滚动了。

<ScrollView style={viewStyle} scrollY={!this.state.addCartLayer} enableBackToTop={true}>
  <View>这里是内容......</View>
</ScrollView>

商品属性切换

当我们切换商品的颜色的时候,其实是切换了一个新的商品,而商品的唯一 ID 就是 skuId , 因此商品属性我们把 skuId 打在选项 Radio 的属性上,当点击改选项的时候获取该点击的对象里的值,然后拉取新的数据重新渲染浮层里的内容。

// jsx
<RadioGroup className='radiogroup' onChange={this.spuChange}>
  {item.props && item.props.map(prop => {
    return <Label className={prop.className}>
      <Radio className='radio' value={prop.skuId} checked={prop.selected} />
      {prop.name}
    </Label>
  }) }
</RadioGroup>
// js
spuChange (e) => {
  let skuId = e.detail.value // 拿到skuId
}

属性切换的时候数据发生变化,按钮的样式也会跟着变化,按钮有三个状态:正常的灰边白色背景黑色文字,选中态为黑背景白字,无货的状态为灰背景灰字,稍微复杂,我们使用样式的类名来控制。

小结

商品的信息都是从接口拉取出来然后展示,虽然比较丰富,但都是展示型的内容,相对来说还是比较简单,这里就不一一介绍了,大家可以移步 Demo 看看实现代码。

在下一章节中我们将介绍购物车的开发。