Skip to content

项目基本的骨架与搭建首页的静态(Day3 1/2) ​

本页对应 day3,共 4 段视频,约 1 小时 22 分。

学习目标 ​

  1. 能调用分类层级接口获取数据,并在分类页左侧渲染一级分类、右侧渲染对应二级分类,完成切换分类时数据联动展示。
  2. 能在小程序页面生命周期中封装并调用异步请求函数,避免在 onLoad 中直接编写异步逻辑,并通过调试输出验证接口返回数据。
  3. 能查看并复用个人中心静态结构与样式,配置页面自定义导航栏,完成“请登录”入口及登录页路由跳转。
  4. 能使用 npm 安装依赖,并通过开发者工具“构建 npm”将依赖转换为小程序可用包,验证第三方库在小程序中正常引入使用。
  5. 能按 Vant 文档完成安装、全局样式配置、项目配置修改与组件注册,并在登录页中使用 Vant 按钮等组件完成静态改造。
  6. 能搭建用户编辑页静态结构,完成头像按钮、昵称输入区、底部操作按钮布局,并实现授权登录后进入编辑页且登录页销毁的跳转逻辑。

知识点 ​

本节围绕项目基本骨架,把分类业务、个人中心静态、npm 与 Vant、登录跳转和编辑页静态串起来。转写稿里反复强调:先看接口和数据,再写渲染;静态要自己搭,样式和布局要心里有数。

分类模块:先看接口,再发请求渲染 ​

为什么需要:分类页左侧是一级分类,右侧是该分类下的商品/子分类。静态已搭好,差的是“发请求—拿数据—渲染”。

怎么想:先看接口文档。转写稿中“干请求”即 get 请求;当前未登录,不带 token;接口无参数。返回整体是数组,约 10 个一级对象,每个对象下有 children,且各一级下子级数量不同。所以左侧遍历一级数组,右侧遍历“当前选中索引”对应项的 children。

怎么做:先封装 API,命名可用 reqCategory,不带参数、默认 GET;再在页面 onLoad 里调用封装后的方法。注意不要在生命周期里直接写 async,而是封装成函数:

js
// api/category.js
export const reqCategory = () => request({
  url: '按接口文档填写地址' // 默认 GET,不带参数
})
js
// 分类页
import { reqCategory } from '../../api/category'

Page({
  data: {
    cateArr: [],
    currentIndex: 0
  },
  onLoad() {
    this.getData()
  },
  async getData() {
    const res = await reqCategory()
    console.log(res)
    if (res.code === 200) {
      this.setData({ cateArr: res.data })
    }
  }
})

渲染时左侧一级要加 key,右侧用当前索引取子级:

html
<view v-for="(item, index) in cateArr" :key="item.id">
  {{ item.name }}
</view>
<view v-for="child in cateArr[currentIndex].children" :key="child.id">
  {{ child.name }}
</view>

易错点:this.getData() 的 this 不能漏;不要直接写 getData();不要在 onLoad 上加 async;渲染前先打印确认 res.data 结构;文本插值不要写错,属性绑定按小程序语法处理。

个人中心静态与隐藏顶部导航 ​

为什么需要:个人中心要展示未登录/已登录两种状态,还要把顶部系统导航去掉,做出自定义头部效果。

怎么想:静态结构可从 todo 里拷贝,但样式和图标要接入项目。顶部导航不是自己写的 DOM,不能直接删元素,要用页面配置。

怎么做:在个人中心页面的 json 中写:

json
{
  "navigationStyle": "custom"
}

它属于页面配置,只保留右上角胶囊按钮。图标字体要在 app.wxss 中引入:

css
@import "路径/iconfont.wxss";

易错点:navigationStyle 是页面配置,不要误以为改全局;custom 不是完全无导航,而是只保留胶囊;静态拷贝后要检查图标样式是否全局引入。

npm 在小程序中使用与 Vant ​

为什么需要:想用 Vant 这类小程序 UI 组件库,减少重复搭按钮、分割线、日历等。

怎么想:小程序可以用 npm,但 node_modules 里的包不能直接引用,必须通过开发者工具“构建 npm”,生成小程序可用依赖。

