Skip to content

微信小程序指令与页面跳转(Day2 2/2) ​

本页对应 day2,共 5 段视频,约 1 小时 46 分。

学习目标 ​

  • 能够在小程序后台完成服务器域名配置,并说明 HTTPS、域名备案、端口限制等规则,能通过“本地设置忽略 HTTPS 校验”解决开发阶段域名不合法警告。
  • 能够搭建首页“猜你喜欢”和“热门推荐”的静态结构,并将商品卡片封装为可复用组件,在页面中完成引入、注册和使用。
  • 能够调用猜你喜欢与热门推荐接口获取数据,通过页面给组件传参、组件 props 接收的方式完成商品图片、标题、价格等字段渲染。
  • 能够搭建分类页左右布局,使用 scroll-view 实现左侧菜单纵向滚动,并通过点击事件切换 active 索引实现高亮效果。
  • 能够使用 scroll-view 实现横向滚动导航,配置 scroll-x、enable-flex、scroll-into-view 和滚动动画,实现点击导航项后自动滚动定位到对应位置。
  • 能够结合 Page 生命周期函数在页面加载时发起请求,并说明页面跳转、Page 函数其他参数在小程序页面开发中的基本使用方式。

知识点 ​

先定位本节:指令要落到真实页面里 ​

本节笔记只有一句核心:列表渲染 wx:for;显示与隐藏 wx:if、wx:else、hidden。转写稿虽然没有单独讲指令语法,但后面搭首页、分类页时一直在用这些指令。所以学习时不要孤立背语法,而要把它放回“商品列表、分类菜单、高亮切换”这些真实结构里。本节笔记未附图片和完整代码,下面代码是按转写稿字段整理的示意,字段名以你实际接口为准。

列表渲染 wx:for:把重复的商品卡片交给数据 ​

为什么需要:猜你喜欢、热门推荐、分类左侧菜单、右侧商品列表,结构都高度重复。手写六份、十份既慢又容易漏,数据一变还要改模板。

怎么想:先把“一个商品长什么样”写出来,再让 wx:for 按数组重复。转写稿里首页请求到猜你喜欢数据后,存到 likeArr,页面把 list="{{likeArr}}" 传给 card 组件,组件里再遍历。分类左侧菜单也是同理,用索引和高亮状态配合。

怎么做:模板只写一项,wx:for 遍历,wx:key 指定唯一字段。转写稿反复强调“别忘了写 wx:key”,否则列表更新容易错乱。

html
<view wx:for="{{list}}" wx:key="id" class="item">
  <image src="{{item.imageUrl}}" class="goods" />
  <view class="title">{{item.name}}</view>
  <view class="subtitle">{{item.subTitle}}</view>
  <view class="bottom">
    <text class="price">{{item.price}}</text>
    <text class="origin">¥{{item.originPrice}}</text>
  </view>
</view>

易错点:默认项名是 item、索引是 index,不要自己写错;wx:key 不要省;组件接收页面数据时写 list="{{likeArr}}",小程序里不要写成 Vue 的 :list。转写稿还遇到子标题高度不固定导致卡片不对齐,最后给子标题固定高度、超出用省略号解决。

显示与隐藏 wx:if / wx:else / hidden:高亮与条件展示 ​

为什么需要:分类左侧菜单只有当前项要高亮,某些标记只在选中时出现;滚动条、提示块也常需要条件显示。不能把所有节点都永久显示。

怎么想:wx:if 是“条件成立才创建节点”,适合不常切换的结构;wx:else 必须紧跟 wx:if,中间不要插别的节点;hidden 是节点还在、只是隐藏,适合频繁切换。转写稿里分类高亮就是用 activeIndex 记录当前索引,点击菜单时通过 data-index 取索引并更新,模板里用 wx:if="{{index === activeIndex}}" 控制高亮线/标记。转写稿没有展开 hidden 的调试,这里只按笔记说明概念,不虚构实验现象。

html
<view
  wx:for="{{menus}}"
  wx:key="id"
  class="item"
  data-index="{{index}}"
  bindtap="handleMenu"
>
  {{item.name}}
  <view wx:if="{{index === activeIndex}}" class="active-line"></view>
</view>

易错点:wx:if 和 wx:else 必须相邻;频繁切换不要全用 wx:if,否则反复销毁重建;hidden 不是 wx:else 的替代品;点击事件里取索引用 e.currentTarget.dataset.index,不要写成 e.target 取错层。

