慕尚花坊 --- 页面编写及交互效果

1. 首页

0. 首页配置

1. 数据请求 API

1. 定义接口 API 函数

miniprogram/api/index.js

import http from '../utils/http'

/**
 * @description 获取首页轮播图数据
 */

/**
 * @description 通过并发请求获取首页的数据, 提升页面的渲染速度
 */
export const reqIndexData = () => {
  // 使用 Promise.all() 发送并发请求
  // return Promise.all(http.get('/index/findBanner'), http.get('/index/findCategory1'), http.get('/index/advertisement'), http.get('/index/findListGoods'), http.get('/index/findRecommendGoods'))

  // 使用封装的 http.all() 发送并发请求
  return http.all(http.get('/index/findBanner'), http.get('/index/findCategory1'), http.get('/index/advertisement'), http.get('/index/findListGoods'), http.get('/index/findRecommendGoods'))
}

pages/index/index.js

import {
  reqIndexData
} from '../../api/index'
Page({
  data: {
    bannerList: [], // 轮播图
    categoryList: [], // 商品导航
    activeList: [], // 活动区
    hotList: [], // 热门商品(人气推荐)
    guessList: [] // 猜你喜欢
  },
  // 获取首页数据
  async getIndexData() {
    const res = await reqIndexData()
    this.setData({
      bannerList: res[0].data,
      categoryList: res[1].data,
      activeList: res[2].data,
      hotList: res[3].data,
      guessList: res[4].data,
    })
  },
  // 监听页面的加载
  onLoad() {
    // 在页面加载以后, 调用获取首页数据的方法
    this.getIndexData()
  }
})

2. 全局样式

pages/index/inde.scss

// 整个页面样式
.index-container {
  background-color: #efefef !important;
  position: relative;

  // 首页背景图样式
  .window-bgc {
    position: absolute;
    width: 100%;
    height: 450rpx;
    top: -230rpx;
    border-radius: 30rpx;
    background-color: #f3514f;
  }
}

1. 轮播图组件

0. 效果图

1. 创建组件

1. pages/index/banner 创建组件 banner

2. 当前 index 页面注册 banner 组件

pages/index/index.json

{
  "usingComponents": {
    "banner": "./banner/banner"
  }
}

3. 当前 index 页面使用 banner 组件

pages/index/index.wxml

<!-- 轮播图区 -->
<banner />

2. 页面构建

pages/index/banner/banner.wxml

<!-- 轮播图区 -->
<view class="swiper">
  <swiper autoplay circular indicator-dots interval="5000" indicator-color="#fff" indicator-active-color="#f3514f" class="swiper-warpper">
      <swiper-item class="swiper-item">
          <navigator class="navigator" url="/pages/goods/detail/detail?goodId=id">
              <image class="img" src="../../assets/swipers/swiper-1.jpg" mode="" />
          </navigator>
      </swiper-item>
      <swiper-item class="swiper-item">
          <navigator class="navigator" url="/pages/goods/detail/detail?goodId=id">
              <image class="img" src="../../assets/swipers/swiper-2.jpg" mode="" />
          </navigator>
      </swiper-item>
      <swiper-item class="swiper-item">
          <navigator class="navigator" url="/pages/goods/detail/detail?goodId=id">
              <image class="img" src="../../assets/swipers/swiper-3.jpg" mode="" />
          </navigator>
      </swiper-item>
  </swiper>
</view>

pages/index/banner/banner.scss

// 轮播图样式
.swiper {
  .swiper-warpper{
    height: 360rpx;
    padding: 20rpx 16rpx;
    .swiper-item{
      border-radius: 10rpx;
      .navigator{
        width: 750rpx;
        background-color: lightcoral;
        .img {
          width: 100%;
        }
      }
    }
  }
}

3. 数据交互

1. 将 index 请求回来的数据, 传递到组件 banner 组件内

pages/index/index.wxml

<banner bannerList="{{ bannerList }}" />

2. banner 组件接收数据

pages/index/banner/banner.js

Component({

  /**
   * 组件的属性列表
   */
  properties: {
    bannerList: {
      type: Array,
      value: []
    }   // 接受数据
  },
})

3. 循环从服务器请求回来的数据, 生成页面

pages/index/banner/banner.wxml

<!-- 轮播图区 -->
<view class="swiper">
  <swiper autoplay circular interval="5000" indicator-active-color="#f3514f" bindchange="changeSwiper" class="swiper-warpper">
    <!-- 通过 block 标签对轮播图数据进行循环渲染 -->
    <block wx:for="{{ bannerList }}" wx:key="id">
      <swiper-item class="swiper-item">
        <navigator class="navigator" url="/pages/goods/detail/detail?goodId={{ item.id }}}">
          <image class="img" src="{{ item.imageUrl }}" mode="" />
        </navigator>
      </swiper-item>
    </block>
  </swiper>
</view>

4. 自定义圆点样式

1. 去除轮播图圆点图标的默认效果,和选中时的颜色

pages/index/banner/banner.wxml

<view class="swiper">
  <swiper 
  autoplay 
  circular 
  interval="5000" 
  indicator-active-color="#f3514f"
   class="swiper-warpper">
    <block wx:for="{{ bannerList }}" wx:key="id">
      <swiper-item class="swiper-item">
        <navigator class="navigator" url="/pages/goods/detail/detail?goodId={{item.id}}">
          <image class="img" src="{{ item.imageUrl }}" mode="" />
        </navigator>
      </swiper-item>
    </block>
  </swiper>

  <!-- 轮播图的面板指示点, 因为面板指示点不支持自定义, 所以只能通过自定义结构的方式 -->
  <view class="indicator">
    <!-- active 类名: 当前被激活的面板指示点颜色 -->
    <!-- cricle 类名: 默认的面板指示点颜色 -->
    <text wx:for="{{ bannerList.length }}" wx:key="id" class="rectangle">
    </text>
  </view>
</view>

pages/index/banner/banner.scss

// 轮播图样式
.swiper {
  position: relative;
  .swiper-warpper {
    height: 360rpx;
    padding: 20rpx 16rpx;
    position: relative;
    z-index: 10;

    .swiper-item {
      border-radius: 10rpx;

      .navigator {
        width: 750rpx;

        .img {
          width: 100%;
        }
      }
    }
  }
  // 圆点样式
  .indicator {
    position: absolute;
    top: 360rpx;
    left: 33.33%;
    z-index: 100;
    display: flex;
    .text {
      margin-right: 20rpx;
      width: 60rpx;
      height: 10rpx;
      z-index: 100;
    }
    .active {
      background-color: #f3514f;
    }
    .rectangle {
      background-color: #fff;
    }
  }
}

pages/index/banner/banner.wxml