怎么做:终端安装后,在工具里执行“构建 npm”。按 Vant 文档修改 app.json 的 style: "v2",并在 project.config.json 的 setting 中按文档补 packNpmRelationList,新版本路径注意用 ./miniprogram_npm 一类写法。页面 json 注册组件:

json
{
  "usingComponents": {
    "van-button": "@vant/weapp/button/index"
  }
}
html
<van-button type="primary">按钮</van-button>

易错点:不构建 npm 就直接 import 会报错;构建后仍不生效,先重新编译/清缓存;组件必须在页面 json 注册;文档里的路径要按当前工具版本调整。

登录跳转与编辑页静态 ​

为什么需要:个人中心点“请登录”进入登录页;授权后进入编辑页,编辑页只需授权一次,返回时不应再回登录页。

怎么做:请登录绑定事件,登录页不是 tabBar,可用 navigateTo;登录页进入编辑页用 redirectTo,销毁登录页:

js
goLogin() {
  wx.navigateTo({ url: '/pages/login/login' })
}
onTapEdit() {
  wx.redirectTo({ url: '/pages/edit/edit' })
}

编辑页静态:顶部头像用 button 包 image,下方昵称用 flex,一左一右;底部两个按钮用 flex。根节点宽高 100%,按钮默认 padding 要去掉:

css
.edit { width: 100%; height: 100%; }
.avatar { width: 200rpx; height: 200rpx; padding: 0; }
.avatar .image { width: 200rpx; height: 200rpx; }
.name { display: flex; align-items: center; }
.foot { display: flex; }

易错点:根节点类选择器别漏点;button 默认有 padding,不清理头像会有缝隙;图片默认尺寸大,要显式设宽高;navigateTo 不能跳 tabBar,redirectTo 会销毁当前页,按业务选择。

分类模块:先看接口,再写静态渲染 ​

为什么需要:首页分类不是写死的,左侧一级分类、右侧对应二级分类都来自接口。先确认接口返回结构,才能决定静态结构怎么遍历。

怎么想:分类接口是 GET,无参数;返回数组,数组每一项是一级分类,一级分类下有 children 二级分类。左侧遍历一级,右侧遍历当前选中项的 children。

怎么做:先封装 API:

js
// api/index.js
import request from '../utils/request'

export const reqCategory = () => request({
  url: '/category',
  method: 'get'
})

页面中定义数据并请求:

js
data() {
  return {
    sARR: [],
    currentIndex: 0
  }
},
onLoad() {
  this.getData()
},
methods: {
  async getData() {
    const res = await reqCategory()
    if (res.code === 200) {
      this.sARR = res.data
    }
  }
}

静态遍历左侧:

html
<view
  wx:for="{{sARR}}"
  wx:key="id"
  class="{{currentIndex === index ? 'active' : ''}}"
>
  {{item.name}}
</view>

右侧取当前一级分类的 children:

html
<view wx:for="{{sARR[currentIndex].children}}" wx:key="id">
  {{item.name}}
</view>

易错点:onLoad 里不要直接写大量 await 逻辑,最好封装成 this.getData();调用对象方法要写 this;wx:for 要加 wx:key;插值用 {{item.name}},不要写成字符串或乱加冒号。

生命周期里发请求:为什么要封装 ​

为什么需要:页面一挂载就要拿分类数据,但生命周期里直接写异步逻辑,容易让代码同步异步混杂,后期难维护。

怎么想:onLoad 只负责“触发”,真正请求放到 methods 里。这样结构清晰,也方便复用和调试。

怎么做:先 console.log(res) 看整体返回,再赋值。确认 res.code === 200 后再 this.sARR = res.data。如果页面没数据,先检查接口、this、赋值路径,不要急着写渲染。

易错点:不要写成 getData() 而漏 this;不要没打印结果就直接遍历;不要忘了 await。

个人中心静态:先搭结构,再接业务 ​

为什么需要:个人中心有未登录、已登录、编辑资料等状态,先把静态搭好,后面才好做条件渲染和授权逻辑。

怎么想:未登录展示默认头像和“请登录”;登录后展示用户头像、昵称。顶部导航要去掉,使用页面配置 navigationStyle: "custom",只保留右上角胶囊按钮。