数据从哪来:wx.request 域名配置不能绕 ​

为什么需要:wx:for 要有数组。转写稿里首页一加载就请求猜你喜欢和热门推荐,若不先解决 wx.request 的域名合法性,控制台一直提示“域名不合法”,列表数据拿不到。

怎么想:微信小程序要求提前配置服务器域名。转写稿演示:小程序后台 → 开发管理 → 开发设置 → 服务器域名,扫码后配置 HTTPS 域名并保存提交;配置后详情项目配置里能看到合法域名。开发阶段也可在本地设置勾选“不校验合法域名/HTTPS 证书”,但上线不能靠它。

怎么做:域名必须是 HTTPS,不能写 IP、localhost;可配端口,配了端口就只能向该端口发请求;域名要备案。请求回来后判断 code === 200,把数据存到 likeArr/hotArr,再传给组件渲染。

易错点:拿到别人代码一直报域名不合法,多半是没配服务器域名;不要把开发阶段的“忽略校验”当成上线方案;接口字段名要和模板一致,先打印数据再写渲染。

组件与滚动容器:wx:for 的常见配套 ​

为什么需要:猜你喜欢和热门推荐结构一样,转写稿把它们封装成 card 组件复用;分类左侧菜单多时要用 scroll-view 滚动,否则内容会把页面顶出去。

怎么想:页面负责请求数据,组件负责展示列表;组件没有路由,只是页面的一部分。scroll-view 纵向滚动必须写 scroll-y 并给固定高度;横向滚动写 scroll-x;转写稿调试发现 scroll-view 里用 flex 还要开 enable-flex,空间不足时子项会等比缩小,需要 flex-shrink: 0。

易错点:scroll-view 不给高度会失效;只写 scroll-view 不写 scroll-y 不滚;组件属性传参不要加冒号;分类高亮用索引切换后要记得 setData 更新 activeIndex。

先厘清本节边界:页面跳转的两种方式 ​

本节笔记只给出一句结论:微信小程序页面跳转方式有声明式导航和编程式导航。转写稿并没有继续展开这两个词对应的具体标签或 API,而是先讲网络请求域名配置、首页“猜你喜欢/热门推荐”的静态与数据渲染、分类页静态,以及 scroll-view 的滚动效果。因此这里不能臆造跳转代码;能确认的是:页面跳转之前,必须先把“目标页面”和“触发入口”准备好。转写稿明确提到,“查看更多”会进入商品列表页,点击商品会进入商品详情页。
为什么需要:跳转不是孤立动作,目标页不存在、入口没搭好,跳转就无从谈起。
怎么想:先区分页面与组件。一个应用可以有多个页面,一个页面可以有多个组件;页面有路由,组件没有路由。
怎么做:把商品卡片封装成 components/card,在首页页面中引入、注册、使用;后续“查看更多”和商品卡片就是跳转入口。
易错点:把 card 当页面去配置路由;组件里写页面生命周期;只做“查看更多”样式却不绑定后续跳转。

网络请求域名配置是跳转目标页的数据基础 ​

为什么需要:商品列表页、详情页、首页推荐都要发请求;转写稿中 wx.request 一直提示“域名不合法”,因为小程序只能向已配置的域名通信。
怎么想:先看警告,再读规则:服务器域名要在小程序后台“开发管理 → 开发设置”里配置;域名必须是 HTTPS,不能直接用 IP,也不能用 localhost;配置端口后只能向该端口发请求。
怎么做:后台配置服务器域名;开发阶段若不想配置,可在开发者工具“本地设置”勾选忽略 HTTPS 校验,但这是开发调试方案,不是上线方案。
易错点:拿到别人代码后一直报域名不合法;把 192.168.x.x 或 localhost 当合法域名;以为勾了忽略校验就等于线上可用。

首页静态:猜你喜欢与热门推荐复用卡片组件 ​

为什么需要:首页底部商品区结构重复,猜你喜欢和热门推荐都要展示商品卡片,复用组件能减少重复代码。
怎么想:标题、“查看更多”属于页面结构;商品卡片属于可复用组件。转写稿特别说明:不要把“标题 + 查看更多 + 商品列表”整体封成一个组件,因为热门推荐上方没有同样的标题和按钮。
怎么做:新建 components/card,在页面 JSON 中引入注册:

json
{
  "usingComponents": {
    "card": "/components/card/card"
  }
}

页面中使用:

html
<card list="{{likeArr}}"></card>

组件里接收:

js
properties: {
  list: {
    type: Array
  }
}

易错点:组件没有路由;页面给组件传参时不要写成 list:="{{likeArr}}",转写稿强调小程序这里不要加冒号;组件属性、自身 data、方法要分清。

数据渲染与布局调试 ​

为什么需要:静态卡片只是占位,真正展示要靠接口数据。
怎么想:页面加载时发请求,拿到数据后存到页面 data,再通过属性传给 card 组件。转写稿中首页加载调用 this.getLikeData(),热门推荐调用 this.getHot(),返回 code === 200 后分别存入 likeArr、hotArr。
怎么做:先打印确认数据,再渲染;图片地址用 item.imageUrl,标题用 item.title,子标题用 item.subTitle,现价和原价分别替换。
易错点:子标题不固定高度会导致卡片上下不对齐;标题过长要省略;图片默认缩放可能模糊,应固定宽度、让高度自适应;不要没拿到数据就先写复杂渲染。

分类页与 scroll-view:跳转落地页的常见容器 ​

为什么需要:分类页左侧导航可能很多,不能把页面撑高,需要固定区域内滚动。
怎么想:左侧用 scroll-view 做纵向滚动,右侧展示对应分类内容;横向导航则用 scroll-x。
怎么做:纵向滚动要固定高度并加 scroll-y;隐藏滚动条可用 enhanced 与 show-scrollbar="{{false}}";点击切换高亮用 activeIndex。横向滚动还要开启 enable-flex,子项加 flex-shrink: 0,否则空间不足会等比缩小。
易错点:scroll-view 不给固定高度,内容会顶出去;只写 scroll-x 不写 enable-flex,flex 布局不生效;scroll-into-view 的目标 id 不能以数字开头;想要滚动动画要加 scroll-with-animation。

回到页面跳转:本节能确认的结论 ​

为什么需要:页面跳转的两种方式名称已经给出,但转写稿没有展开具体写法,所以不能编造 API。
怎么想:先把“目标页面”准备好:商品列表页、商品详情页;再把“入口”准备好:“查看更多”和商品卡片。页面负责路由,组件只负责页面中的一部分。
怎么做:后续补声明式导航、编程式导航时,应分别绑定到“查看更多”和商品卡片点击上;当前先保证页面结构、组件复用、数据渲染和滚动容器正确。
易错点:把“页面跳转”误写成组件切换;组件当页面加路由;入口只做视觉不接逻辑;在材料未给出具体 API 时凭空补代码。

先分清应用级和页面级 ​

本节笔记只列了两组生命周期函数名:应用级 onLaunch、onShow、onHide、onError,页面级 onLoad、onShow、onReady、onHide、onUnload。学习时先不要急着背,先想清楚它们分别挂在谁身上:应用级写在 App() 里,整个小程序只有一份;页面级写在 Page() 里,每个页面各有一份。转写稿里反复出现“App 是创建应用、Page 是创建页面、Component 是创建组件”,这句话很关键:生命周期函数放错文件,就不会按预期触发。

应用级生命周期 ​

为什么需要:小程序从启动、切前台、切后台到报错,需要一个全局入口做初始化、日志和兜底。
怎么想:onLaunch 是“第一次启动”,只触发一次;onShow 是“每次看到小程序”,可能多次;onHide 是“每次退到后台”;onError 是“出错时收错误信息”。
怎么做:在 app.js 的 App({...}) 中写:

js
App({
  onLaunch() {
    // 全局只做一次:初始化、读取本地配置
  },
  onShow() {
    // 每次进入前台:可刷新全局状态
  },
  onHide() {
    // 每次进入后台:暂停定时器、保存草稿
  },
  onError(error) {
    console.error('小程序错误:', error)
  }
})

易错点:onLaunch 不要写“每次显示都要做”的逻辑;onShow 会多次触发,别重复注册事件;onError 不是业务 try/catch 的替代品,业务异常仍要自己处理。

页面级生命周期 ​