<view class="swiper">
  <swiper autoplay circular interval="5000" indicator-active-color="#f3514f" bindchange="changeSwiper" class="swiper-warpper">
    <!-- 通过 block 标签对轮播图数据进行循环渲染 -->
    <block wx:for="{{ bannerList }}" wx:key="id">
      <swiper-item class="swiper-item">
        <navigator class="navigator" url="/pages/goods/detail/detail?goodId=id">
          <image class="img" src="{{ item.imageUrl }}" mode="" />
        </navigator>
      </swiper-item>
    </block>
  </swiper>

  <view class="indicator">
    <!-- 每次轮播图切换时, 获取到当前轮播图的索引, 跟当前索引对比, 如果一致则添加选中效果 -->
    <text wx:for="{{ bannerList.length }}" wx:key="id" class="{{ index === activeIndex ? 'text active' : 'text rectangle'}}">
    </text>
  </view>
</view>

pages/indx/banner/banner.js

Component({
  data: {
    activeIndex: 0  // 默认为0, 默认选中第一个圆点 
  },
  methods: {
    // 每次轮播图切换时,触发 change 事件
    changeSwiper(event){
      this.setData({
        activeIndex: event.detail.current
      })
    }
  }
})

5. 页面跳转配置

1. 创建 goods 目录及页面

{
  "pages": [
    "pages/goods/goods"
  ]
}

2. 创建 pages/goods/detail 组件

3. 配置 pages/goods/detail组件全局路径

app.json

{
  "pages": [
    "pages/goods/detail/detail"
  ],
}

2. 商品导航区

0. 效果图

1. 创建组件

1. 创建 index/entrance 组件

2. 注册 index/entrance 组件

pages/index/index.json

{
  "usingComponents": {
    "banner": "./banner/banner",
    "entrance": "./entrance/entrance"    // 注册
  }
}

3. 当前 index 页面使用 entrance 组件

pages/index/index.wxml

<!-- 商品导航区 -->
<entrance />

2. 页面构建(字体图标方式)

1. 字体图标 --- 阿里云矢量图标库

https://www.iconfont.cn/

2. 将想要的图标加到项目中

3. 点击 项目设置, 勾选 base64

4. 生成代码后,点击复制代码

5. 在项目根目录下创建 iconfont/iconfont.scss

6. 在 pages/index/entrance/entrance.scss 中导入 iconfont.scss

@import "./iconfont/iconfont.scss";    // 注意一定要分号结尾

7. 页面结构和样式

pages/index/entrance/entrance.wxml

<!-- 快速导航栏 -->
<view class="entrance">
  <view class="entrance-item">
    <navigator class="entrance-nav" url="">
      <text class="iconfont icon-meiguihua"></text>
      <text>鲜花玫瑰</text>
    </navigator>
  </view>
  <view class="entrance-item">
    <navigator class="entrance-nav" url="">
      <text class="iconfont icon-meiguihua"></text>
      <text>鲜花玫瑰</text>
    </navigator>
  </view>
  <view class="entrance-item">
    <navigator class="entrance-nav" url="">
      <text class="iconfont icon-meiguihua"></text>
      <text>鲜花玫瑰</text>
    </navigator>
  </view>
  <view class="entrance-item">
    <navigator class="entrance-nav" url="">
      <text class="iconfont icon-meiguihua"></text>
      <text>鲜花玫瑰</text>
    </navigator>
  </view>
  <view class="entrance-item">
    <navigator class="entrance-nav" url="">
      <text class="iconfont icon-meiguihua"></text>
      <text>鲜花玫瑰</text>
    </navigator>
  </view>
  <view class="entrance-item">
    <navigator class="entrance-nav" url="">
      <text class="iconfont icon-meiguihua"></text>
      <text>鲜花玫瑰</text>
    </navigator>
  </view>
  <view class="entrance-item">
    <navigator class="entrance-nav" url="">
      <text class="iconfont icon-meiguihua"></text>
      <text>鲜花玫瑰</text>
    </navigator>
  </view>
  <view class="entrance-item">
    <navigator class="entrance-nav" url="">
      <text class="iconfont icon-meiguihua"></text>
      <text>鲜花玫瑰</text>
    </navigator>
  </view>
  <view class="entrance-item">
    <navigator class="entrance-nav" url="">
      <text class="iconfont icon-meiguihua"></text>
      <text>鲜花玫瑰</text>
    </navigator>
  </view>
  <view class="entrance-item">
    <navigator class="entrance-nav" url="">
      <text class="iconfont icon-meiguihua"></text>
      <text>鲜花玫瑰</text>
    </navigator>
  </view>
</view>

pages/index/entrance/entrance.scss

@import './iconfont/iconfont.scss';

// 标题快速导航样式
.entrance {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  padding: 16rpx;
  margin: 0 16rpx;
  background-color: #fff;
  border-radius: 10rpx;
  font-size: 24rpx;
  height: 260rpx;

  .entrance-item {
    .entrance-nav {
      width: 120rpx;
      display: flex;
      flex-direction: column;
      align-items: center;

      text {
        &:nth-of-type(2) {
          margin-top: 12rpx;
        }
      }
    }

  }

  // 字体图标样式
  .iconfont {
    font-size: 70rpx;
    color: #efefef;
    background-color: #f3514f;
    border-radius: 25rpx;
  }
}

3. 页面构建(图片方式)

pages/entrance/entrance.wxml

<!-- 快速导航栏 -->
<view class="entrance">
  <view class="entrance-item">
    <navigator class="entrance-nav" url="/pages/good/list/list?categoryId=1">
      <image class="nav-img" src="../../../assets/categorys/cate-1.png"/>
      <text class="nav-text">鲜花玫瑰</text>
    </navigator>
  </view>
  <view class="entrance-item">
    <navigator class="entrance-nav" url="/pages/good/list/list?categoryId=1">
      <image class="nav-img" src="../../../assets/categorys/cate-1.png"/>
      <text class="nav-text">鲜花玫瑰</text>
    </navigator>
  </view>
  <view class="entrance-item">
    <navigator class="entrance-nav" url="/pages/good/list/list?categoryId=1">
      <image class="nav-img" src="../../../assets/categorys/cate-1.png"/>
      <text class="nav-text">鲜花玫瑰</text>
    </navigator>
  </view>
  <view class="entrance-item">
    <navigator class="entrance-nav" url="/pages/good/list/list?categoryId=1">
      <image class="nav-img" src="../../../assets/categorys/cate-1.png"/>
      <text class="nav-text">鲜花玫瑰</text>
    </navigator>
  </view>
  <view class="entrance-item">
    <navigator class="entrance-nav" url="/pages/good/list/list?categoryId=1">
      <image class="nav-img" src="../../../assets/categorys/cate-1.png"/>
      <text class="nav-text">鲜花玫瑰</text>
    </navigator>
  </view>
  <view class="entrance-item">
    <navigator class="entrance-nav" url="/pages/good/list/list?categoryId=1">
      <image class="nav-img" src="../../../assets/categorys/cate-1.png"/>
      <text class="nav-text">鲜花玫瑰</text>
    </navigator>
  </view>
  <view class="entrance-item">
    <navigator class="entrance-nav" url="/pages/good/list/list?categoryId=1">
      <image class="nav-img" src="../../../assets/categorys/cate-1.png"/>
      <text class="nav-text">鲜花玫瑰</text>
    </navigator>
  </view>
  <view class="entrance-item">
    <navigator class="entrance-nav" url="/pages/good/list/list?categoryId=1">
      <image class="nav-img" src="../../../assets/categorys/cate-1.png"/>
      <text class="nav-text">鲜花玫瑰</text>
    </navigator>
  </view>
  <view class="entrance-item">
    <navigator class="entrance-nav" url="/pages/good/list/list?categoryId=1">
      <image class="nav-img" src="../../../assets/categorys/cate-1.png"/>
      <text class="nav-text">鲜花玫瑰</text>
    </navigator>
  </view>
  <view class="entrance-item">
    <navigator class="entrance-nav" url="/pages/good/list/list?categoryId=1">
      <image class="nav-img" src="../../../assets/categorys/cate-1.png"/>
      <text class="nav-text">鲜花玫瑰</text>
    </navigator>
  </view>