怎么做:

json
{
  "navigationStyle": "custom",
  "navigationBarTitleText": "个人中心"
}

结构上分头像区、订单区、功能区和底部说明。头像和昵称用条件渲染二选一;订单标题用 display:flex; justify-content:space-between 做左右分布。

易错点:navigationStyle 是页面配置,不是自己写个元素;页面没配标题时会继承全局配置,页面配置优先级更高。

小程序里使用 npm 与 Vant ​

为什么需要:登录页、编辑页要按钮、分割线、加载等组件,手写费时且不稳定,可以用 Vant Weapp。

怎么想:小程序能用 npm 包,但 node_modules 里的依赖不能直接给小程序用,必须通过开发者工具“构建 npm”转成小程序依赖。

怎么做:终端安装:

bash
npm i @vant/weapp -S --production

在 app.json 加:

json
{
  "style": "v2"
}

按 Vant 文档在 project.config.json 配置 npm 路径,然后工具菜单执行“构建 npm”。页面 usingComponents 注册后使用:

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

易错点:安装完不构建 npm 会报找不到组件;构建后仍报错,检查 project.config.json 路径和是否重新构建。

登录页与编辑页静态:跳转与布局 ​

为什么需要:点击“请登录”进入登录页,授权后进入编辑页;授权只需一次,编辑页进入后应销毁登录页。

怎么想:普通跳转可用 wx.navigateTo;这里授权流程只走一次,可用 wx.redirectTo,避免返回登录页。

怎么做:

js
goLogin() {
  wx.redirectTo({
    url: '/pages/login/login'
  })
}

编辑页根节点撑满:

css
page {
  width: 100%;
  height: 100%;
}
.edit {
  width: 100%;
  height: 100%;
}

头像按钮、昵称输入、底部两个按钮用 flex 布局;按钮内图片设置宽高,去掉默认 padding。

易错点:page 是标签选择器,不要写成 .page;百分比宽高参照父级;flex 布局要明确主轴和侧轴。

样式与调试提醒 ​

转写中反复强调:静态要自己搭,不能只抄。调试时先看数据有没有、再看结构、最后调样式。样式重点掌握 flex 布局、水平垂直居中、justify-content、align-items 等。遇到工具卡住,先保存、重新编译、重新构建 npm,再判断是不是代码问题。

本节要解决的问题 ​

本节围绕“网络请求:HTTPS 协议”展开。笔记给出了两个地址:接口文档地址 http://39.98.123.211:8300/doc.html,服务器地址 https://gmall-prod.atguigu.cn。核心目标是:在微信小程序里通过官方 API wx.request 拿到分类数据,再把数据渲染到分类页。

为什么需要:分类页的静态结构已经搭好,左侧是一级分类,右侧是对应分类下的商品/二级内容。静态页面没有数据,必须发请求拿真实数据。
怎么想:先看接口文档,确认请求方式、是否需要参数、返回数据结构,再写 API 封装,最后在页面生命周期里调用。
怎么做:用 wx.request 发 GET 请求,判断 res.code === 200,把返回数组存到页面数据里。
易错点:不要没看接口就写请求;不要请求回来不打印结构就直接渲染;不要把文档地址当成服务器请求地址。

先看接口文档,再决定请求怎么写 ​

为什么需要:接口文档决定请求地址、请求方法、参数和返回结构。在本节先找到“获取分类层级数据”这个接口,确认它是 GET 请求,而且当前还没涉及登录,所以不需要带 token,也没有额外参数要求。
怎么想:先判断“要不要参数、要不要登录态、返回的是对象还是数组”。转写稿里明确说,返回整体是一个数组,数组里大约有 10 个对象,每个对象代表左侧的一级分类;每个一级分类下面还有 children,代表它自己的二级分类,而且不同一级分类下的二级数量不一样。
怎么做:打开接口文档,找到对应接口,先用工具或浏览器查看 JSON。重点看最外层是数组还是对象,再看每一项里的 id、name、children 等字段。
易错点:不要看到文档地址是 http://... 就直接拿它发请求;真正请求服务器要用笔记里的 https://gmall-prod.atguigu.cn。接口路径要按文档填,不能自己猜。