为什么需要:页面有加载、渲染、显示、隐藏、卸载的过程,不同阶段要做不同的事。
怎么想:onLoad 只一次,适合拿路由参数、发首次请求;onShow 每次显示都触发,适合刷新;onReady 只一次,表示初次渲染完成;onHide 是页面隐藏但没销毁;onUnload 是页面卸载。
怎么做:在页面 Page({...}) 中按阶段放置:

js
Page({
  onLoad(options) {
    // 只执行一次:取参数、发首次请求
  },
  onShow() {
    // 每次显示:从其他页返回也会触发
  },
  onReady() {
    // 只执行一次:初次渲染完成
  },
  onHide() {
    // 页面隐藏:暂停动画、定时器
  },
  onUnload() {
    // 页面卸载:清理定时器、解绑事件
  }
})

易错点:onLoad 不等于 onShow,从详情页返回列表页时,列表页通常触发 onShow 而不是 onLoad;onReady 只一次,不要拿它做每次刷新;onHide 不是销毁,onUnload 才是;tab 页切换一般只触发 onHide/onShow,不会触发 onUnload。

结合网络请求调试理解触发时机 ​

转写稿里讲师配置 wx.request 时,开发工具一直提示“域名不合法”。解决路径是:到小程序后台“开发管理 → 开发设置 → 服务器域名”配置 request 合法域名;开发阶段也可在开发者工具“详情 → 本地设置”勾选“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”。注意纠正同音字:是 HTTPS,不是“HTTS/HTDPS”;是服务器域名,不是“福器玉米”;域名不能使用 IP 地址或 localhost,需要 HTTPS 且通常要备案;端口范围是 0–65535,HTTPS 默认 443,不要记成 80。

这和生命周期有什么关系?转写稿说“页面一加载的时候,要拿到猜你喜欢的数据”,这类首次请求通常放 onLoad;如果页面每次显示都要刷新,才考虑 onShow。页面拿到数据后再通过属性传给 card 组件,因为“组件没有路由”,组件只是页面的一部分。不要把页面生命周期和组件生命周期混在一起,也不要把只在页面加载时做一次的请求放到 onShow 里造成重复请求。

易错点清单 ​

  1. onLaunch 只一次,onShow 可多次;不要互相替代。
  2. onLoad 只一次,onShow 每次显示都触发;返回页面时优先检查 onShow。
  3. onReady 只一次,表示初次渲染完成,不是“每次渲染完成”。
  4. onHide 不是销毁,onUnload 才是;定时器、事件监听要在 onHide 暂停、在 onUnload 清理。
  5. 页面有路由,组件没有路由;生命周期函数要写在正确的 App/Page/Component 配置里。
  6. 网络请求报“域名不合法”时,先查后台服务器域名配置,再查本地设置是否勾选忽略校验,不要误以为是生命周期写错。

本节定位:别只背“Page参数” ​

本节笔记标题是“Page函数其他参数”,但转写稿实际把几条线串在一起:wx.request 前的域名配置、页面加载后取数据、页面给组件传值、商品卡片复用,以及 scroll-view 的纵向/横向滚动。转写稿没有展开“分享”API,下面只整理可确认的内容。核心思路是:页面负责取数和组织,组件负责展示和复用。

网络请求与合法域名配置 ​

为什么需要: 直接调用 wx.request 时,控制台提示“域名不合法”,请求发不出去。小程序不是浏览器,不能随意请求任意地址。
怎么想: 先判断是代码错还是配置错。转写中请求地址本身能写,但微信要求提前登记通讯域名,所以先看后台有没有配。
怎么做: 到小程序后台“开发管理→开发设置→服务器域名”配置请求域名;域名要 HTTPS,不能用 localhost、192.168 这类 IP,通常还需备案;配置端口后只能向该端口发请求。调试阶段也可在开发者工具本地设置里勾选“不校验合法域名”等选项,先让项目跑起来。
易错点: 拿到别人代码一直提示域名不合法,多半是没配域名;勾选不校验只是开发调试,不是上线方案。

页面加载取数,再传给组件 ​