</view>

pages/index/entrance/entrance.scss

@import './iconfont/iconfont.scss';

// 标题快速导航样式
.entrance {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  padding: 16rpx;
  margin: 0 16rpx;
  background-color: #fff;
  border-radius: 10rpx;
  font-size: 24rpx;
  height: 260rpx;

  .entrance-item {
    .entrance-nav {
      width: 120rpx;
      display: flex;
      flex-direction: column;
      align-items: center;

      .nav-img {
        width: 90rpx;
        height: 90rpx;
      }
    }
    &:nth-child(n+6) {     // 第二排的标签
      margin-top: 25rpx;
    }
  }
}

4. 数据交互

1. 将 index 请求回来的数据, 传递到组件 banner 组件内

pages/index/index.wxml

<entrance categoryList="{{ categoryList }}"/>

2. entrance 组件接收数据

pages/index/entrance/entrance.js

Component({

  /**
   * 组件的属性列表
   */
  properties: {
    categoryList: {
      type: Array,
      value: []
    }   // 接受数据
  },
})

3. 循环生成标签

pages/index/entrance/entrance.wxml

<!-- 快速导航栏 -->
<view class="entrance">
  <view class="entrance-item" wx:for="{{ categoryList }}" wx:key="id">
    <navigator class="entrance-nav" url="/pages/goods/list/list?categoryId={{ item.id }}">
      <image class="nav-img" src="{{ item.imageUrl }}"/>
      <text class="nav-text">{{ item.name }}</text>
    </navigator>
  </view>
</view>

5. 页面跳转配置

1. 创建 pages/goods/list 组件

2. 配置 pages/goods/list组件全局路径

app.json

{
  "pages": [
    "pages/goods/list/list",
  ],
}

3. 活动区

pages/index/index.wxml

<!-- 活动区 -->
<view class="content">
  <view class="left-content">
    <navigator url="/pages/good/list/list?category2Id={{ activeList[0].category2Id }}">
      <image src="{{ activeList[0].imageUrl }}" mode="" />
    </navigator>
  </view>
  <view class="right-content">
    <navigator url="/pages/good/list/list?category2Id={{ activeList[1].category2Id }}">
      <image src="{{ activeList[1].imageUrl }}" mode="" />
    </navigator>
    <navigator url="/pages/good/list/list?category2Id={{ activeList[2].category2Id }}">
      <image src="{{ activeList[2].imageUrl }}" mode="" />
    </navigator>
  </view>
</view>

pages/index/index.scss

// 活动区
.content {
  height: 400rpx;
  display: flex;
  margin: 16rpx;
  background-origin: content-box;
  background-color: #fff;
  .left-content {
    width: 360rpx;
    border-radius: 10rpx;
    margin-right: 10rpx;
    navigator {
      image {
        width: 100%;
        height: 400rpx;
      }
    }

  }

  .right-content {
    navigator {
      image {
        width: 348rpx;
        border-radius: 10rpx;
        height: 196rpx;

        &:first-child {
          margin-top: 0;
        }
      }
    }

  }
}

4. 猜你喜欢

0. 效果图

1. 创建全局组件

1. 创建 components/goods-list 全局组件

2. 创建 components/goods-card 全局组件: 单独一个商品

3. 在 index 页面注册 goods-list 全局组件

pages/index/index.json

{
  "usingComponents": {
    "goods-list": "../../components/goods-list/goos-list"
  }
}

4. 在 index 页面使用 goods-list 组件

pages/index/index.wxml

<!-- 猜你喜欢 -->
<goods-list />
<!-- 人气推荐 -->
<goods-list />

5. 在 goods-list 组件注册 goods-card 全局组件

components/goods-list/goods-list.json

{
  "usingComponents": {
    "goods-card": "../goods-card/goods-card"
  }
}

6. 在 goods-list 页面使用 goods-card 组件

components/goods-list/goods-list.wxml

<goods-card goodItem="{{ item }}"></goods-card>

2. 页面构建

components/goods-list/goods-list.wxml

<!-- 商品列表 -->
<view class="goods_container">
  <!-- 标题 -->
  <view class="goods_title">{{ title }}</view>

  <!-- 列表区域 -->
  <view class="goods_card_list">
    <goods-card></goods-card>
  </view>

  <!-- 查看更多 -->
  <view class="goods_more">查看更多</view>
</view>

components/goods-list/goods-list.scss

.goods_container {
  width: 100%;

  // 标题
  .goods_title {
    padding: 20rpx 16rpx;
    align-items: center;
    display: flex;
    justify-content: center;
    font-size: 40rpx;
    font-weight: 1000;
  }

  // 商品卡片列表
  .goods_card_list {
    display: flex;
    flex-wrap: wrap
  }
  
  // 查看更多
  .goods_more {
    padding: 20rpx 16rpx;
    align-items: center;
    display: flex;
    justify-content: center;
    font-size: 25rpx;
    font-weight: 100;
    background-color: #fff;
    border-radius: 30rpx;
    margin: 20rpx 16rpx;
  }
}

iconfont/iconfont.cscc

@font-face {
  font-family: "iconfont"; /* Project id 4590971 */
  src: 
       url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'),
       url('//at.alicdn.com/t/c/font_4590971_zc6rr70adlo.woff?t=1719306935805') format('woff'),
       url('//at.alicdn.com/t/c/font_4590971_zc6rr70adlo.ttf?t=1719306935805') format('truetype');
}