用 wx.request 封装分类请求 ​

为什么需要:微信小程序发起网络请求需要通过它提供的 API 实现,本节用的就是 wx.request。把请求封装成函数,页面里只负责调用,逻辑更清楚,也方便以后复用。
怎么想:分类接口是 GET、不带参数,所以封装时只需要写 url 和 method。转写稿里把它封装成类似 reqCategory 的请求函数,页面里引入后调用。
怎么做:可以先写最基础的请求,确认能通,再封装:

js
// 基础示意:实际接口路径按接口文档填写
wx.request({
  url: 'https://gmall-prod.atguigu.cn/接口路径',
  method: 'GET',
  success(res) {
    console.log(res)
  }
})

封装时可以写成:

js
// 按转写稿整理,函数名和路径以项目为准
export const reqCategory = () => {
  return wx.request({
    url: 'https://gmall-prod.atguigu.cn/接口路径',
    method: 'GET'
  })
}

易错点:wx.request 是异步的,不能请求完立刻拿结果;要先 await 或放在回调里。请求函数导出后,页面里要正确引入,路径别写错。

在分类页生命周期里发请求 ​

为什么需要:分类页一挂载就应该拿到数据。转写稿里说,默认进入首页,点击 tabBar 切到分类页时,页面挂载后就要发请求。
怎么想:不要在 onLoad 里堆一大段异步逻辑。建议把“获取分类数据”封装成页面方法,比如 getData,然后在 onLoad 里调用。
怎么做:

js
import { reqCategory } from '../../api/index'

Page({
  data: {
    categoryList: []
  },

  onLoad() {
    this.getData()
  },

  async getData() {
    const res = await reqCategory()
    console.log(res)

    if (res.code === 200) {
      this.setData({
        categoryList: res.data
      })
    }
  }
})

转写稿里反复提醒:先 console.log 打印整体返回数据,确认结构,再做 res.code === 200 判断,再把数据存起来。
易错点:不要在生命周期里直接写一堆 await 导致逻辑混乱;不要忘记 this;不要没判断 code 就赋值;不要数据还没回来就先渲染。

先打印结构,再渲染左侧和右侧 ​

为什么需要:分类页是“左侧一级分类 + 右侧当前一级分类下的 children”。如果不知道返回结构,直接写 v-for 很容易报错。
怎么想:先确认 res.data 是数组,数组每一项是一级分类;左侧遍历这个数组,显示 item.name;右侧遍历当前选中项的 children。
怎么做:左侧 v-for 要加 key,一般用 item.id;右侧根据当前索引取对应项的 children。文本要用插值,图片字段按接口返回写。
易错点:v-for 不加 key 会有警告;插值时不要写成字符串,也不要多加冒号;图片地址要用接口字段;先确认 AppData 里真的有数据,再写渲染,不要“咔咔先搞完再报错往回找”。

本节调试过程回顾 ​

的调试顺序是:先看接口文档,确认“获取分类层级数据”是 GET、无参数;再写 API 请求函数;在分类页 onLoad 里调用封装好的 getData;await 后先 console.log(res),看到整体是数组、里面有 10 个一级分类、每项带 children;确认结构后再 this.setData 存分类数组;最后才改模板,左侧遍历一级分类,右侧遍历当前项 children。这个顺序能避免“数据没通就渲染”的无效调试。

课堂画面 ​

课堂画面 1

课堂画面 2

课堂画面 3

课堂画面 4

代码示例 ​

1. 分类模块:wx.request 取数据 + 左侧一级、右侧二级渲染 ​

api/index.js

js
// 服务器地址(笔记中给出)
const BASE_URL = 'https://gmall-prod.atguigu.cn'

// 对 wx.request 做一次 Promise 封装,方便用 async/await
export const request = (options) => {
  return new Promise((resolve, reject) => {
    wx.request({
      url: BASE_URL + options.url,
      // 不传 method 时默认就是 GET,这里保留是为了通用
      method: options.method || 'GET',
      data: options.data || {},
      success: resolve,
      fail: reject
    })
  })
}