为什么需要: 猜你喜欢、热门推荐的数据要由页面请求,再交给卡片组件渲染。组件只负责展示,不应该自己乱发请求。
怎么想: 转写中先确认接口:猜你喜欢不需要 token,是 GET;页面一加载就取数。拿到 result.data 后先 setData 存到页面,再通过属性传给组件。
怎么做: 页面配置里用 usingComponents 注册 card 组件;模板中写 <card list="{{like}}"></card>。注意转写反复强调:小程序属性绑定不要写成 :list 这种 Vue 冒号写法。组件里用 properties 接收 list,类型为 Array,再在组件模板里 wx:for 渲染。
易错点: 数据请求到了但页面不显示,先看控制台有没有数据、setData 是否执行、属性名是否一致;组件没有路由,别把组件当页面跳。

商品卡片组件与静态布局 ​

为什么需要: 猜你喜欢和热门推荐下面的商品结构一样,封装成组件才能复用。
怎么想: 转写中先搭静态:外层灰色底、标题居中加粗、卡片两列、每个 item 白底、图片、标题、子标题、现价/原价、购物车小图。
怎么做: 卡片根节点用 display:flex 并允许换行;item 给固定宽度,左右用 justify-content: space-between 留缝;图片固定宽高;子标题用 display:-webkit-box、-webkit-line-clamp、overflow:hidden 做两行省略;原价加删除线。
易错点: 转写中商品没对齐,原因是子标题没固定高度,一行和两行把下面撑得不一致;给子标题固定或最小高度后对齐。组件文件是 Component 构造,不是 Page,且组件没有路由。

分类页左右布局与 scroll-view ​

为什么需要: 分类页左侧导航多时不能把页面撑长,要在固定区域内滚动。
怎么想: 外层左右 flex;左侧固定宽、右侧 flex:1 占剩余空间。左侧项目多时用 scroll-view,但必须给固定高度,否则内容会把容器顶开。
怎么做: 左侧 scroll-view 设高度为父容器高度,开启 scroll-y 才能纵向滚动;转写中还尝试隐藏滚动条,并给当前项加 active 类,用索引控制高亮。
易错点: 只写 scroll-view 不设高度,滚动不生效;不写 scroll-y,默认不是纵向滚动。点击切换高亮时,要通过事件对象取 currentTarget.dataset.index,再更新当前索引。

横向导航与 scroll-into-view 拓展 ​

为什么需要: 顶部导航项很多时要横向滚动,点击某一项还要自动滚到对应位置。
怎么想: 转写中先给 scroll-view 开 scroll-x,并给固定高度;子项横向排列要开 enable-flex,否则 flex 不生效。空间不足时 flex 子项会等比缩小,所以子项要加 flex-shrink:0。
怎么做: 每个子项给唯一 id,但 id 不能以数字开头,转写中拼成 A0、A1 这类形式;scroll-into-view 绑定当前项 id,配合 scroll-with-animation 出现滚动动画。
易错点: 不设高度内容会顶下去;不开 scroll-x 横向不动;不开 enable-flex 横向布局不生效;子项不加 flex-shrink:0 会被压缩;scroll-into-view 的值要和子项 id 对应。

收束:调试顺序 ​

先看控制台请求和域名,再看 setData 与属性名,最后看样式高度和滚动方向。转写里多次出现“先看有没有数据,再干活”,这是本节最稳的调试习惯。

课堂画面 ​

课堂画面 1

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

代码示例 ​

1. 猜你喜欢 / 热门推荐卡片组件(组件传参 + wx:for + 两行省略) ​

components/card/card.json

json
{
  "component": true,
  "usingComponents": {}
}

components/card/card.js

js
Component({
  properties: {
    list: {
      type: Array,
      value: []
    }
  }
})

components/card/card.wxml

wxml
<view class="card">
  <view class="item" wx:for="{{list}}" wx:key="id">
    <image class="goods" src="{{item.imgUrl}}" mode="widthFix"></image>
    <view class="title">{{item.name}}</view>
    <view class="sTitle">{{item.sTitle}}</view>
    <view class="bottom">
      <view class="price">¥{{item.price}}</view>
      <view class="right">
        <text class="oldPrice">¥{{item.oldPrice}}</text>
        <image class="cardIcon" src="{{item.cartIcon}}" mode="aspectFit"></image>
      </view>
    </view>
  </view>
</view>

components/card/card.wxss

wxss
.card {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
}

.item {
  width: 360rpx;
  background: #fff;
  margin: 20rpx 0;
}

.goods {
  width: 360rpx;
  height: 180rpx;
}

.title {
  font-size: 26rpx;
  font-weight: 900;
}