.iconfont {
  font-family: "iconfont" !important;
  font-size: 16px;
  font-style: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.icon-jiarugouwuche:before {
  content: "\e615";
}

.icon-ic_jiarugouwuche:before {
  content: "\e69c";
}

.icon-meiguihua:before {
  content: "\e602";
}

.icon-huahonghuazhiwuhuaduo:before {
  content: "\e622";
}

components/goods-card/goods-card.wxml

<!-- 列表分类卡片 -->
<view class="goods_card_container">
  <!-- 跳转商品详情 -->
  <navigator class="navigator_nav" url="/pages/goods/detail/detail?goodsId={{goodItem.id}}">
    <image class="good_img" src="{{ goodItem.imageUrl }}" mode="" />
    <view class="title">
      <text class="title_txt">{{ goodItem.material }}</text>
    </view>
    <view class="content">
      <text class="content_txt">{{ goodItem.floralLanguage }}</text>
    </view>
    <view class="bottom">
      <view class="bottom_container">
        <view class="xian">
          <text class="text">¥</text>{{ goodItem.price }}
        </view>
        <view class="yuan">
          <text class="text">¥</text>{{ goodItem.marketPrice }}
        </view>
        <view class="in_cart">
          <navigator url="">
            <text class="iconfont icon-jiarugouwuche"></text>
          </navigator>
        </view>
      </view>
    </view>
  </navigator>
</view>

components/goods-card/goods-card.scss

@import '../../iconfont/iconfont.scss';

.goods_card_container {
  margin: 20rpx 16rpx;
  width: 340rpx;
  height: 590rpx;
  background-color: #fff;

  .navigator_nav {
    width: 100%;

    .good_img {
      width: 100%;
      height: 350rpx;
    }

    .title {
      width: 100%;
      font-size: 32rpx;
      font-weight: 1000;
      padding: 20rpx 16rpx;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
	
    .content {
      font-size: 25rpx;
      font-weight: 100;
      margin: 30rpx 0;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      padding: 0 16rpx;
    }
      
    // 底部价格 + 购物车

    .bottom {
      padding: 0 16rpx;

      .bottom_container {
        display: flex;
        align-items: center;

        .xian {
          color: #f3514f;
        }

        .yuan {
          margin-left: 20rpx;
          font-size: 16rpx;
          text-decoration: line-through;
        }
      }
    }
	
    // 加入购物车
    .in_cart {
      background-color: #f3514f;
      border-radius: 50%;
      width: 50rpx;
      height: 50rpx;
      text-align: center;
      margin-left: 90rpx;
      .iconfont {
        color: #fff;
      }
    }
  }

}

3. 数据交互

1. 将 index 请求回来的数据, 传递到组件 goods-list 组件内

pages/index/index.wxml

<!-- 猜你喜欢 -->
<goods-list title="猜你喜欢" list="{{ guessList }}"/>

2. goods-list 组件接收数据

components/goods-list/goods-list.js

Component({
  properties: {
    // 列表标题
    title: {
      type: String,
      value: ''
    },
    // 列表数据
    list: {
      type: Array,
      value: []
    }
  },
})

3. 根据数据循环生成商品卡片

components/goods-list/goods-list.wxml

<!-- 商品列表 -->
<view class="goods_container">
  <!-- 标题 -->
  <view class="goods_title">{{ title }}</view>

  <!-- 列表区域 -->
  <view class="goods_card_list">
  <block wx:for="{{ list }}" wx:key="id">
    <goods-card goodItem="{{ item }}"></goods-card>
  </block>
  </view>

  <!-- 查看更多 -->
  <view class="goods_more">查看更多</view>
</view>

4. 渲染商品卡片数据

components/goods-card/goods-card.wxml

<!-- 列表分类卡片 -->
<view class="goods_card_container">
  <navigator class="navigator_nav" url="">
    <image class="good_img" src="{{ goodItem.imageUrl }}" mode="" />
    <view class="title">
      <text class="title_txt">{{ goodItem.material }}</text>
    </view>
    <view class="content">
      <text class="content_txt">{{ goodItem.floralLanguage }}</text>
    </view>
    <view class="bottom">
      <view class="bottom_container">
        <view class="xian">
          <text class="text">¥</text>{{ goodItem.price }}
        </view>
        <view class="yuan">
          <text class="text">¥</text>{{ goodItem.marketPrice }}
        </view>
        <view class="in_cart">
          <navigator url="">
            <text class="iconfont icon-ic_jiarugouwuche"></text>
          </navigator>
        </view>
      </view>
    </view>
  </navigator>
</view>

5. 人气推荐

pages/index/index.wxml

<goods-list title="人气推荐" list="{{ hotList }}"/>

6. 骨架屏

1. 什么是骨架屏

骨架屏是页面的一个空白版本, 开发者会使用 CSS 绘制一些灰色的区块, 将页面内容大致够了出轮廓,通常会在页面完全渲染之前, 将骨架屏代码进行展示, 待数据加载完成后, 再替换成真实的内容, 骨架屏的设置旨在优化用户体验

在进行项目开发时, 我们需要手工维护骨架屏的代码, 当业务变更时, 同样需要对骨架屏代码进行调整, 为了方便开发者进行骨架屏的绘制, 开发者工具提供了自动生成骨架屏代码的能力

2. 使用步骤

1. 使用微信开发者工具为当前正在预览的页面生成骨架屏代码, 工具入口位于模拟器面板右下角的三点处

2. 生成骨架屏后的页面样式

3. index 下新建skeleton文件夹, 用于存放骨架屏代码

4. 当前正在预览的页面.wxml 中引入模版

pages/index/index.wxml

<!-- 引入骨架屏 -->
<import src="./skeleton/index.skeleton" />
<!-- 使用骨架屏 -->
<template is="skeleton" wx:if="{{loading}}" />

<!-- 真实页面结构,添加 wx:else -->
<view class="index-container" wx:else>
</view>

5. 修改样式文件为 .scss 结尾

6. 当前正在预览的页面.scss 中引入样式文件

pages/index/index.scss

// 引入骨架屏样式文件
@import "./skeleton/index.skeleton.scss";
// 整个页面样式
.index-container {
}

7. 当前正在预览的页面.js 中 定义 loading 来控制是否显示骨架屏

pages/index/index.js

import {
  reqIndexData
} from '../../api/index'
Page({
  data: {
    bannerList: [], // 轮播图
    categoryList: [], // 商品导航
    activeList: [], // 活动区
    hotList: [], // 热门商品(人气推荐)
    guessList: [], // 猜你喜欢
    loading: true  // 是否显示骨架屏,默认显示
  },
  // 获取首页数据
  async getIndexData() {
    const res = await reqIndexData()
    this.setData({
      bannerList: res[0].data,
      categoryList: res[1].data,
      activeList: res[2].data,
      hotList: res[3].data,
      guessList: res[4].data,
      loading: false    // 在得到真实数据后, 不再显示骨架屏
    })
  },
  // 监听页面的加载
  onLoad() {
    // 在页面加载以后, 调用获取首页数据的方法
    this.getIndexData()
  }
})

3. 处理警告

index.skeleton.wxml

<!-- 由于这个标签中有行内样式的原因 -->
<swiper-item class="banner--swiper-item" style="position: absolute; width: 100%; height: 100%; transform: translate(0%, 0px) translateZ(0px);">
</swiper-item>

index.skeleton.wxml 修改后

<swiper-item class="banner--swiper-item swiper-item-1" 
</swiper-item>

index.skeleton.scss

.swiper-item-1 {
  position: absolute;
  width: 100%;
  height: 100%;
  transform: translate(0%, 0px) translateZ(0px);
}

2. 分类

0. 配置

1. 数据请求 API

1. 定义接口 API 函数

miniprogram/api/category.js

import http from '../utils/http'

/**
 * @description 获取商品分类的数据
 * @returns Promise
 */
export const reqCategoryData = () => {
  return http.get('/index/findCategoryTree')
}

pages/cate/cate.js

import {
  reqCategoryData
} from '../../api/category'
Page({
  data: {
    categoryList: []
  },

  // 获取商品分类数据
  async getCategoryData() {
    const res = await reqCategoryData()
    console.log(res);
    this.setData({
      categoryList: res.data
    })
  },
  onLoad(options) {
    // 获取商品分类数据
    this.getCategoryData()
  },
})

2. 全局样式

1. 一级分类

**需求: **

  1. 当进入分类页面的时候, 第一个一级分类默认是高亮选中的状态
  2. 当点击任意的一级分类以后, 对应的一级分类需要高亮选中, 其余的一级分类取消高亮选中

0. 效果图

1. 页面创建

pages/cate/cate.wxml

<view>
  <view class="category-container">
    <!-- 左侧的滚动视图区域 -->
    <scroll-view class="category-left-view" scroll-y bind:tap="clickOneCategory">
      <view class="left-view-item active"> 鲜花玫瑰 </view>
      <view class="left-view-item active"> 鲜花玫瑰 </view>
      <view class="left-view-item active"> 鲜花玫瑰 </view>
      <view class="left-view-item active"> 鲜花玫瑰 </view>
      <view class="left-view-item active"> 鲜花玫瑰 </view>
    </scroll-view>
  </view>
</view>

pages/cate/cate.scss

.category-container {
  display: flex;

  .category-left-view {
    width: 340rpx;
    padding: 10rpx 5rpx;

    .left-view-item {
      height: 65rpx;
      padding: 16rpx 20rpx;
      line-height: 50rpx;
      border-right: 1rpx solid #efefef;
    }

    .active {
      &::before {
        content: '|';
        background-color: #f3514f;   // 使 | 加粗
        margin-right: 20rpx;
      }
      color: #f3514f;
    }
  }
}

2. 数据交互

1. 根据数据渲染页面

<view>
  <view class="category-container">
    <!-- 左侧的滚动视图区域 -->
    <scroll-view class="category-left-view" scroll-y bind:tap="clickOneCategory">
      <block wx:for="{{ categoryData }}" wx:key="id">
        <view class="left-view-item">{{item.name}} </view>
      </block>
    </scroll-view>
  </view>
</view>

2. 选中效果

1. 先初始化数据 activeIndex, 代表被激活的一级分类, 默认值为0

pages/cate/cate.js

import {
  reqCategoryData
} from '../../api/cate'
Page({
  data: {
    activeIndex: 0,    // 用户点击的一级菜单索引
    categoryData: []
  },

  async getCategoryData() {
    const res = await reqCategoryData()
    this.setData({
      categoryData: res.data
    })
  },

  clickOneCategory(event){
    this.setData({
      activeIndex: event.target.dataset.index
    })
  },

  onLoad(options) {
    this.getCategoryData()
  },

})

2. 使用事件冒泡给左侧菜单栏绑定点击事件, 当点击某个一级分类时, 将对应的索引赋值给 activeIndex

pages/cate/cate.wxml

<view>
  <view class="category-container">
    <!-- 绑定点击事件 -->
    <scroll-view class="category-left-view" scroll-y bind:tap="clickOneCategory">
      <block wx:for="{{ categoryData }}" wx:key="id">
        <!-- 传递当前标签的 index,并对比用户点击的索引是否与当前标签索引一致,如果一致就添加 active 高亮效果 -->
        <view data-index="{{ index }}" class="left-view-item {{ activeIndex === index ? 'active' : '' }}">{{item.name}} </view>
      </block>
    </scroll-view>
  </view>
</view>

pages/cate/cate.js

import {
  reqCategoryData
} from '../../api/cate'
Page({
  data: {
    activeIndex: 0,
    categoryData: []
  },

  async getCategoryData() {
    const res = await reqCategoryData()
    this.setData({
      categoryData: res.data
    })
  },
	
  // 获取用户点击的索引, 并赋值给 activeIndex
  clickOneCategory(event){
    this.setData({
      activeIndex: event.target.dataset.index
    })
  },
  onLoad(options) {
    this.getCategoryData()
  },
})

2. 二级分类

0. 效果图

1. 页面创建

pages/cate/cate.wxml

<view>
  <view class="category-container">
    <!-- 左侧的滚动视图区域 -->
    <scroll-view class="category-left-view" scroll-y bind:tap="clickOneCategory">
    </scroll-view>

    <!-- 右侧的滚动视图区域 -->
    <scroll-view class="category-right-view" scroll-y>
      <!-- 二级分类 -->
      <view class="right--view-container">
        <view class="right-view-item">
          <navigator class="navigator-nav" url="">
            <image class="img" src="../../assets/categorys/cate-1.png" mode="" />
            <text class="text">真情告白</text>
          </navigator>
        </view>
        <view class="right-view-item">
          <navigator class="navigator-nav" url="">
            <image class="img" src="../../assets/categorys/cate-1.png" mode="" />
            <text class="text">真情告白</text>
          </navigator>
        </view>
        <view class="right-view-item">
          <navigator class="navigator-nav" url="">
            <image class="img" src="../../assets/categorys/cate-1.png" mode="" />
            <text class="text">真情告白</text>
          </navigator>
        </view>
        <view class="right-view-item">
          <navigator class="navigator-nav" url="">
            <image class="img" src="../../assets/categorys/cate-1.png" mode="" />
            <text class="text">真情告白</text>
          </navigator>
        </view>
        <view class="right-view-item">
          <navigator class="navigator-nav" url="">
            <image class="img" src="../../assets/categorys/cate-1.png" mode="" />
            <text class="text">真情告白</text>
          </navigator>
        </view>
        
      </view>
    </scroll-view>
  </view>
</view>

pages/cate/cate.scss

.category-container {
  display: flex;

  // 一级菜单栏
  .category-left-view {
  }
  // 二级菜单栏
  .category-right-view {
    .right--view-container {
      display: flex;
      flex-wrap: wrap;

      .right-view-item {
        width: 110rpx;
        margin: 16rpx 35rpx;

        .navigator-nav {
          display: flex;
          flex-direction: column;

          .img {
            width: 110rpx;
            height: 110rpx;
          }

          .text {
            margin-top: 10rpx;
            font-size: 27rpx;
          }
        }
      }
    }
  }
}

2. 数据交互

pages/cate/cate.wxml

<view>
  <view class="category-container">
    <!-- 左侧的滚动视图区域 -->
    <scroll-view class="category-left-view" scroll-y bind:tap="clickOneCategory">
    </scroll-view>

    <!-- 右侧的滚动视图区域 -->
    <scroll-view class="category-right-view" scroll-y>
      <view class="right--view-container">
        <!-- 循环对应索引的 children 对象数组 -->
        <view class="right-view-item" wx:for="{{ categoryData[activeIndex].children }}" wx:key="id">
          <!-- 跳转二级分类的商品列表 -->
          <navigator class="navigator-nav" url="/pages/goods/list/list?category2Id={{ item.id }}">
            <image class="img" src="{{ item.imageUrl }}" mode="" />
            <text class="text">{{item.name}}</text>
          </navigator>
        </view>
      </view>
    </scroll-view>
  </view>
</view>

3. 我的

1. 登录

1. Token

**1. 什么是 Token **

Token 是服务器生成的一串字符串, 用作客户端发起请求的一个身份令牌。当第一次登录成功后, 服务器生成一个 Token 便将次 Token 返回给客户端, 客户端在接收到 Token 后, 会使用某种方式将 Token 保存到本地。以后客户端发起请求, 只需要在请求头上携带这个 Token, 服务器通过验证 Token来确认用户的身份, 而无需再次带上用户名和密码。

2. Token 的交互流程

  1. 客户端向服务器发起登录请求, 服务端验证用户名与密码
  2. 验证成功后, 服务端会签发一个 Token, 并将 Token 发送给客户端
  3. 客户端收到 Token 以后, 将其存储起来, 比如放在 localStoragesessionStorage
  4. 客户端每次向服务器请求资源的时候, 需要携带服务器签发的 Token, 服务端收到请求, 然后去验证客户端请求中携带的 Token, 如果验证成功, 就向客户端返回请求的数据

2. 实现流程

传统的登录功能, 需要用户先注册, 注册完成后, 使用注册的账号、密码进行登录, 小程序的登录流程比较简单, 小程序可以通过微信提供的登录能力, 便捷的获取微信提供的用户身份标识进行登录, 免去了注册和输入账号密码的步骤, 从而提高了用户体验

官方文档

https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html

**小程序登录图示: **

3. 集成 Vant Weapp

1. 安装

npm i @vant/weapp

2. 工具 => 构建 Npm

3. 修改 app.json

{
  "style": "v2"   // 删除此配置项
}

4. 引入组件

pages/login/login.json

{
  "usingComponents": {
    "van-empty": "@vant/weapp/empty/index",
    "van-button": "@vant/weapp/button/index"
  }
}

4. 页面构建

pages/login/login.wxml

<van-empty class="empty-image" image="/assets/default.png" description="点击下方的按钮, 授权登录您的账户">
  <van-button round type="danger" class="bottom-button">点击授权登录</van-button>
</van-empty>

5. 数据交互

1. 绑定点击事件

pages/login/login.wxml

<van-empty class="empty-image" image="/assets/default.png" description="点击下方的按钮, 授权登录您的账户">
  <van-button round type="danger" class="bottom-button" bindtap="login">点击授权登录</van-button>
</van-empty>

2. 获取临时登录凭证,并发送给后端服务器, 得到 token 后将其保存在本地

api/user.js

import http from '../utils/http'

export const reqLoginData = (code) => {
  return http.get(`wexin/wxLogin/${code}`)
}

pages/login/login.js

import {
  toast
} from '../../utils/extendAPI'
import {
  reqLoginData
} from '../../api/user'
import {
  setStorage
} from '../../utils/storage'
Page({

  // 获取临时登录凭证 Code
  login() {
    wx.login({
      success: async (res) => {
        const { code } = res
        if (code) {
          // code 获取成功后, 传给后端服务器,后端会返回 token 字符串
          const res = await reqLoginData(code)
          // 将 token 保存在本地
          setStorage('token', res.token)
        } else {
          toast({
            title: "授权失败,请重新授权"
          })
        }
      },
    })
  },
})

3. 每次发送请求时, 携带 token 在请求头中, 发送给服务器

utils/http.js

import WxRequest from './request'
import {
  getStorage,
  clearStorage
} from './storage'
import {modal, toast} from './extendAPI'

// 实例化 WxRequest,并配置基准地址
const wxr = new WxRequest({
  baseURL: 'https://gmall-prod.atguigu.cn/mall-api',
  timeout: 15000
})

// 配置请求拦截器
wxr.interceptors.request = (config) => {
  // 在发送请求前, 需要先判断本地是否存在访问令牌 token
  const token = getStorage('token')
  //  如果存在,则需要在请求头中添加该令牌
  if (token) {
    config.header.token = token
  }
  return config
}

// 配置响应拦截器
wxr.interceptors.response = async (response) => {
  const {
    isSuccess,
    data
  } = response
  if (!isSuccess) {
    wx.showToast({
      title: '网络异常请重试',
      icon: 'error'
    })
    return response
  }

  // 判断状态码
  switch (data.code) {
    case 200:
      return data
    case 208:   
      const res = await modal({
        content: '鉴权失败, 请重新登录',
        showCancel: false    
      })
      if (res) { 
        clearStorage()

        wx.navigateTo({
          url: '/pages/login/login',
        })
      }
      return Promise.reject(response)
    default:
      toast({
        title: "程序出现异常, 请联系客服或稍后重试"
      })
      return Promise.reject(response)
  }
}

export default wxr

6. token 存储到 store

Token 直接存储到本地不方便对数据进行操作: 要先从本地存储取出、修改、保存, 并且 存储到本地的数据不是响应式的, 当本地存储里面的内容发生改变, 页面不会发生改变。这时候就需要将 Token 存储到 Store中,

1. 安装 Mobx, 并构建 工具 => 构建Npm

npm i mobx-miniprogram mobx-miniprogram-bindings

2. 创建 Store 对象

stores/userStore.js

import {
  observable,
  action
} from 'mobx-miniprogram'
import {
  getStorage
} from '../utils/storage'

export const userStore = observable({
  // 定义响应式数据
  token: getStorage('token') || '',

  // 修改 Token
  setToken: action(function (token) {
    this.token = token
  })
})

pages/login/login.js

import {
  toast
} from '../../utils/extendAPI'
import {
  reqLoginData
} from '../../api/user'
import {
  setStorage
} from '../../utils/storage'
import {
  ComponentWithStore
} from 'mobx-miniprogram-bindings'
import {
  userStore
} from '../../stores/userStore'
ComponentWithStore({   // 使用 ComponentWithStore() 替换 Page()
  data: {

  },
  storeBindings: {
    store: userStore,
    fields: ['token'],
    actions: ['setToken']
  },
  methods: {    // 将 login 回调函数挪到 methods 对象中
    login() {
      wx.login({
        success: async (res) => {
          const {
            code
          } = res
          if (code) {
            const res = await reqLoginData(code)
            setStorage('token', res.token)
            this.setToken(res.token)  // 将 token 保存到 store
            wx.navigateTo({
              url: '/pages/my/my',
            })
          } else {
            toast({
              title: "授权失败,请重新授权"
            })
          }
        },
      })
    },
  },
})

2. 用户信息

用户信息可能会在多个页面或组件中使用, 为了方便对用户信息的获取和使用, 需要将用户信息存储到 store

1. 获取用户信息

1. 封装接口 API 函数

api/user.js

import http from '../utils/http'

/**
 * @description 登录
 * @param {String} code 临时登录凭证
 * @returns Promise
 */
export const reqLoginData = (code) => {
  return http.get(`/wexin/wxLogin/${code}`)
}

/**
 * @description 获取用户信息
 * @returns Promise
 */
export const reqUserInfo = () => {
  return http.get('/weixin/getuserInfo')
}

2. 保存用户信息到 本地 和 store

1. 发送请求获取用户信息,并保存在 本地store

pages/login/login.js

import {
  toast
} from '../../utils/extendAPI'
import {
  reqLoginData,
  reqUserInfo
} from '../../api/user'
import {
  setStorage
} from '../../utils/storage'
import {
  ComponentWithStore
} from 'mobx-miniprogram-bindings'
import {
  userStore
} from '../../stores/userStore'
ComponentWithStore({
  data: {

  },
  storeBindings: {
    store: userStore,
    fields: ['token', 'userInfo'],     // 绑定 store 中的用户信息
    actions: ['setToken', 'setUserInfo']
  },
  methods: {
    login() {
      wx.login({
        success: async (res) => {
          const {
            code
          } = res
          if (code) {
            const res = await reqLoginData(code)

            setStorage('token', res.token)

            this.setToken(res.token)

            // 获取用户信息,并保存到 本地 和 store
            this.getUserInfo()

            wx.navigateTo({
              url: '/pages/my/my',
            })
          } else {
            toast({
              title: "授权失败,请重新授权"
            })
          }
        },
      })
    },

    // 获取用户信息
    async getUserInfo() {
      const {data} = await reqUserInfo()

      // 将用户信息存储在本地
      setStorage('userInfo', data)

      // 将用户信息存储在 store
	  this.setUserInfo(data)
    }
  }
})

stores/userStore.js

import {
  observable,
  action
} from 'mobx-miniprogram'
import {
  getStorage
} from '../utils/storage'

export const userStore = observable({
  // 定义响应式数据
  token: getStorage('token') || '',
  userInfo: getStorage('userInfo') || '',

  setToken: action(function (token) {
    this.token = token
  }),

  // 修改 用户信息
  setUserInfo: action(function (userInfo) {
    this.userInfo = userInfo
  })
})

3. 页面

在获取到用户信息后, 已经将用户信息存储到了 本地Store, 想在页面渲染则需要从 Store 中取出用户数据, 并渲染到页面中。

如果用户没有登录, 展示默认头像, 提示用户登录的文案信息, 不展示设置按钮

如果用户已经登录, 展示用户的头像和昵称, 并且展示设置按钮, 方便用户对收货地址、头像、昵称进行更改

**1. 实现思路: **

  1. 在个人中心页面导入 ComponentWithStore 方法构建页面
  2. 配置 storeBindings 让组件和 Store 建立关联
  3. 渲染页面

0. 效果图

1. 构建页面

pages/my/my.wxml

<view class="container">
  <!-- 顶部展示图 -->
  <view class="top-show">
    <image class="img" src="../../assets/swipers/swiper-1.jpg" mode="" />
  </view>

  <view class="bottom-show">
    <!-- 未登录面板 -->
    <view class="user-container section">
      <image class="empty-image" src="../../assets/default.png" mode="" />
      <view class="login-status">
        <text>未登录</text>
        <text>点击授权登录</text>
      </view>
    </view>

    <!-- 登录面板 -->
    <!-- <view class="user-container section">
      <image class="empty-image" src="../../assets/default.png" mode="" />
      <view class="login-status">
        <text>未登录</text>
        <text>点击授权登录</text>
      </view>
    </view> -->

    <!-- 订单面板 -->
    <view class="order section">
      <view class="order-top">
        <view>
          <text>我的订单</text>
        </view>
        <navigator url="">
          <text>查看更多></text>
        </navigator>
      </view>
      <view class="order-content">
        <navigator url="" class="content-item">
          <text class="iconfont icon-dingdan"></text>
          <text class="title">商品订单</text>
        </navigator>

        <navigator url="" class="content-item">
          <text class="iconfont icon-lipinka"></text>
          <text class="title">礼品卡订单</text>
        </navigator>

        <navigator url="" class="content-item">
          <text class="iconfont icon-shouhou"></text>
          <text class="title">退款/售后</text>
        </navigator>
      </view>
    </view>

    <!-- 关于售前售后服务面板 -->
    <view class="after-scale section">
      <view class="scale-top">
        <text>关于售前售后服务</text>
      </view>
      <view class="scale-content">
        <navigator url="" class="content-item">
          <text class="iconfont icon-kefu"></text>
          <text>可与小程序客服实时聊天或电话咨询</text>
        </navigator>
        <navigator url="" class="content-item">
          <text class="iconfont icon-shijian"></text>
          <text>小程序客服工作时间为 08:30 - 20:30</text>
        </navigator>
        <navigator url="" class="content-item">
          <text class="iconfont icon-gantanhao"></text>
          <text>鲜花制作完毕情况下暂不支持退款</text>
        </navigator>
        <navigator url="" class="content-item">
          <text class="iconfont icon-bijijilu"></text>
          <text>鲜花可以提前7-15天预定重大节假日不支持定时配送</text>
        </navigator>
      </view>

    </view>
  </view>
</view>

paes/my/my.scss

.container {
  position: relative;

  // overflow: hidden;
  .top-show {
    .img {
      width: 100%;
      height: 400rpx;
      position: absolute;
      z-index: -100;
    }
  }

  .bottom-show {
    width: 100%;
    position: absolute;
    top: 290rpx;

    .user-container {
      height: 130rpx;
      display: flex;
      background-color: #efefef;
      margin: 0 15rpx;
      padding: 20rpx;
      border-radius: 25rpx;

      .empty-image {
        width: 120rpx;
        height: 120rpx;
      }

      .login-status {
        display: flex;
        flex-direction: column;
        justify-content: center;
        font-size: 27rpx;
        color: #979292;
        margin-left: 20rpx;
      }
    }

    .order {
      height: 230rpx;
      border-radius: 25rpx;
      background-color: #efefef;
      padding: 20rpx;
      margin: 15rpx;

      .order-top {
        display: flex;
        justify-content: space-between;
        font-size: 35rpx;

        navigator {
          text {
            color: #bbb8b8;
            font-size: 28rpx;
          }
        }
      }

      .order-content {
        margin-top: 20rpx;
        display: flex;
        justify-content: space-between;

        .content-item {
          display: flex;
          flex-direction: column;
          padding: 20rpx;

          .iconfont {
            font-size: 80rpx;
            margin: 0 auto;
          }

          .title {
            margin-top: 10rpx;
            font-size: 28rpx;
          }
        }
      }
    }

    .after-scale {
      height: 437rpx;
      background-color: #efefef;
      padding: 20rpx;
      margin: 15rpx;

      .scale-top {
        font-size: 34rpx;
        font-weight: 600;
      }

      .scale-content {
        margin-top: 30rpx;

        .content-item {
          font-size: 26rpx;
          color: #979292;
          margin-bottom: 40rpx;
        }

        .iconfont {
          color: rgb(221, 221, 121);
          margin-right: 15rpx;
        }
      }
    }
  }
}

2. 数据交互

4. 设置

1. 页面构建

1. 设置页面

pages/setup/setup.wxml

<view class="container">
  <view class="item-container">
    <navigator class="item" url="../../pages/setup/personal-data/personal-data">
      <text>修改个人资料</text>
      <text class="right">></text>
    </navigator>

    <navigator class="item" url="../../pages/setup/receive-address/receive-address">
      <text>我的收货地址</text>
      <text class="right">></text>
    </navigator>

    <navigator class="item" url="../../pages/setup/problem-feedback/problem-feedback">
      <text>问题反馈</text>
      <text class="right">></text>
    </navigator>

    <navigator class="item" url="../../pages/setup/contact-us/contact-us">
      <text>联系我们</text>
      <text class="right">></text>
    </navigator>

    <navigator class="item" url="../../pages/setup/auth-info/auth-info">
      <text>授权信息</text>
      <text class="right">></text>
    </navigator>
  </view>

</view>

pages/setup/setup.scss

.container {
  width: 100%;
  height: 100%;
  background-color: #efefef;

  .item {
    width: 100%;
    height: 40rpx;
    background-color: white;
    margin: 16rpx 20rpx;
    padding: 20rpx;
    font-size: 28rpx;
    font-weight: 500;
    display: flex;
    justify-content: space-between;

    .right {
      font-size: 28rpx;
      font-weight: 300;
      margin-right: 70rpx;
    }
  }
}

2. 修改个人资料

pages/setup/personal-data/personal-data.wxml

<view class="container">
  <view class="avatar">
    <text class="avatar-text">头像</text>
    <view class="right">
      <image class="right-img" src="/assets/default.png" mode="" />
      <text class="right-text">></text>
    </view>
  </view>

  <view class="nickname">
    <text class="nickname-text">头像</text>
    <view class="right">
      <text class="right-nickname">尚硅谷</text>
      <text class="right-text">></text>
    </view>
  </view>

  <view class="save-button">
    <van-button round type="danger" size="large">保存</van-button>
  </view>
</view>

pages/setup/personal-data/personal-data.scss

.container {
  width: 100%;
  height: 100%;
  background-color: #efefef;

  .avatar {
    width: 100%;
    height: 80rpx;
    padding: 16rpx 20rpx;
    background-color: white;
    display: flex;
    align-items: center;
    justify-content: space-between;

    .avatar-text {
      font-size: 30rpx;
    }

    .right {
      display: flex;
      align-items: center;

      .right-img {
        width: 80rpx;
        height: 80rpx;
        margin-right: 30rpx;
      }

      .right-text {
        margin-right: 50rpx;
        font-size: 35rpx;
        font-weight: 300;
        color: #b8b1b1;
      }
    }

  }


  .nickname {
    width: 100%;
    height: 80rpx;
    padding: 16rpx 20rpx;
    background-color: white;
    display: flex;
    align-items: center;
    margin-top: 15rpx;
    justify-content: space-between;

    .nickname-text {}

    .right {
      display: flex;
      align-items: center;

      .right-nickname {
        width: 150rpx;
        height: 80rpx;
        line-height: 80rpx;
        font-size: 28rpx;
        margin-right: -40rpx;
      }

      .right-text {
        margin-right: 50rpx;
        font-size: 35rpx;
        font-weight: 300;
        color: #b8b1b1;
      }
    }
  }

  .save-button {
    margin-top: 15rpx;
  }
}

3. 我的收货地址

pages/setup/receive-address/receive-address.wxml


pages/setup/receive-address/receive-address.scss


4. 问题反馈

pages/setup/problem-feedback/problem-feedback.wxml


pages/setup/problem-feedback/problem-feedback.scss


5. 联系我们

pages/setup/contact-us/contact-us.wxml


pages/setup/contact-us/contact-us.scss


6. 授权信息

pages/setup/auth-info/auth-info.wxml


pages/setup/auth-info/auth-info.scss


2. 分包处理

随着项目功能的增加, 项目体积也随着增大, 从而影响小程序的加载速度, 影响用户的体验, 因此需要将 更新个人资料收货地址 功能配置成一个分包, 当用户在访问设置页面时, 还要预先加载 更新个人资料收货地址 所在的分包

在分包后, 通过查看代码依赖检查是否分包完成: 详情 => 基本信息 => 代码依赖分析

1. 新建文件夹来存放分包后的页面: miniprogram/modules/settingModule

app.json

{
  "subPackages": [{
    "root": "modules/settingModule",
    "name": "settingModule",
    "pages": [
      "pages/setup/personal-data/personal-data",
      "pages/setup/receive-address/receive-address"
    ]
  }]
}

2. 将 个人资料收货地址 目录移动到分包 settingModule

3. 替换 个人资料收货地址 的访问路径

pages/setup/setup.wxml

<view class="container">
  <view class="item-container">
    <!-- 修改为 /modules/settingModule/ 起始的路径地址-->
    <navigator class="item" url="/modules/settingModule/pages/setup/personal-data/personal-data">
      <text>修改个人资料</text>
      <text class="right">></text>
    </navigator>

    <navigator class="item" url="/modules/settingModule/pages/setup/receive-address/receive-address">
      <text>我的收货地址</text>
      <text class="right">></text>
    </navigator>
</view>

3. 分包预下载

app.json

{
  "preloadRule": {
    "pages/setup/setup": {
      "network": "all",
      "packages": ["settingModule"]
    }
  }
}

当点击进入设置页面时, 预下载了 settingModule 这个分包

5. 头像上传

通过 bindchooseavatar 事件回调获取到的头像信息的临时路径随时会失效, 因为小程序服务端会检测临时文件超过一定的容量时, 会将临时文件清理掉, 所以需要将头像信息的临时路径上传到自己的后台服务器, 如果想要将本地资源上传到服务器, 就需要使用到小程序提供的 API 方法: wx.uploadFile, 原生 API的语法如下:

wx.uploadFile({
    url: '开发者服务器地址',
    filePath: '要上传文件资源的路径 (本地路径)',
    name: '文件对应的 key',
    header: 'HTTP 请求的 Header',
    // 接口调用成功的回调函数
    success: (res) =>{},
    // 接口调用失败的回调函数
    fail: (err) =>{}
})

miniprogram/modules/settingModule/pages/setup/personal-data/personal-data.js

import http from '../../utils/http'
Page({
  data: {
    avatar: ''
  },
  async getAvatar(event) {
    const avatarUrl = event.detail.avatarUrl

    // 发送请求, 将头像保存在服务器
    const { data:avatar } = await http.upload('/fileUpload',avatarUrl)
    this.setData({
      avatar: avatar
    })
  }
})

99. 流程图



posted @ 2024-06-20 15:21  河图s  阅读(89)  评论(0)    收藏  举报