// 获取分类层级数据:GET 请求、不需要带参数
// url 路径以接口文档(http://39.98.123.211:8300/doc.html)为准
export const reqCategory = () => request({ url: '/接口路径按文档填写' })

pages/category/index.js

js
import { reqCategory } from '../../api/index'

Page({
  data: {
    categoryList: [], // 存储分类数据(10 个一级分类,每个带 children)
    activeIndex: 0    // 当前选中的一级分类索引,右侧靠它取 children
  },

  // 生命周期:页面一挂载就发请求
  onLoad() {
    this.getData()
  },

  // 不要直接在 onLoad 里写 async,封装成函数再调用
  async getData() {
    const result = await reqCategory()
    console.log(result) // 先打印看整体返回结构
    if (result.code === 200) {
      this.setData({
        categoryList: result.data
      })
    }
  },

  // 点击左侧切换高亮 + 右侧内容
  changeIndex(e) {
    this.setData({
      activeIndex: e.currentTarget.dataset.index
    })
  }
})

pages/category/index.wxml

xml
<view class="category">
  <!-- 左侧:一级分类 -->
  <scroll-view class="left" scroll-y>
    <view
      class="item {{activeIndex === index ? 'active' : ''}}"
      wx:for="{{categoryList}}"
      wx:key="id"
      data-index="{{index}}"
      bindtap="changeIndex"
    >
      {{item.name}}
    </view>
  </scroll-view>

  <!-- 右侧:当前一级分类下的二级分类 -->
  <scroll-view class="right" scroll-y>
    <view wx:if="{{categoryList.length}}">
      <text class="title">{{categoryList[activeIndex].name}}</text>
      <view class="child-list">
        <block wx:for="{{categoryList[activeIndex].children}}" wx:key="id">
          <image class="pic" src="{{item.imageUrl}}"></image>
          <text>{{item.name}}</text>
        </block>
      </view>
    </view>
  </scroll-view>
</view>

pages/category/index.wxss

css
.category {
  display: flex;
  height: 100%;
}

.left {
  width: 200rpx;
  height: 100%;
  background: #f3f3f3;
}

.left .item {
  height: 80rpx;
  line-height: 80rpx;
  text-align: center;
  font-size: 28rpx;
}

.left .item.active {
  color: #e93b3d;
  background: #fff;
}

.right {
  flex: 1;      /* 右侧占满剩余宽度 */
  height: 100%;
  padding: 20rpx;
  box-sizing: border-box;
}

.pic {
  width: 120rpx;
  height: 120rpx;
}

关键行与易错点

  • request 里 method: options.method || 'GET':分类接口是 GET 且无参数,不写 method 也可以,保留是为了封装通用。
  • 分类接口返回的是数组,数组里每个对象是一级分类,对象内部还有 children 才是二级分类,渲染时要分两次遍历。
  • wx:for 一定要加 wx:key(用 id),不加会有控制台警告且影响复用性能。
  • onLoad 里不要直接写 async onLoad,把请求封装成 getData 再调用,避免生命周期函数变成异步导致时序问题。
  • this.setData 必须带 this,直接写 setData 会报错。
  • 右侧取的是 categoryList[activeIndex].children,所以 activeIndex 必须有默认值 0,否则页面初次渲染右侧为空。

2. 个人中心:隐藏原生导航栏 + 请登录跳转 ​

pages/mine/index.json

json
{
  "navigationStyle": "custom"
}

pages/mine/index.wxml

xml
<view class="container">
  <!-- 顶部背景图(小熊) -->
  <image class="bg" src="/static/images/bg.png"></image>

  <view class="user-container">
    <!-- 未登录:显示默认头像 + 请登录 -->
    <view class="no-login" bindtap="goLogin">
      <image class="avatar" src="/static/images/default.png"></image>
      <text class="tip">请登录</text>
    </view>
  </view>

  <view class="order">
    <view class="order-title">
      <text>我的订单</text>
      <text>查看更多 ></text>
    </view>
    <view class="order-list">
      <view class="order-item">
        <van-icon name="balance-list-o" size="40rpx"></van-icon>
        <text>商品订单</text>
      </view>
      <view class="order-item">
        <van-icon name="gift-o" size="40rpx"></van-icon>
        <text>礼品订单</text>
      </view>
      <view class="order-item">
        <van-icon name="cash-back-record" size="40rpx"></van-icon>
        <text>退款售后</text>
      </view>
    </view>
  </view>