.sTitle {
  font-size: 24rpx;
  color: #666;
  min-height: 80rpx;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

.bottom {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

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

.oldPrice {
  color: #ccc;
  text-decoration: line-through;
  margin-left: 22rpx;
}

.cardIcon {
  width: 48rpx;
  height: 48rpx;
  margin-left: 22rpx;
}

pages/index/index.json

json
{
  "usingComponents": {
    "card": "/components/card/card"
  }
}

pages/index/index.wxml

wxml
<view class="footer">
  <view class="like">猜你喜欢</view>
  <card list="{{likeList}}" />
  <button class="more">查看更多</button>
</view>

<view class="footer">
  <view class="like">热门推荐</view>
  <card list="{{hotList}}" />
  <button class="more">查看更多</button>
</view>

pages/index/index.js

js
Page({
  data: {
    likeList: [
      { id: 1, imgUrl: '', name: '浪漫情人节音乐球', sTitle: '浪漫情人节音乐球', price: '1999', oldPrice: '1299', cartIcon: '' },
      { id: 2, imgUrl: '', name: '香水护体', sTitle: '香水护体', price: '1999', oldPrice: '1299', cartIcon: '' },
      { id: 3, imgUrl: '', name: '浪漫情人节音乐球', sTitle: '浪漫情人节音乐球', price: '1999', oldPrice: '1299', cartIcon: '' },
      { id: 4, imgUrl: '', name: '香水护体', sTitle: '香水护体', price: '1999', oldPrice: '1299', cartIcon: '' },
      { id: 5, imgUrl: '', name: '浪漫情人节音乐球', sTitle: '浪漫情人节音乐球', price: '1999', oldPrice: '1299', cartIcon: '' },
      { id: 6, imgUrl: '', name: '香水护体', sTitle: '香水护体', price: '1999', oldPrice: '1299', cartIcon: '' }
    ],
    hotList: [
      { id: 7, imgUrl: '', name: '浪漫情人节音乐球', sTitle: '浪漫情人节音乐球', price: '1999', oldPrice: '1299', cartIcon: '' },
      { id: 8, imgUrl: '', name: '香水护体', sTitle: '香水护体', price: '1999', oldPrice: '1299', cartIcon: '' }
    ]
  }
})

pages/index/index.wxss

wxss
.footer {
  width: 750rpx;
  background: #f5f5f5;
}

.like {
  text-align: center;
  font-size: 40rpx;
  font-weight: 900;
  margin-top: 10rpx;
}

.more {
  width: 200rpx;
  height: 42rpx;
  line-height: 42rpx;
  font-size: 24rpx;
  border-radius: 20rpx;
  margin: 20rpx auto;
}

要点解释:

  • 组件里用 properties.list 接收页面传入的数组,页面用 list="{{likeList}}" 传参,注意小程序里不要写成 :list。
  • wx:for="{{list}}" 渲染商品,wx:key="id" 避免列表更新问题。
  • 子标题必须固定 min-height,否则一行/两行内容会导致卡片底部价格不对齐。
  • -webkit-line-clamp: 2 实现两行省略,必须配合 display: -webkit-box 和 overflow: hidden。
  • 组件没有路由,页面才需要通过 usingComponents 引入并注册组件。

2. scroll-view 横向滚动导航 + scroll-into-view 动画定位 ​

pages/scroll-nav/scroll-nav.json

json
{}

pages/scroll-nav/scroll-nav.wxml

wxml
<scroll-view
  class="scroll"
  scroll-x
  enable-flex
  scroll-into-view="A{{currentIndex}}"
  scroll-with-animation
>
  <view
    class="item"
    wx:for="{{navList}}"
    wx:key="*this"
    id="A{{index}}"
    data-index="{{index}}"
    bindtap="handleNav"
  >
    {{item}}
  </view>
</scroll-view>

pages/scroll-nav/scroll-nav.wxss

wxss
page {
  height: 100%;
}

.scroll {
  width: 100%;
  height: 80rpx;
  display: flex;
}

.item {
  flex-shrink: 0;
  width: 100rpx;
  height: 80rpx;
  line-height: 80rpx;
  text-align: center;
  white-space: nowrap;
}

pages/scroll-nav/scroll-nav.js

js
Page({
  data: {
    currentIndex: 0,
    navList: [
      '军事', '军事1', '军事2', '军事3', '军事4', '军事5',
      '军事6', '军事7', '军事8', '军事9', '军事10', '军事11',
      '军事12', '军事13', '军事14', '军事15'
    ]
  },

  handleNav(e) {
    this.setData({
      currentIndex: e.currentTarget.dataset.index
    })
  }
})

要点解释:

  • 横向滚动必须写 scroll-x,纵向滚动写 scroll-y。
  • enable-flex 开启后,scroll-view 上的 display: flex 才生效。
  • 子项必须写 flex-shrink: 0,否则空间不足时 flex 会等比例缩小,宽度失效。
  • scroll-into-view="A{{currentIndex}}" 要和子节点 id="A{{index}}" 对应,且 id 不能以数字开头。
  • scroll-with-animation 控制滚动到指定位置时是否有动画。
  • scroll-view 无论横向还是纵向,都必须有固定高度,否则内容会撑开或滚动异常。

3. 分类左右布局 + 左侧 scroll-view 纵向滚动 + active 高亮 ​

pages/category/category.json

json
{}

pages/category/category.wxml

wxml
<view class="category">
  <scroll-view class="left" scroll-y enhanced show-scrollbar="{{false}}">
    <view
      class="item {{currentIndex === index ? 'active' : ''}}"
      wx:for="{{cateList}}"
      wx:key="*this"
      data-index="{{index}}"
      bindtap="handleCate"
    >
      {{item}}
    </view>
  </scroll-view>

  <view class="right">
    <view class="title">香水护体</view>
    <view class="box">
      <view class="item" wx:for="{{rightList}}" wx:key="*this">
        <image class="img" src="" mode="aspectFit"></image>
        <view class="kttle">香水有毒</view>
      </view>
    </view>
  </view>
</view>

pages/category/category.wxss

wxss
page {
  height: 100%;
}

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

.left {
  width: 180rpx;
  height: 100%;
  background: #f5f5f5;
}

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

.left .active {
  background: #fff;
  color: #000;
  font-weight: 900;
}

.right {
  flex: 1;
  height: 100%;
}

.right .title {
  text-align: center;
  font-size: 30rpx;
  margin: 20rpx 0;
}

.box {
  display: flex;
  flex-wrap: wrap;
}

.box .item {
  width: 33.33%;
  display: flex;
  flex-direction: column;
  align

常见错误 ​

  1. 一发请求,控制台就刷“域名不合法” 现象:wx.request 能发出、也能拿到数据,但底部一直黄字警告,换台电脑/换个人跑代码就报得更凶。 原因:小程序只允许向“已配置的服务器域名”通信;开发时很多人勾了“不校验合法域名”本地能跑,代码一交出去别人没勾就炸。 修法:后台 → 开发管理 → 开发设置 → 服务器域名,把接口域名(HTTPS、不能是 IP/localhost、已备案)配进去;只在本地调试才用“忽略 HTTPS 校验”,且不要把它当成解决方案交付。

  2. 组件建出来了,页面里就是不显示 现象:标签写了、样式也写了,页面上什么都没有,控制台还报“未找到组件”。 原因:三个环节缺一个——组件文件是用“新建 Page”建的(该用 Component)、页面 json 里没在 usingComponents 注册、或者路径写错(components/card/card 少个前导 /)。 修法:组件必须 Component() 创建;页面 json 里 "card": "/components/card/card" 注册后才能用 <card />;组件没有路由,别想着跳转到它。

  3. 给组件传值,子组件收到的永远是空 现象:父页面数据明明有,properties 里打印是 undefined 或 []。 原因:属性写成了 Vue 的 :list="likeArr" 或 list="{{likeArr}}"——小程序没有 : 语法,冒号会被当成字符串的一部分;或者 properties 里 key 名和父级写的对不上。 修法:写成 list="{{likeArr}}",键名两边逐字对齐;父级数据要先用 setData 落到 data 上,子组件才有值可接。

  4. scroll-view 里内容一大堆,就是滚不动 现象:内容全被撑开或者直接溢出到屏幕外,手指划不动;有时候横着能滚、竖着不能。 原因:scroll-view 必须有固定高度(或宽高受限)才能产生滚动区域,光给内容高度没用;竖滚还要显式加 scroll-y,横滚加 scroll-x。 修法:给 scroll-view 一个确定高度(百分比要保证父级有高度),补 scroll-y / scroll-x;不想要滚动条加 show-scrollbar="{{false}}"。

  5. flex 子项宽度设了 360rpx,实际被压扁 现象:写了 width: 360rpx,渲染出来却窄了一截,几个卡片挤在一起。 原因:父容器空间不足时,flex 项目默认 flex-shrink: 1 会等比缩小,你写的宽度只是“期望值”。 修法:flex-shrink: 0 锁死尺寸,或者父级 flex-wrap: wrap 让它换行;scroll-view 里用 flex 还要额外开 enable-flex,否则 display:flex 根本不生效。

  6. scroll-into-view 点了没反应 / 没动画 现象:点击导航项,列表不滚动到对应位置,或者“啪”地跳过去没有过渡。 原因:目标子节点的 id 用了数字开头(如 0、1),属性值不合法;或者只改了值没加动画属性。 修法:id 用字母开头拼接,如 a{{index}};scroll-into-view="a{{currentIndex}}",并加 scroll-with-animation。

  7. 卡片区标题、副标题高低不齐,一排看着“狗啃” 现象:有的卡片两行文字、有的一行,底部价格对不齐。 原因:副标题容器高度由内容撑开,行数不同高度就不同。 修法:给副标题固定(或最小)高度 + 多行省略(-webkit-box + line-clamp + overflow:hidden),让每张卡片骨架一致。

  8. 列表渲染后勾选/高亮状态乱串 现象:点第 3 项,第 1 项也亮了,或者删除一项后剩下的状态全错位。 原因:wx:for 没写 wx:key,或用了 index 当 key 又对数组做了增删。 修法:用数据里的唯一字段(如商品 id)做 wx:key;只有纯静态、不重排的列表才允许用 index。

练习 ​

  1. 域名配置排障:拿一个线上 HTTPS 接口,在“不校验合法域名”关闭的状态下让它跑通;记录你改了哪几处配置,并说明如果接口是 http://192.168.x.x:8080 为什么必须换域名。

  2. 组件拆分:把“猜你喜欢”里的单个商品卡片抽成 card 组件,父页面通过 properties 传一个数组进去;要求父页面在 onLoad 拿到数据后 setData 再渲染,并验证子组件能正确收到 6 条数据。

  3. 两列布局:用 flex 实现商品两列排布,卡片间距 20rpx、左右各留 15rpx;故意把某个标题写超长,验证你的省略与对齐方案在“一行标题”和“两行标题”混排时底部价格仍然对齐。

  4. 左侧分类滚动:分类列表造 100 条数据,用 scroll-view 做纵向滚动并隐藏滚动条;点击某一项时该项高亮,且列表自动滚到可视区域并带过渡动画。

  5. 生命周期辨析:在首页 onLoad、onShow 里各打一条日志,然后从详情页返回首页,观察打印顺序;据此回答:如果需求是“每次回到首页都刷新猜你喜欢数据”,请求应该放在哪个钩子里,为什么。

原视频对照 ​

#视频时长
1小程序网络配置10:04
2首页猜你喜欢与热门推荐的静态的搭建30:32
3获取猜你喜欢与热门推荐的数据并且渲染出来14:42
4分类的静态搭建32:53
5scroll-view顶部导航效果---拓展的技能点18:40

小结 ​

  1. 域名配置是交付级问题,不是本地开关问题——“忽略校验”只能救你自己,救不了别人的电脑。
  2. 组件三件套缺一不可:Component() 创建、usingComponents 注册、标签使用;路径前导 / 是排查高频点。
  3. 小程序模板语法是 {{}} 世界,Vue 的 : 绑定在这里是错的,属性名和值都要逐字核对。
  4. scroll-view 的心智模型是“固定窗口 + 可滚动内容”,没有确定尺寸就没有滚动,方向必须显式声明。
  5. flex 的默认收缩行为会悄悄改写你写的宽度,凡是“宽度不对”先怀疑 flex-shrink。
  6. 列表类 UI 的观感一致,靠的是骨架固定(高度、行数、省略规则),不是靠调字体大小。
  7. wx:key、id 命名、scroll-into-view 这类细节,单看都是小语法,凑在一起就是“点了没反应”和“状态串台”。
  8. 请求放 onLoad 还是 onShow,取决于“数据是否需要每次可见时刷新”,这是业务判断而非语法判断。