</view>

pages/mine/index.wxss

css
.container {
  width: 100%;
  height: 100%;
  position: relative;
}

.bg {
  width: 100%;
  height: 300rpx;
  position: absolute;
  top: 0;
  left: 0;
}

.user-container {
  position: relative;  /* 层级压在背景图之上 */
  z-index: 2;
  padding: 200rpx 30rpx 0;
}

.avatar {
  width: 120rpx;
  height: 120rpx;
  border-radius: 50%;
}

.order {
  background: #fff;
  border-radius: 10rpx;
  margin: 20rpx 30rpx;
}

.order-title {
  display: flex;
  justify-content: space-between;
  padding: 20rpx;
}

.order-list {
  display: flex;
  justify-content: space-around;
}

.order-item {
  display: flex;
  flex-direction: column;
  align-items: center;
}

pages/mine/index.js

js
Page({
  // 请登录文字点击 → 跳到登录页
  goLogin() {
    wx.navigateTo({
      url: '/pages/login/login'
    })
  }
})

关键行与易错点

  • 顶部原生导航栏自己写不了,只能靠页面配置 "navigationStyle": "custom" 隐藏,它只保留右上角胶囊按钮;写进 app.json 的 window 则全局生效,写在页面 json 里只对当前页生效。
  • 页面配置优先级高于全局配置,例如登录页标题要改,就在 pages/login/login.json 里写 navigationTitle 覆盖,不必动全局。
  • 背景图用 position: absolute 时,用户信息容器必须给 position: relative + z-index,否则会被背景图盖住。
  • 登录页不是 tabBar 页面,用 wx.navigateTo 即可;如果目标页是 tabBar 页面则必须用 wx.switchTab,用错会跳不过去。
  • 根节点 .container 要撑满一屏,高度依赖父级,百分比高度要逐层给到 height: 100%。

3. 编辑个人中心静态 + Vant 组件库接入 ​

依赖安装与构建(终端)

bash
npm init -y
npm i @vant/weapp -S --production

app.json

json
{
  "pages": [
    "pages/index/index",
    "pages/category/index",
    "pages/mine/index",
    "pages/login/login",
    "pages/edit/edit"
  ],
  "window": {
    "navigationBarTitleText": "首页",
    "navigationBarBackgroundColor": "#ffffff",
    "navigationBarTextStyle": "black"
  },
  "style": "v2",
  "sitemapLocation": "sitemap.json"
}

project.config.json(setting 部分)

json
{
  "miniprogramRoot": "miniprogram/",
  "setting": {
    "packNpmManually": true,
    "packNpmRelationList": [
      {
        "packageJsonPath": "./package.json",
        "miniprogramNpmDistDir": "./miniprogram/"
      }
    ]
  }
}

pages/edit/edit.json

json
{
  "navigationBarTitleText": "编辑个人中心",
  "usingComponents": {
    "van-button": "@vant/weapp/button/index"
  }
}

pages/edit/edit.wxml

xml
<view class="edit">
  <!-- 头像:用 button 才能调起微信选择头像 -->
  <button class="avatar-wrapper">
    <image class="avatar" src="/static/images/avatar.png"></image>
  </button>

  <view class="name">
    <text class="label">昵称</text>
    <input class="input" placeholder="请输入昵称" />
  </view>

  <view class="footer">
    <van-button type="warning">忽略设置</van-button>
    <van-button type="primary">更新用户信息</van-button>
  </view>
</view>

pages/edit/edit.wxss

css
.edit {
  width: 100%;
  height: 100%;
  background: #111;
}

/* 头像按钮:去掉 button 自带的内边距和边框 */
.avatar-wrapper {
  width: 200rpx;
  height: 200rpx;
  padding: 0;
  margin: 0 auto;
  background: #111;
  border: none;
  border-radius: 50%;
  overflow: hidden;
}

.avatar {
  width: 200rpx;
  height: 200rpx;
}

.name {
  display: flex;
  align-items: center;
  height: 100rpx;
  padding: 0 50rpx;
  margin: 20rpx 0;
  border: 1px solid #fff;
  color: #fff;
}

.name .label {
  padding-left: 20rpx;
  margin-right: 20rpx;
}

.name .input {
  flex: 1;
  color: #fff;
}

.footer {
  display: flex;
  justify-content: space-around;
  align-items: center;
  padding: 40rpx 0;
}

pages/login/login.js(授权后跳编辑页)

js
Page({
  // Vant 按钮的点击事件名是 bind:click
  goEdit() {
    // 授权只需一次,用 redirectTo 把登录页销毁掉,返回时不再回到登录页
    wx.redirectTo({
      url: '/pages/edit/edit'
    })
  }
})

关键行与易错点

  • 小程序不能直接用 node_modules 里的包,必须在开发者工具里执行「工具 → 构建 npm」,把它转成小程序可用的依赖,否则 import 报错。
  • app.json 的 "style": "v2" 用来去掉小程序内置组件默认样式,避免影响 Vant 组件外观。
  • 新版开发者工具目录结构变了,packNpmManually 要配合 miniprogramNpmDistDir 指向 miniprogram/,路径写错就会出现"构建 npm 找不到依赖"。
  • Vant 组件用前必须在页面 json 的 usingComponents 里注册,注册名(如 van-button)和标签名要一致。
  • van-button 的点击事件是 bind:click,而原生 button 是 bindtap,两者不要混用。
  • 头像用 <button> 包 <image> 才能调起微信头像选择,但原生 button 自带 padding 和边框,必须 padding: 0; border: none; 清掉,否则图片两边会留缝。
  • .edit 的 height: 100% 依赖父级页面高度,若父级没撑满会失效;写选择器时 .edit 是类名,别漏掉前面的点。
  • 登录页 → 编辑页用 wx.redirectTo(销毁当前页),这样从编辑页返回不会再回到登录页;用 wx.navigateTo 则会保留登录页在栈里。

常见错误 ​

  1. 生命周期里直接写 async / await 现象:onLoad 里写 async onLoad(){ await reqCategory() },页面偶发白屏、数据时序混乱,或后续维护时逻辑纠缠不清。 原因:小程序生命周期函数由框架调用,返回 Promise 不会被框架处理,异步逻辑与生命周期职责混在一起,且容易把"同步语义"写成异步。 修法:onLoad 只做一行调用——this.getCategoryData(),把 async 与 await 全部收进这个自定义方法内部。

  2. 调用组件方法忘记 this 现象:onLoad(){ getData() } 直接报 "getData is not defined"。 原因:方法挂在页面实例对象上,不是全局函数,必须通过实例访问。 修法:写成 this.getData();同时确认方法定义在 Page({ ... }) 的配置对象里。

  3. wx:for 不写 wx:key 现象:控制台反复告警,切换选中项时高亮错位、列表复用异常。 原因:缺少唯一标识,diff 无法稳定复用节点。 修法:wx:key="id"(用数据里的唯一字段名,不加 {{}}、不加 item.)。

  4. 模板里把变量当字符串写 现象:页面上直接显示 item.name、imgUrl 这样的字面量,而不是真实数据。 原因:写成了普通属性值,没有用插值语法。 修法:属性与文本一律写成 {{ item.name }}、src="{{ item.imgUrl }}";不要额外加冒号。

  5. 页面根节点样式不生效 现象:.page { height: 100% } 写了没反应,页面撑不满、背景色只覆盖内容高度。 原因:根节点是内置标签 page,不是带 class 的元素,用类选择器选不到。 修法:直接写标签选择器 page { height: 100% }(不加点),且百分比高度依赖父级,需逐级确认。

  6. npm 装的包直接 require 就报错 现象:import moment from 'moment' 后运行报错,或 vant 组件注册了却不渲染。 原因:node_modules 里的包不能直接被小程序使用,必须先转成小程序可识别的依赖目录。 修法:开发者工具 → 工具 → 构建 npm;每次新增依赖后都要重新构建,改完不生效先重新编译再刷新。

  7. 引入 UI 组件库漏配置 现象:van-button 标签写了但页面空白,或组件默认样式被小程序内置样式污染。 原因:只 npm install 了,没做三件事——app.json 加 "style": "v2"、project.config.json 里补 packNpmManually 与 packNpmRelationList(路径按新版目录写 ./ 开头)、页面 json 的 usingComponents 注册。 修法:按官方"快速上手"逐步核对,缺一步都不行。

  8. 图标字体不显示 现象:个人中心里阿里图标全是方块/空白。 原因:iconfont 的 @font-face 与类名只写在了某个页面的 wxss 里,不是全局样式。 修法:把 iconfont 样式文件放进项目,在 app.wxss 中用 @import "路径/iconfont.wxss"; 引入。

  9. 该销毁的页面用了 navigateTo 现象:授权登录页用 navigateTo 跳到编辑页,返回时又回到授权页,用户被迫二次授权。 原因:navigateTo 保留页面栈,redirectTo 关闭当前页。 修法:一次性流程(授权、登录中转)用 wx.redirectTo;需要返回上一层的用 wx.navigateTo;tabBar 页只能用 switchTab。

  10. 想隐藏导航栏却去改全局或写死结构 现象:想只让"我的"页顶部无导航,结果全站导航都没了,或自己拼了个假导航。 原因:导航栏是原生组件,只能通过配置控制,不能靠 CSS/结构改。 修法:在该页面的 json 里写 "navigationStyle": "custom",仅对当前页生效;页面配置优先级高于 app.json 全局配置。改完记得给内容补顶部安全区,避免被胶囊按钮遮挡。

练习 ​

  1. 分类页数据渲染:在 api 目录封装 reqCategory(GET、无参),在分类页 onLoad 中通过一个 async 自定义方法取数并存入 data,先 console.log 确认返回结构,再渲染左侧一级分类(含 wx:key)与右侧当前选中项的子分类,并让左侧点击切换高亮与右侧内容。

  2. vant 落地:在登录页接入 vant,完成 style:v2、project.config.json 的 npm 配置、usingComponents 注册三步,用 van-button 替换原生按钮,并用 van-divider 做分隔线,验证自定义文字与主色按钮两种形态。

  3. 隐藏顶部导航:把"我的"页改为自定义导航(navigationStyle: custom),使背景图顶到状态栏下方,同时保证头像、昵称、订单区、功能列表在真机与模拟器上都不被胶囊遮挡。

  4. 个人中心登录态两套 UI:未登录显示默认头像 + "请登录"(点击 redirectTo 到登录页),已登录显示微信头像与昵称,用条件渲染实现二选一。

  5. 编辑资料页静态:完成头像按钮(内含 image,去掉 button 默认 padding)、昵称输入行(左右 flex 布局 + 边框)、底部两个按钮(van-button 的 default / primary 组合,横向 flex 排列),并给根节点写 page 标签样式保证满屏。

原视频对照 ​

#视频时长
1分类模块业务完成12:51
2个人中心静态的查看与npm在小程序中使用33:47
3微信小程序UI组件库vant使用16:04
4用户编辑页的静态搭建19:54

小结 ​

  • 数据没打印出来之前不要写渲染逻辑,先看结构再写模板,能省掉一半排错时间。
  • 所有网络请求走 wx.request,接口必须是 https,封装到 api 层,页面里只调用不拼地址。
  • 小程序可以用 npm 生态,但"装完"不等于"能用",构建 npm 是必经的一步,且每次新增依赖都要重来一次。
  • UI 组件库(vant)能显著提效,代价是配置项多,配置顺序错一步组件就不渲染。
  • 页面配置优先级高于全局配置,导航栏、标题这类原生能力只能靠配置,不能靠 CSS。
  • 路由选择看语义:要返回用 navigateTo,一次性流程用 redirectTo,tabBar 页用 switchTab。
  • 静态页面必须自己动手搭,flex 布局与水平垂直居中是硬功夫,也是面试必问,别指望抄。
  • 样式不生效先怀疑两件事:选择器写错(比如给 page 加了点)和编辑器缓存(重新编译再刷新)。