小程序案例展示

小程序案例展示

7. 案例展示

7.1 创建页面

为了节省时间,我们使用上面所说的简单方式进行编写

在全局配置文件 app.json 中的 pages 属性中,直接编写页面名称,然后保存,工具会自动在 pages 目录下创建对应 的页面

"pages":[
    "pages/welcom/welcom",
    "pages/posts/posts",
    "pages/index/index",
    "pages/logs/logs"
  ],

7.2 初始化项目

7.2.1基本配置

image.png

7.2.1.1 配置原因
  • 增强编译:让我们可以尽情使用ES的新语法
  • 不校验合法域名。。。。:微信小程序要求请求的后台 API 必须是 https 的,我们在本地开发中不 可能安装证书,勾选此项后,本地开发时,微信小程序就可以先忽略这个问题。但小程序上线时, 后台接口必须时 https 协议,且必须配置到域名列表中
7.2.1.2 全局配置文件

在此注意:

  1. 每次删除目录或文件,小程序都会重新编译,所以当上面列出的目录和文件都删除完成后,控制台有如 下错误 (所以小程序与 vue 一样,都需要一个入口文件,那就是 app.json)

    image.png

  2. app.json 文件不能为空,否则会报如下错误

    image.png

  3. 在app.json中只写入 一个

    {}
    

    也会报错:

    image.png

    意思是这个文件需要一个 pages 字段

  4. 当我们写入 pages 字段,不书写内容时,

    {
        "pages":[]
    }
    

    image.png

    这是一个小细节: pages字段中不能为空,至少需要存在一项

  5. 当我们写入一项,则不会再报错了

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

附:说一下微信开发者工具创建页面的方式以及 pages/welcome/welcome 各个路径的意义

到现在为止,我们发现程序没有任何错误,那么是不是意味着,程序不需要 app.js 文件呢? 不是的,因为在预览时,就强制要求必须有 app.js 文件

7.2.2 样式单位

使用我们微信小程序的单位rpx进行编写(上面有讲到)

7.2.3 请求的数据的路径
名称请求类型请求状态接口地址接口描述创建时间路径
获取搜索建议get开启/api/subject2021-04-19 19:34:30https://www.fastmock.site/mock/553417b978737e91d9112446e179feec/shifuji/api/subject
文章搜索get开启/api/search通过问号传值,传递搜索参数2021-04-19 19:28:50https://www.fastmock.site/mock/553417b978737e91d9112446e179feec/shifuji/api/search
获取轮播图数据get开启/api/focus轮播图2021-04-19 15:37:07https://www.fastmock.site/mock/553417b978737e91d9112446e179feec/shifuji/api/focus
发表评论post开启/api/posts/:id/comment发表评论2021-04-16 16:12:09https://www.fastmock.site/mock/553417b978737e91d9112446e179feec/shifuji/api/posts/:id/comment
更改收藏状态put开启/api/posts/:id/collection文章id通过url传递 收藏状态通过body传递2021-04-16 11:32:50https://www.fastmock.site/mock/553417b978737e91d9112446e179feec/shifuji/api/posts/:id/collection
文章详情get开启/api/posts/:postid2021-04-16 08:30:38https://www.fastmock.site/mock/553417b978737e91d9112446e179feec/shifuji/api/posts/:postid
获取文章列表get开启/api/posts返回诗词列表2021-04-13 14:02:34https://www.fastmock.site/mock/553417b978737e91d9112446e179feec/shifuji/api/posts

7.3 欢迎页

image.png

wxml

<view class="container">
 <view class="avatar-container">
  <open-data class="avatar" type="userAvatarUrl"></open-data>
 </view>
  <view class="usernick"><text class="motto">你好,</text>
    <open-data type="userNickName" lang="zh_CN"></open-data>
  </view>
  <view class="button-container">
      <navigator url="/pages/posts/posts"  class="self-button">进入小程序</navigator>      
  </view>
</view>

wxss

/* pages/welcom/welcom.wxss */
.container{
  display: flex;
  flex-direction: column;
  align-self: center;
  background-color: #b3d4db;
}
.avatar-container{
  width: 200rpx;
  height: 200rpx;
  margin-top: 160rpx;
  border-radius: 50%;
  /* border: 1px solid black; */
  overflow: hidden;
}
.avatar{
  width: 200rpx;
  height: 200rpx;
}
.usernick{
  display: flex;
  flex-direction: row;
  margin-top: 100rpx;
  color: #666;
}
.motto{
  font-size: 32rpx;
  font-weight: bold;
}
.button-container{
  border: 1px solid #405f89;
  width: 200rpx;
  height: 80rpx;
  border-radius: 5px;
  text-align: center;
  margin-top: 200rpx;
}
.self-button{
  font-size: 22rpx;
  color: #405f89;
  line-height: 88rpx;
  font-weight: bold;
}
page{
  background-color: #b3d4db;
}

说明

  • 微信小程序推荐使用 felx 布局
  • .button-containe 是我们定义的一个类似按钮的结构,因为要将小程序的 button 组件改造成这个 样式还是很难的,而且完全没有必要。另外,后面我们将 button-containe 封装成一个组件,所以 页面都可以调用
  • navigator 页面跳转组件
  • 根目录下新建 images 目录
7.3.1 展示用户信息

上面展示的用户头像和名称都是假数据

可以使用 **open-data**开放能力获取用户实际信息

<!-- <image class="avatar" src="/images/avatar/1.jpg"></image> -->
<!-- 使用 open-data 替换上面的 image -->
<open-data class="avatar" type="userAvatarUrl"></open-data>

虽然可以展示头像,但不是原型的,

.avatar 中设置的属性没有生效 如何解决 外面包裹一层view,设置 view 圆角,并隐藏溢出

<view class="avatar-container">
<open-data class="avatar" type="userAvatarUrl"></open-data>
</view>
7.3.1.1 展示用户昵称
<text class="motto">你好,</text>
<open-data class="motto" type="userNickName"></open-data>

在此注意,我们不能把 不能将 open-data 嵌入到 text 组件中

哪如何放到一行呢?

使用容器包裹起来,并将 motto 样式应用到容器上

<view class="user-nick motto">
<text>你好,</text>
<open-data type="userNickName"></open-data>
</view>
.user-nick{
display: flex;
}
.motto{
margin-top: 100rpx;
color:#666;
font-size: 32rpx;
font-weight: bold;
}

7.4 设置全局导航栏

在 app.json 中,加入如下配置,所有页面生效

"window":{
    "navigationBarBackgroundColor": "#fff",
    "navigationBarTitleText": "爱读诗",
    "navigationBarTextStyle":"black"
  },

参见 window 配置

注意:

  • 我们可以在文件自己内部的json文件中配置他的局部头部样式,而局部会覆盖全局

  • 页面级 json 文件中,不用写到 window 中,因为这里只能配置 window 信息,不能配置其他, 如 pages 等信息

7.5 阅读页面

image.png

7.5.1 轮播图

我们可以使用 swiper组件 来编写轮播图,下面是他的属性及作用

滑块视图容器。其中只可放置swiper-item组件,否则会导致未定义的行为。

属性类型默认值必填说明最低版本
indicator-dotsbooleanfalse是否显示面板指示点1.0.0
indicator-colorcolorrgba(0, 0, 0, .3)指示点颜色1.1.0
indicator-active-colorcolor#000000当前选中的指示点颜色1.1.0
autoplaybooleanfalse是否自动切换1.0.0
currentnumber0当前所在滑块的 index1.0.0
intervalnumber5000自动切换时间间隔1.0.0
durationnumber500滑动动画时长1.0.0
circularbooleanfalse是否采用衔接滑动1.0.0
verticalbooleanfalse滑动方向是否为纵向1.0.0
previous-marginstring“0px”前边距,可用于露出前一项的一小部分,接受 px 和 rpx 值1.9.0
next-marginstring“0px”后边距,可用于露出后一项的一小部分,接受 px 和 rpx 值1.9.0
snap-to-edgeboolean“false”当 swiper-item 的个数大于等于 2,关闭 circular 并且开启 previous-margin 或 next-margin 的时候,可以指定这个边距是否应用到第一个、最后一个元素2.12.1
display-multiple-itemsnumber1同时显示的滑块数量1.9.0
easing-functionstring“default”指定 swiper 切换缓动动画类型2.6.5
bindchangeeventhandlecurrent 改变时会触发 change 事件,event.detail = {current, source}1.0.0
bindtransitioneventhandleswiper-item 的位置发生改变时会触发 transition 事件,event.detail = {dx: dx, dy: dy}2.4.3
bindanimationfinisheventhandle动画结束时会触发 animationfinish 事件,event.detail 同上1.9.0

WXML

 <!-- 轮播图 -->
  <swiper indicator-dots="{{true}}" indicator-color="orange" indicator-active-color="#fff" autoplay="{{true}}"
    vertical="{{false}}">
    <swiper-item>
      <image src="/image/1.png"></image>
    </swiper-item>
    <swiper-item>
      <image src="/image/2.png"></image>
    </swiper-item>
    <swiper-item>
      <image src="/image/3.png"></image>
    </swiper-item>
  </swiper>
swiper{
width:100%;
height:460rpx
}
swiper image{
width:100%;
height:460rpx
}

总结:

  • swiper 是滑块视图容器,并不是只能作为图片的轮播图
  • swiper 中只可放置swiper-item组件,否则会导致未定义的行为,好比 ul 中只能放置 li 标签一样
  • swiper-item 仅可放置在swiper组件中,宽高自动设置为100%。
  • t通过插槽的方式向 swiper-item 组件中放置元素,swiper-item 实现并不知道,所以如上面案例, swiper-item 中放置的图片的尺寸,需要我们自己定义
  • 定义尺寸时,需要设置 image 与 swiper 的宽高一致即可,swiper 不用单独设置,因为其默认宽 高自动设置为swiper的100%
  • 关于属性设置:swiper 组件上有几个属性,如果属性值要求为布尔值,需要使用{{}} 将 true 或者 false 包含起来,否则会将值是做字符串,所以,“true” 和 “false” 的布尔值都是“真”,导致无法达 到我们期望的效果
  • 当前图片路径来自本地,实际开发中应该来自后台接口响应的数据
7.5.2 阅读列表

在 swiper 下加入如下代码

<!-- 诗词阅读列表 -->
<view class="post-container">
    <!-- 头像和发表日期 -->
    <view class="post-author-date">
        <image class="post-author" src="/images/avatar/2.jpg"></image>
        <text class="post-date">2020-04-06</text>
    </view>
    <!-- 标题 -->
    <text class="post-title">碧玉妆成一树高,万条垂下绿丝绦</text>
    <image class="post-image" src="/images/posts/biyu.webp"></image>
    <text class="post-content">
        杨柳的形象美是在于那曼长披拂的枝条。一年一度,它长出了嫩绿的新叶,丝丝下垂,在春风吹拂中,有着一种迷人的意态。这是谁都能欣赏的。古典诗词中,借用这种形象美来形容、比拟美人苗条的身段,婀娜的腰身,也是读者所经常看到的。这诗别出新意,翻转过来。“碧玉妆成一树高”,一开始,杨柳就化身为美人而出现:“万条垂下绿丝绦”,这千条万缕的垂丝,也随之而变成了她的裙带....
    </text>
    <view class="post-like">
        <image class="post-like-image" src="/images/icon/collection.png">
        </image>
        <text class="post-like-font">99</text>
        <image class="post-like-image" src="/images/icon/like.png"></image>
        <text class="post-like-font">120</text>
    </view>
</view>

说明:

  • 收藏和喜欢图片采用的是 iconfont 中的字体图标,下载其 png 格式图片

样式

.post-container {
  display: flex;
  flex-direction: column;
  margin-top: 20rpx;
  margin-bottom: 40rpx;
  background-color: #fff;
  border-bottom: 1px solid #ededed;
  padding-bottom: 10rpx;
}

.post-author-date {
  /* margin-top:10rpx;
  margin-bottom: 20rpx;margin-left: 10rpx; */
  margin: 10rpx 0 20rpx 10rpx;
  display: flex;
  flex-direction: row;
  align-items: center;
}

.post-author {
  width: 60rpx;
  height: 60rpx;
  /* vertical-align: middle; */
}

.post-date {
  margin-left: 20rpx;
  font-size: 26rpx;
  /* vertical-align: middle; */
}

.post-title {
  font-size: 34rpx;
  font-weight: 600;
  margin-bottom: 20rpx;
  margin-left: 20rpx;
  color: #333;
}

.post-image {
  width: 100%;
  height: 340rpx;
  margin-bottom: 30rpx;
}

.post-content {
  color: #666;
  font-size: 28rpx;
  margin-bottom: 20rpx;
  margin-left: 20rpx;
  line-height: 40rpx;
  letter-spacing: 2rpx;
}

.post-like {
  display: flex;
  flex-direction: row;
  align-items: center;
  margin-left: 20rpx;
}

.post-like-image {
  /* height:32rpx;
width:32rpx; */
  font-size: 32rpx;
  color: red;
  margin-right: 16rpx;
}

/* html */
.post-like-font {
  margin-right: 40rpx;
  font-size: 26rpx;
}

说明

  • 整体仍然采用 flex 布局,包括实现文字与图片垂直居中,使用 flex 布局要比使用其他方式更加简 单直观
7.5.3 使用字体图标

由于使用图片会出现很多问题,所以我们讲上面收藏和喜欢使用字体图标来代替图片

7.5.3.1 使用图片会出现的问题:
  1. 图片尺寸固定,某些尺寸屏幕上可能造成浪费
  2. 难以修改图片颜色
7.5.3.2 使用图标字体代替图片的步骤:
  • 添加字体图标到购物车

  • 将其添加至项目

  • 在项目中选中 Font class ,然后选择 “查看在线字体”,然后生成在线地址

  • 地址栏中访问上面的地址,查看生成的代码,然后右键另存为到项目文件夹中,修改扩展名为 wxss,保存到根目录下

  • 在根目录下的 app.wxss 文件中,使用下面代码引入

    @import '/iconfont.wxss'
    
  • 最后在页面或组件中直接使用即可

    <text class="iconfont icon-Like"></text>
    
  • 所以修改我们posts 页面中的代码

    <view class="post-like">
            <!-- <image class="post-like-image" src="/images/icon/collection.png"></image> -->
            <text class="post-like-image iconfont icon-shoucang1"></text>
            <text class="post-like-font">{{postData.collection}}</text>
            <!-- <image class="post-like-image" src="/images/icon/like.png"></image> -->
            <text class="post-like-image iconfont icon-shouchang"></text>
            <text class="post-like-font">{{postData.reading}}</text>
          </view>
    
  • 对 post-like-image 样式类也要做适当的修改

    .post-like-image {
      /* height:32rpx;
    width:32rpx; */
      font-size: 32rpx;
      color: red;
      margin-right: 16rpx;
    }
    
7.5.4 动态数据

实际开发中,数据应该从后台api获取

这是使用基于 mock 技术的 https://www.fastmock.site/ ps:下面有对 mock的讲解

posts.js 代码

data: {
    postsList: []
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
    const url=`https://www.fastmock.site/mock/feab55dd20334e49edf0d5adb2f79cbe/libaiserver/api/posts`wx.request({
        url: url,
        success:(res)=> {
            this.setData({
                postsList: res.data.data
            })
        }
    })

修改 posts.wxml

<block wx:for="{{postsList}}" wx:key="id">
    <view class="post-container">
        <!-- 头像和发表日期 -->
        <view class="post-author-date">
            <image class="post-author" src="{{item.avatar}}"></image>
            <text class="post-date">{{item.date}}</text>
        </view>
        <!-- 标题 -->
        <text class="post-title">{{item.title}}</text>
        <image class="post-image" src="{{item.imgSrc}}"></image>
        <text class="post-content">{{item.desc}}</text>
        <view class="post-like">
            <!-- <image class="post-like-image"src="/images/icon/collection.png"></image> -->
            <text class="post-like-image iconfont icon-collection"></text>
            <text class="post-like-font">{{item.collection}}</text>
            <!-- <image class="post-like-image" src="/images/icon/like.png"></image> -->
            <text class="post-like-image iconfont icon-Like"></text>
            <text class="post-like-font">{{item.reading}}</text>
        </view>
    </view>
</block>
  • 小程序建议,如果便利的模板结构比较复杂,使用 block 进行包裹,并在 block 上进行遍历
7.5.5 页面跳转

点击 welcome 中的 进入小程序,跳转到 posts 页面

参考 路由api

  • navigateTo :保留当前页面,跳转到应用内的某个页面,页面左上角会出现返回标志,再返回本页面 onShow 会被触发,离开页面时 onHide 会触发
  • redirectTo:关闭当前页面,跳转到应用内的某个页面,页面左上角不会出现返回标志,再返回本页面 onLoad、onShow、onReady 会被触发,相当于页面首次加载,离开页面时 onUnLoad 会触发
  • navigateTo 适合于父子关系的页面,redirectTo适合级别相等的页面

直接跳转

welcome.wxml

 <view class="button-container">
      <navigator url="/pages/posts/posts"  class="self-button">进入小程序</navigator>      
  </view>

点击跳转

welcome.wxml

<view class="button-container" bind:tap="toToPosts">
    <text class="self-button">进入小程序</text>
</view>

welcome.js

toToPosts:function(){
    wx.redirectTo({
        url: '/pages/posts/posts',
    })
}
7.5.6 bind:tap 和 catch:tap

bindtap和catchtap都属于点击事件函数,将事件绑定到组件上,点击组件后可以触发函数。

二者的区别在于是否冒泡事件。

bind:tap :冒泡

catch:tap:阻止事件冒泡

冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递。

非冒泡事件:当一个组件上的事件被触发后,该事件不会向父节点传递。

7.5.7 点击头像查看大图

利用 图片预览API

image.png

posts.js 中的 previeImg 函数代码

 // 预览头像
  previeImg(event){
    let imgArr=[]
    this.data.postsList.forEach(item=>{
      imgArr.push({url:item.imgSrc})
    })
    console.log(imgArr)
    wx.previewMedia({
      sources:imgArr
    })
  },

任务单2.gif

7.5.8 跳转到详情页

为整个 .post-container 绑定 tap 事件

<block wx:for="{{postsList}}" wx:key="id">
        <view class="post-container" bind:tap="gotoDetail" data-id="{{item.postId}}">
            <!-- 头像和发表日期 -->
            <view class="post-author-date">
  // 跳转到详情页面
  gotoDetail(event){
    console.log(event);
    const postId=event.currentTarget.dataset.id
   wx.navigateTo({
     url: '/pages/detail/detail?postId='+postId,
   })
  },

然后在详情页的 onLoad 函数中,通过 options对象获取传递的数据

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    console.log(options);
  },
7.5.9 注意点

当前点击用户头像,在大图预览后,也会跳转页面,这是因为bind:tap会触发事件冒泡现象,所以我们要使用使用 catch:tap 替换 bind:tap

7.6 详情页

7.6.1 静态页面布局
<view class="container">
    <image class="head-image" src="/images/posts/3.jpg"></image>        
    <view class="author-date">
        <image class="avatar" src="/images/avatar/2.jpg"></image>
        <text class="author">李白</text>
        <text class="const-text">发表于</text>
        <text class="date">24小时前</text>
    </view>
    <text class="title">春眠不觉晓</text>
    <view class="tool">
        <view class="circle"> 
            <image bind:tap="onCollect" class="" src="/images/icon/collection.png"></image>
            <image bind:tap="onShare" class="share-img" src="/images/icon/share.png"></image>
        </view>
        <view class="horizon"></view>
    </view>
    <text class="detail">撒发射点发射点发射点发士大夫撒旦范德萨</text>
</view>
.container{
  display: flex;
  flex-direction: column;
  margin-top: -200rpx;
}
.head-image{
  width: 100%;
  height: 460rpx;
  }
  .author-date{
  display: flex;
  flex-direction: row;
  align-items: center;
  margin-top: 20rpx;
  margin-left: 30rpx;
  }
  .avatar{
  width: 64rpx;
  height: 64rpx;
  }
  .author{
  font-size: 30rpx;
  font-weight: 300;
  margin-left:20rpx;
  color:#666;
  }
  .const-text{
  font-size: 24rpx;
  color: #999;
  margin-left: 20rpx;
  }
  .date{
  font-size: 24rpx;
  margin-left: 30rpx;
  color:#999;
  }
  .title{
  margin-left: 40rpx;
  font-size: 36rpx;
  font-weight: 700;
  margin-top: 30rpx;
  letter-spacing: 2px;
  color:#4b556c;
  }
  .tool{
    display: flex;
    align-items: center;
  }
  .circle{
  display: flex;
  width: 660rpx;
  flex-direction: row;
  justify-content: flex-end;
  }
  .circle image{
    width: 90rpx;
    height: 90rpx;
    }
    .share-img{
    margin-left: 30rpx;
    }
    /* •主轴 交叉轴 */
    .horizon{
    width: 660rpx;
    height: 1px;
    background-color: #e5e5e5;
    position: absolute;
    z-index: -99;
    }
    .detail {
    color: #666;
    margin-left: 30rpx;
    margin-top: 20rpx;
    margin-right: 30rpx;
    line-height: 44rpx;
    letter-spacing: 2px;
  }
  .contall{
    width: 100%;
    height: 100%;
    margin-top:60rpx;
  }
  .ctater{
    margin: 0 30rpx;
  }
  .contall-text{
    display: inline-block ;
    margin-bottom: 30rpx;
  }
  .contall-input{
    border: 1px solid #333333;
  }
  .mini-btn{
    margin-top: 30rpx;
    margin-left: 500rpx;
  }
  .pingcss{
    margin: 30rpx 30rpx;
  }
  .zhanShi{
    display: flex;
    justify-content: flex-start;
  }
  .contsimg{
    margin-top: 30rpx;
  }
  .contsimg image{
    width: 90rpx;
    height: 90rpx;
    margin-right: 20rpx;
  }
  .conts{
    display: flex;
    flex-direction: column;
    justify-content: space-around;
  }
7.6.2 动态获取数据
/**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    wx.request({
      url: 'https://www.fastmock.site/mock/feab55dd20334e49edf0d5adb2f79cbe/libai-server/api/posts/' + options.postId,
      success: (res) => {
        this.setData({
          postData: res.data.data
        })
      }
    })
  },

修改 wxml

<!--pages/detail/detail.wxml-->
<view class="container">
  <image class="head-image" src="{{post.headImgSrc}}"></image>
  <view class="author-date">
    <image class="avatar" src="{{post.imgSrc}}"></image>
    <text class="author">{{post.author}}</text>
    <text class="const-text">发表于</text>
    <text class="date">{{post.dateTime}}</text>
  </view>
  <text class="title">{{post.title}}</text>
  <view class="tool">
    <view class="circle">
      <image class="post-like-image" wx:if="{{!post.isCollection}}" bindtap="onCollect" src="/image/icon/收藏(1).png">
      </image>
      <image class="post-like-image" wx:else bindtap="onCollect" src="/image/icon/收藏.png"></image>
      <image bind:tap="onShare" class="share-img" src="/image/icon/分 享.png"></image>
    </view>
    <view class="horizon"></view>
  </view>
  <text class="detail">{{post.detail}}</text>
  <view class="contall">
    <view class="ctater">
      <text class="contall-text">评论区:</text>
      <textarea placeholder="请输入您索要评论的内容"  model:value="{{comment}}" class="contall-input" />
      <button class="mini-btn" type="primary" bindtap="faSend" size="mini">发送</button>
    </view>
    <view class="pingcss">
      <text>评论内容:</text>
      <view class="zhanShi" wx:for="{{commentList}}">
        <view class="contsimg">
          <image src="/image/1.jfif"></image>
        </view>
        <view class="conts">
          <view>
            <text>{{item.username}}</text>
          </view>
          <view>
            <text>{{item.comment}}</text>
          </view>
        </view>
      </view>
    </view>
  </view>
</view>

注意:这里展示的是内容,所以使用 postData.detail,而不是 postData.desc

7.6.3 收藏与取消收藏

实现逻辑:

点击收藏时展示收藏的图标状态,点击取消收藏时,显示取消图标收藏状态,起始状态根据后台请求到的isCollection 的数值进行判断展示

image.png

image.png

7.6.4 发表评论

在detail里面加入

<view class="contall">
    <view class="ctater">
      <text class="contall-text">评论区:</text>
      <textarea placeholder="请输入您索要评论的内容"  model:value="{{comment}}" class="contall-input" />
      <button class="mini-btn" type="primary" bindtap="faSend" size="mini">发送</button>
    </view>
    <view class="pingcss">
      <text>评论内容:</text>
      <view class="zhanShi" wx:for="{{commentList}}">
        <view class="contsimg">
          <image src="/image/1.jfif"></image>
        </view>
        <view class="conts">
          <view>
            <text>{{item.username}}</text>
          </view>
          <view>
            <text>{{item.comment}}</text>
          </view>
        </view>
      </view>
    </view>
  </view>

设置样式

.contall{
    width: 100%;
    height: 100%;
    margin-top:60rpx;
  }
  .ctater{
    margin: 0 30rpx;
  }
  .contall-text{
    display: inline-block ;
    margin-bottom: 30rpx;
  }
  .contall-input{
    border: 1px solid #333333;
  }
  .mini-btn{
    margin-top: 30rpx;
    margin-left: 500rpx;
  }
  .pingcss{
    margin: 30rpx 30rpx;
  }
  .zhanShi{
    display: flex;
    justify-content: flex-start;
  }
  .contsimg{
    margin-top: 30rpx;
  }
  .contsimg image{
    width: 90rpx;
    height: 90rpx;
    margin-right: 20rpx;
  }
  .conts{
    display: flex;
    flex-direction: column;
    justify-content: space-around;
  }

实现逻辑:(在这里我们将头像和昵称设置成固定的)绑定输入框的值,将输入的数据以post方式提交给数据库,并在页面展示出来

data:{
commentList:[],
username:'哲子君',
comment:''
}


async faSend(){
    // console.log(this.data.comment)
    const {data:{data}} = await addConter(this.data.post.postId,this.data.username,this.data.comment)
    this.setData({
      commentList:[data,...this.data.commentList]
    })
  },

由于我们进行了封装处理,所以我们请求的地址和请求的数据都将会在后续封装代码中展示

7.7 封装处理

7.7.1 轮播图改造

为了方便我们的封装,我们将轮播图和文章列表写成两个请求接口,分开处理

image.png

PS:笔记写的比较晚,由于本人比较懒,就不再展示以前的,直接展示封装得了

7.7.2 请求封装

根目录下新建 utils 文件夹,其中新建 http.js

根目录下新建 service 文件夹,其中新建 posts.js

http.js

let baseUrl='https://www.fastmock.site/mock/553417b978737e91d9112446e179feec/shifuji/api/'
export default function request(prams){
  return new Promise((resolve,reject)=>{
    wx.request({
      url: baseUrl+prams.url,
      method: prams.method?prams.method:'GET',
      data:prams.data?prams.data:null,
      success:(res)=>{
       resolve(res)
      },
      fail(err){
        reject(err)
      }
    })
  })
}

posts.js

import request from '../utils/http'
/**
 * 获取文章列表
 */
export function getPosts(){
  return request({
    url:'posts'
  })
}
/**
 * 根据 id 获取文章详情
 */
export function getPostById(postId){
  return request({
    url:`posts/${postId}`
  })
}

/**
 * 更改收藏状态
 */
export function updateCollection(postId,status){
  return  request({
    url: `posts/${postId}/collection`,
    method:'PUT',
    data:{
      status:status
    }
  })
}
/**
 * 添加评论
 */
export function addConter(postId,username,comment){
  return request({
    url:`posts/${postId}/comment`,
    method:'POST',
    data:{
      username: username,
      comment: comment
    }
  })
}
// 轮播图
export function getFocus(){
  return request({
    url:'focus'
  })
}

改造 posts.js

import {getPosts,getFocus} from '../../service/posts'
Page({

  /**
   * 页面的初始数据
   */
  data: {
    postsList:[],
    focusList:[]
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    this.loadPosts()
    this.loadFocus()
  },
  async loadPosts(){
    wx.showLoading({
      title: '加载中...',
      mask:true
    })
    let { data: { data: {  posts }, status } } = await getPosts()
    this.setData({
      // 如果是第一次请求接口,就为 focusList 赋值,否则就不再替换
      postsList: [...this.data.postsList, ...posts]
    })
    wx.stopPullDownRefresh()
    wx.hideLoading()
  },
  /**
   * 获取轮播图
   */
  async loadFocus(){
    const res = await getFocus()
    // console.log(res.data)
    this.setData({
      focusList:res.data.focus
    })
  },
  /**
   * 详情页
   */
  gotoDatil:(event)=>{
    console.log(event)
    const postId=event.currentTarget.dataset.id
    wx.navigateTo({
      url: `/pages/detail/detail?postId=${postId}`,
    })
  },
   // 预览头像
   previewAvatar(obj) {
    let imgArr = []
    this.data.postsList.forEach(item => {
      imgArr.push({
        url: item.imgSrc
      })
    })
    wx.previewMedia({
      sources: imgArr,
      current:obj.detail.index
    })
  },
  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {

  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {
    this.loadPosts()
  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {
    this.loadPosts()
  },
  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})

改造 detail.js

// pages/detail/detail.js
import { getPostById,updateCollection,commention, addConter } from '../../service/posts'
Page({

  /**
   * 页面的初始数据
   */
  data: {
    post:[],
    commentList:[],
    username:'哲子君',
    comment:''
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad:  async function (options) {
    console.log(options)
    let res = await getPostById(options.postId)
    this.setData({
      post:res.data.data
    })
  },
  // 收藏
  async onCollect(){
    let postId = this.data.post.postId
    let res = await updateCollection(postId,!this.data.post.isCollection)
     // 收藏失败后的处理
     if(res.data.status!==0) return wx.showToast({
      title: '收藏失败',
      icon:'error'
    })
    // 收藏成功后的处理
    this.setData({
      'post.isCollection':!this.data.post.isCollection
    })
    // 提示
    wx.showToast({
      title: this.data.post.isCollection?'收藏成功':'取消收藏成功',
    })
  },
  // 添加评论
  async faSend(){
    // console.log(this.data.comment)
    const {data:{data}} = await addConter(this.data.post.postId,this.data.username,this.data.comment)
    this.setData({
      commentList:[data,...this.data.commentList]
    })
  },

  // 获取评论


  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {

  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})

7.8 封装小程序自定义组件

小程序自定义组件

发者可以将页面内的功能模块抽象成自定义组件,以便在不同的页面中重复使用;也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护

上面两句话说明了自定义组件的必要性

小程序自定义组件于 vue 中的自定义组件很相似

自定义组件就是一个小 page,只不过一般都是将一个页面的一部分定义为一个自定义组件

以 posts 页面为例,将文章展示和轮播图部分分别封装成自定义组件

7.8.1 自定义组件

根目录下新建 comonents 目录

目录下新建 组件 post

目录下新建 组件 post-focus

结构

将 posts.wxml 中的列表展示代码拷贝到 post.wxml 中

<!--components/post/post.wxml-->
<view bindtap="onPostTap" data-id="{{item.postId}}" class="post-container">
  <!-- 头像和发表日期 -->
  <view class="post-author-date">
    <image catchtap="previewAvatar" class="post-author" data-index="{{index}}" src="{{item.imgSrc}}">
    </image>
    <text class="post-date">{{item.dateTime}}</text>
  </view>
  <!-- 标题 -->
  <text class="post-title">{{item.title}}</text>
  <image class="post-image" src="{{item.headImgSrc}}"></image>
  <!-- 内容 -->
  <text class="post-content">
    {{item.desc}}
  </text>
  <view class="post-like">
    <!-- <image class="post-like-image" src="/images/icon/collection.png"></image> -->
    <text class="post-like-image iconfont icon-shoucang1"></text>
    <text class="post-like-font">{{item.collection}}</text>
    <!-- <image class="post-like-image" src="/images/icon/like.png"></image> -->
    <text class="post-like-image iconfont icon-shouchang"></text>
    <text class="post-like-font">{{item.reading}}</text>
  </view>
</view>

样式

将 posts.wxss 中显示内容部分的样式拷贝到 post.wxss 中

/* components/post/post.wxss */
@import '/iconfont.wxss';
.post-container {
  display: flex;
  flex-direction: column;
  margin-top: 20rpx;
  margin-bottom: 40rpx;
  background-color: #fff;
  border-bottom: 1px solid #ededed;
  padding-bottom: 10rpx;
}

.post-author-date {
  /* margin-top:10rpx;
  margin-bottom: 20rpx;margin-left: 10rpx; */
  margin: 10rpx 0 20rpx 10rpx;
  display: flex;
  flex-direction: row;
  align-items: center;
}

.post-author {
  width: 60rpx;
  height: 60rpx;
  /* vertical-align: middle; */
}

.post-date {
  margin-left: 20rpx;
  font-size: 26rpx;
  /* vertical-align: middle; */
}

.post-title {
  font-size: 34rpx;
  font-weight: 600;
  margin-bottom: 20rpx;
  margin-left: 20rpx;
  color: #333;
}

.post-image {
  width: 100%;
  height: 340rpx;
  margin-bottom: 30rpx;
}

.post-content {
  color: #666;
  font-size: 28rpx;
  margin-bottom: 20rpx;
  margin-left: 20rpx;
  line-height: 40rpx;
  letter-spacing: 2rpx;
}

.post-like {
  display: flex;
  flex-direction: row;
  align-items: center;
  margin-left: 20rpx;
}

.post-like-image {
  /* height:32rpx;
width:32rpx; */
  font-size: 32rpx;
  color: red;
  margin-right: 16rpx;
}

/* html */
.post-like-font {
  margin-right: 40rpx;
  font-size: 26rpx;
}

将 posts.wxml 中的列表展示代码拷贝到 post-focus 中

<swiper indicator-dots="{{true}}" indicator-color="orange" indicator-active-color="#fff" autoplay="{{true}}" vertical="{{false}}">
    <block wx:for="{{focusList}}" wx:key="id">
      <swiper-item>
      <view class="img-contauber" bindtap="onPostTap" data-id="{{item.postId}}">
        <image src="{{item.imgSrc}}"></image>
        <text>{{item.title}}</text>
      </view>
    </swiper-item>
    </block>
  </swiper>

将 posts.wxss 中轮播图部分的样式拷贝到 post.wxss 中

/* components/post-focus/post-focus.wxss */
swiper {
  height: 460rpx
}
.img-contauber{
  position: relative;
  width: 100%;
  height: 100%;
  background-color: #ccc;
}
swiper image {
  width: 100%;
  height: 400rpx
}
.img-contauber text{
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%,-50%);
  font-size: 40rpx;
  color: #0df1add7;
}

7.8.2 数据传递

小程序自定义组件的数据传递和vue的数据传递非常相似

封装库的post.js

// components/post/post.js
Component({
  /**
   * 组件的属性列表 于 vue 中的 props 作用相同
   */
  properties: {
    item: {
      type: Object
    }
  },

  /**
   * 组件的初始数据
   */
  data: {

  },

  /**
   * 组件的方法列表
   */
  methods: {
    onPostTap() {
      this.triggerEvent('ontap')
    },
    previewAvatar(event) {
      this.triggerEvent('onPreview', {
        index: event.currentTarget.dataset.index
      })
    }
  }
})

封装库的post-focus.js

// components/post-focus/post-focus.js
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    focusList:{
      type:Array
    }
  },

  /**
   * 组件的初始数据
   */
  data: {

  },

  /**
   * 组件的方法列表
   */
  methods: {
    onPostTap(){
      this.triggerEvent('ontap')
    }
  }
})
// components/post-focus/post-focus.js
Component({
  /**
   * 组件的属性列表
   */
  properties: {
    focusList:{
      type:Array
    }
  },

  /**
   * 组件的初始数据
   */
  data: {

  },

  /**
   * 组件的方法列表
   */
  methods: {
    onPostTap(){
      this.triggerEvent('ontap')
    }
  }
})

pages页面的posts文件posts.wxml文件改造

<view>
  <!-- 轮播图 -->
  <post-focus bindontap="gotoDatil" focusList="{{focusList}}"></post-focus>
  <!-- 诗词阅读列表 -->
  <block wx:for="{{postsList}}" data-id="{{item.postId}}" wx:key="id">
    <post index="{{index}}" item="{{item}}" bindonPreview="previewAvatar" bindontap="gotoDatil" item="{{item}}"></post>
  </block>
</view>

posts.json 中引入组件

{
  "usingComponents": {
   "post":"../../components/post/post",
   "post-focus":"../../components/post-focus/post-focus"
  }
}
7.8.3 js代码迁移

当前组件中有两个事件

  • 点击文章列表跳转到文章详情页面
  • 点击用户头像查看大图

其中第一个功能迁移简单,只需将 posts.js 中的 gotoDetail 方法迁移到 post.js 中即可

说明:组件中的自定义方法要放到 methods 中,这与 page 不同

但是第二个功能优点麻烦,因为点击用户头像后,生成一个列表组,通过滑动图片能够查看当前列表中所有用户的头像(这得益于 wx.previewMedia API 的 souces 属性)

这在组件化之前是没有问题的,因为所有用户的头像信息都存在于 posts.js 中的 postsList 中,非常容易获取

但是,组件化之后,在单个组件中只能获取本文章的用户头像

解决方案有两个

  • 点击头像时,将所有头像地址做成组数传入组件中
  • 点击组件时,在 posts 页面中执行查看大图动作

我们这里采用第二种方式

post.wxml

 <block wx:for="{{postsList}}" data-id="{{item.postId}}" wx:key="id">
    <post index="{{index}}" item="{{item}}" bindonPreview="previewAvatar" bindontap="gotoDatil" item="{{item}}"></post>
  </block>
  • 自定义属性 data-index,绑定了父组件传递过来的 index

post.js

previewAvatar(event){      
    this.triggerEvent('onPreview',{index:event.currentTarget.dataset.index})
}

posts.wxml

<post index="{{index}}" item="{{item}}" bindonPreview="previewAvatar"></post>
  • bindonPreview 绑定了子组件触发的事件

posts.js

// 图片预览
  previewAvatar(obj) {
    let imgArr = []
    this.data.postsList.forEach(item => {
      imgArr.push({
        url: item.avatar
      })
    })
    wx.previewMedia({
      sources:imgArr,
      current:obj.detail.index,
      success(res) {
        console.log(res);
      },
      fail(err) {
        console.log(err);
      }
    })
  },
  • 获取子组件传递数据的方式 obj.detail.index,

7.9 搜索功能

此功能我们需要使用vant组件协同开发

在pages 文件中 创建 search 文件夹,用来进行搜索框操作,

当我们在posts页面点击输入框时,跳转到 search 文件jin个性搜索操作

7.9.1 协同 vant组件 开发
7.9.1.1 安装
步骤一 通过 npm 安装
# 通过 npm 安装
npm i @vant/weapp -S --production
7.9.1.2 配置
步骤二 修改 app.json

将 app.json 中的 "style": "v2" 去除,小程序的新版基础组件强行加上了许多样式,难以覆盖,不关闭将造成部分组件样式混乱。

步骤三 修改 project.config.json

开发者工具创建的项目,miniprogramRoot 默认为 miniprogrampackage.json 在其外部,npm 构建无法正常工作。

需要手动在 project.config.json 内添加如下配置,使开发者工具可以正确索引到 npm 依赖的位置。

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

注意: 由于目前新版开发者工具创建的小程序目录文件结构问题,npm构建的文件目录为miniprogram_npm,并且开发工具会默认在当前目录下创建miniprogram_npm的文件名,所以新版本的miniprogramNpmDistDir配置为’./'即可

步骤四 构建 npm 包

打开微信开发者工具,点击 工具 -> 构建 npm,并勾选 使用 npm 模块 选项,构建完成后,即可引入组件。

image.png

7.9.1.3 使用
步骤五 引入组件

我们在 post 和 search 文件中引入搜索框组件

// 通过 npm 安装
// app.json
"usingComponents": {
   "van-search": "@vant/weapp/search/index",
}

posts.wxml 和 search 页面使用

<!-- 搜索框 -->
<van-search value="{{ value }}" placeholder="请输入搜索关键词" use-action-slot />

注意点:

在此注意一个地方,当我们引入时有时候会报一个

image.png

他还会导致我们icon图标不能用

解决方案:

在van-search里面添加:

class="icon van-icon van-icon van-icon-upgrade van-icon-upgrade"

如果还没解决,下面还有在github上找到的回答:
github

出现这个错误是开发者工具的问题,启用真机调试,看看真机上会不会有这个问题。如果真机上可以显示,那么就更新你的开发者工具(下载最新那版),之后再开发者工具里就可以看见图标了

7.9.2 posts页面的输入框操作

给输入框添加一个单机事件,当输入框获取到焦点时,跳转到 search 页面

<!-- 搜索框 -->
  <van-search class="icon van-icon van-icon van-icon-upgrade van-icon-upgrade" placeholder="请输入搜索关键词" use-action-slot bindtap="onChange">
// 搜索跳转
  onChange(){
    wx.navigateTo({
      url: '/pages/search/search'
    })
  }
7.9.3 search页面
7.9.3.1 search页面的回车和取消事件

对search页面的搜索框也添加一个回车事件,并添加一个取消按钮用来返回posts页面

<!-- 搜索框 -->
<van-search class="icon van-icon van-icon van-icon-upgrade van-icon-upgrade" placeholder="请输入搜索关键词"
  show-action bind:search="onSearch" bind:cancel="onCancel" bind:change="onChage" focus />
// 搜索文章
  async onSearch(event) {
    console.log(event.detail)
  },
  // 点击取消按钮时触发
  onCancel() {
    // wx.navigateTo({
    //   url: '/pages/posts/posts',
    // })
    wx.navigateBack({
      delta: 1
    })
  },
7.9.3.2 数据展示
  • 创建一个用来展示历史记录、搜索建议和搜索内容的板块

    • 初始页面只显示历史记录
    • 当输入框长度大于0时,显示搜索建议,当长度等于0时,隐藏搜索建议
    • 点击搜索历史的单个标签,显示搜索内容,并隐藏搜索历史
  • 搜索内容展示我们使用posts自定义组件(在search.json中引入)

    "post":"../../components/post/post",
    
  • 我们的历史记录使用vant的 tag标签来展示,所以我们需要引入一下(在search.json中引入)

     "van-tag": "@vant/weapp/tag/index"
    
  • 历史记录里的逻辑:

    • 将搜索框的数据储存到缓存区,声明一个数组,把内容赋值到这个数组里

    • 点击tag内含的tag组件里的关闭按钮,删除单个记录,自此注意我们要使用catchtap点击事件,反掌事件冒泡

    • 点击删除图标删除全部历史记录

代码如下:

search.wxml

<!--pages/search/search.wxml-->
<!-- 搜索框 -->
<van-search class="icon van-icon van-icon van-icon-upgrade van-icon-upgrade" placeholder="请输入搜索关键词"
  show-action bind:search="onSearch" bind:cancel="onCancel" bind:focus="onFocus" bind:change="onChage" focus />
<!-- 历史记录 -->
<view wx:if="{{historyShow}}" class="history-container">
  <!-- 删除所有 -->
  <view class="history-clear">
    <text>历史记录</text>
    <text class="iconfont icon-shanchu delete" catchtap="allClear"></text>
  </view>
  <!-- 删除单个 -->
  <view class="history-list">
    <van-tag closeable="{{true}}" class="history-tag" plain size="large" catch:close="onClose" type="primary" data-title="{{item}}"
      wx:for="{{ historyList}}" wx:key="index"><text catchtap="handlePostSearch">{{item}}</text></van-tag>
  </view>
</view>
<!-- 搜索建议 -->
<view class="suggest" wx:else>
  <view wx:for="{{suggestList}}" class="suggest-item" wx:key="id"  data-title="{{item.text}}" catchtap="handlePostSearch">{{item.text}}</view>
</view>
<!-- 内容展示 -->
<block wx:for="{{ postList }}" wx:key="id">
  <post bindontap="gotoDatil" item="{{item}}"></post>
</block>

search.wxss

/* pages/search/search.wxss */
.history-container{
  display: flex;
  flex-direction: column;
  padding: 10rpx 20rpx;
}
.history-clear{
  display: flex;
  justify-content: space-between;
  margin-bottom: 20rpx;
  border-bottom: 1px solid #cccccc;
  padding-bottom: 10rpx;
}
.history-list{
  display: flex;
  flex-wrap: wrap;
}
.history-tag{
  margin: 10rpx 10rpx 10rpx 0;
}
.suggest{
  padding: 10rpx 20rpx;
}
.suggest-item{
  margin:10rpx 0;
  padding-bottom: 10rpx;
  border-bottom: 1px solid #eee;
}
.delete{
  color: red;
}

search.js

// pages/search/search.js
import {
  getSeaech,
  getSuggest
} from '../../service/posts'
Page({

  /**
   * 页面的初始数据
   */
  data: {
    value: '',
    postList: [],
    historyList: [],
    historyShow: true,
    suggestList:[]
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

  },
  // 点击取消按钮时触发
  onCancel() {
    // wx.navigateTo({
    //   url: '/pages/posts/posts',
    // })
    wx.navigateBack({
      delta: 1
    })
  },
  // 搜索文章
  async onSearch(event) {
    // console.log(event.detail)
    // console.log(res.data);
    if (event.detail === '') return
    this.handleSearch(event.detail)
    this.setStorage(event.detail)
    this.setData({
      suggestList:[]
    })
  },
  // 当搜索框聚焦时触发
  onFocus() {
    /**
     * const=>let=>var
     */
    // 展示历史记录
    const storage = wx.getStorageSync('historyList')
    this.setData({
      historyList: storage
    })
  },
  // 点击历史记录进行搜索
  handlePostSearch(event) {
    // 获取点击的历史记录
    const keyword = event.currentTarget.dataset.title
    this.handleSearch(keyword)
    this.setData({
      suggestList:[]
    })
  },
  // 搜索内容
  async handleSearch(keyword) {
    wx.showLoading({
      title: '搜索中',
    })
    let res = await getSeaech(keyword)
    this.setData({
      postList: res.data.data
    })
    wx.hideLoading()
    this.setData({
      historyShow: false
    })
  },
  // 存储搜索记录
  setStorage(keyword) {
    // 将搜索内容存储到本地缓存中
    // 获取缓存内容
    let storage = wx.getStorageSync('historyList')
    if (storage === '') {
      storage = []
    }
    // 验证数组中是否存在当前的搜索关键词
    const index = storage.indexOf(keyword)
    if(index >= 0) return
    storage.push(keyword)
    wx.setStorageSync('historyList', storage)
  },
  // 当搜索框内容发生变化时触发
  async onChage(event) {
    this.setData({
      historyShow: event.detail !== '' ? false : true,
    })
    if(event.detail ==='') return
    // 搜索建议
    let res = await getSuggest(event.detail) 
    this.setData({
      suggestList:res.data.data
    })
    
  },
  // 删除历史记录时触发的事件
  onClose(event) {
    const title = event.currentTarget.dataset.title
    const storage = wx.getStorageSync('historyList')
    const index = storage.indexOf(title)
    storage.splice(index,1)
    // 更新界面
    this.setData({
      historyList:storage
    })
    wx.setStorageSync('historyList', storage)
  },
  // 清除所有历史记录
  allClear(){
    wx.removeStorageSync('historyList')
    this.setData({
      historyList:[]
    })
  },
  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {

  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})

公共请求数据的posts.js文件

/**
 * 搜索功能
 */
export function getSeaech(keyword){
  return request({
    url:'search?wd=' + keyword
  })
}

/**
 * 获取搜索建议
 */
export function getSuggest(keyword){
  return request({
    url:'subject?wd=' + keyword
  })
}

任务单4.gif

7.10 完善功能

创建 my 的页面,用于存储用户信息

7.10.1 设置公共底部导航栏

在 app.json 公共组件中写入

"tabBar": {
    "position": "bottom",
    "borderStyle": "white",
    "backgroundColor": "#fff",
    "list": [
      {
        "pagePath":"pages/posts/posts",
        "text":"阅读",
        "iconPath":"/image/icon/阅读.png",
        "selectedIconPath":"/image/icon/阅读红.png"
      },
      {
        "pagePath":"pages/my/my",
        "text":"我的",
        "iconPath":"/image/icon/我的.png",
        "selectedIconPath":"/image/icon/我的红.png"
      }
    ]
  }
7.10.2 修改welcome页面的跳转

原先我们直接使用 navigator 组件写入地址直接跳转,现在我们给他添加单击事件,让他进行跳转

 <view class="button-container">
      <navigator bindtap="totoPosts" class="self-button">进入小程序</navigator>      
  </view>
totoPosts(){
    wx.switchTab({
      url: '/pages/posts/posts',
    })
  },

在这里跳转页面我们使用 switchTab 进行页面跳转,后续我们会补充几个页面跳转的使用

image.png

7.10.3 用户信息

使用 小程序的 open-date 组件,用来获取用户信息,通过点击进行授权,为了能够使用,我们把appid换成了一个可以操作的id

image.png

<!--pages/my/my.wxml-->
<view catchtap="container">
  <view class="avatar-nickname">
    <open-data class="avatar" type="userAvatarUrl"></open-data>
    <open-data type="userNickName"></open-data>
  </view>
  <view class="userinfo">
    <open-data type="userCountry" lang="zh_CN"></open-data>
    <open-data type="userProvince" lang="zh_CN"></open-data>
    <open-data type="userCity" lang="zh_CN"></open-data>
  </view>
  <view class="menu-list">
    <button type="primary" open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">登录</button>
  </view>
</view>

点击登录进行授权

允许:返回用户数据,拒绝:返回undefined

getPhoneNumber(e){
    console.log(e.detail.errMsg)
    console.log(e.detail.iv)
    console.log(e.detail.encryptedData)
  },
open-data

基础库 1.4.0 开始支持,低版本需做兼容处理

用于展示微信开放的数据。

小程序插件中不能使用。

属性类型默认值必填说明最低版本
typestring开放数据类型1.4.0
open-gidstring当 type=“groupName” 时生效, 群id1.4.0
langstringen当 type=“user*” 时生效,以哪种语言展示 userInfo1.4.0
default-textstring数据为空时的默认文案2.8.1
default-avatarstring用户头像为空时的默认图片,支持相对路径和网络图片路径2.8.1
binderroreventhandle群名称或用户信息为空时触发2.8.1

type 的合法值

说明最低版本
groupName拉取群名称1.4.0
userNickName用户昵称1.9.90
userAvatarUrl用户头像1.9.90
userGender用户性别1.9.90
userCity用户所在城市1.9.90
userProvince用户所在省份1.9.90
userCountry用户所在国家1.9.90
userLanguage用户的语言1.9.90

lang 的合法值

说明最低版本
en英文
zh_CN简体中文
zh_TW繁体中文
Bug & Tip
  1. tip:只有当前用户在此群内才能拉取到群名称
  2. tip:关于open-gid的获取请使用 wx.getShareInfo
示例代码

在开发者工具中预览效果

<open-data type="groupName" open-gid="xxxxxx"></open-data>
<open-data type="userAvatarUrl"></open-data>
<open-data type="userGender" lang="zh_CN"></open-data>

7.11 项目补充知识点

7.11.1 路由
7.11.1.1 wx.switchTab(Object object)

本接口从基础库版本 2.3.1 起支持在小程序插件中使用

跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面

在小程序插件中使用时,只能在当前插件的页面中调用

参数

Object object

属性类型默认值必填说明
urlstring需要跳转的 tabBar 页面的路径 (代码包路径)(需在 app.json 的 tabBar 字段定义的页面),路径后不能带参数。
successfunction接口调用成功的回调函数
failfunction接口调用失败的回调函数
completefunction接口调用结束的回调函数(调用成功、失败都会执行)
示例代码
{
  "tabBar": {
    "list": [{
      "pagePath": "index",
      "text": "首页"
    },{
      "pagePath": "other",
      "text": "其他"
    }]
  }
}
wx.switchTab({
  url: '/index'
})
7.11.1.2 wx.reLaunch(Object object)

基础库 1.1.0 开始支持,低版本需做兼容处理

本接口从基础库版本 2.3.1 起支持在小程序插件中使用

关闭所有页面,打开到应用内的某个页面

在小程序插件中使用时,只能在当前插件的页面中调用

参数

Object object

属性类型默认值必填说明
urlstring需要跳转的应用内页面路径 (代码包路径),路径后可以带参数。参数与路径之间使用?分隔,参数键与参数值用=相连,不同参数用&分隔;如 ‘path?key=value&key2=value2’
successfunction接口调用成功的回调函数
failfunction接口调用失败的回调函数
completefunction接口调用结束的回调函数(调用成功、失败都会执行)
示例代码
wx.reLaunch({
  url: 'test?id=1'
})
// test
Page({
  onLoad (option) {
    console.log(option.query)
  }
})
7.11.1.3 wx.redirectTo(Object object)

本接口从基础库版本 2.2.2 起支持在小程序插件中使用

关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面。

在小程序插件中使用时,只能在当前插件的页面中调用

参数

Object object

属性类型默认值必填说明
urlstring需要跳转的应用内非 tabBar 的页面的路径 (代码包路径), 路径后可以带参数。参数与路径之间使用 ? 分隔,参数键与参数值用 = 相连,不同参数用 & 分隔;如 ‘path?key=value&key2=value2’
successfunction接口调用成功的回调函数
failfunction接口调用失败的回调函数
completefunction接口调用结束的回调函数(调用成功、失败都会执行)
示例代码
wx.redirectTo({
  url: 'test?id=1'
})
7.11.1.4 wx.navigateTo(Object object)

本接口从基础库版本 2.2.2 起支持在小程序插件中使用

保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面。使用 wx.navigateBack 可以返回到原页面。小程序中页面栈最多十层。

在小程序插件中使用时,只能在当前插件的页面中调用

一定要注意 navigateTo 需要跳转的应用内非 tabBar 的页面的路径, 路径后可以带参数

参数

Object object

属性类型默认值必填说明
urlstring需要跳转的应用内非 tabBar 的页面的路径 (代码包路径), 路径后可以带参数。参数与路径之间使用 ? 分隔,参数键与参数值用 = 相连,不同参数用 & 分隔;如 ‘path?key=value&key2=value2’
eventsObject页面间通信接口,用于监听被打开页面发送到当前页面的数据。基础库 2.7.3 开始支持。
successfunction接口调用成功的回调函数
failfunction接口调用失败的回调函数
completefunction接口调用结束的回调函数(调用成功、失败都会执行)
object.success 回调函数

参数

Object res

属性类型说明
eventChannelEventChannel和被打开页面进行通信
示例代码
wx.navigateTo({
  url: 'test?id=1',
  events: {
    // 为指定事件添加一个监听器,获取被打开页面传送到当前页面的数据
    acceptDataFromOpenedPage: function(data) {
      console.log(data)
    },
    someEvent: function(data) {
      console.log(data)
    }
    ...
  },
  success: function(res) {
    // 通过eventChannel向被打开页面传送数据
    res.eventChannel.emit('acceptDataFromOpenerPage', { data: 'test' })
  }
})
//test.js
Page({
  onLoad: function(option){
    console.log(option.query)
    const eventChannel = this.getOpenerEventChannel()
    eventChannel.emit('acceptDataFromOpenedPage', {data: 'test'});
    eventChannel.emit('someEvent', {data: 'test'});
    // 监听acceptDataFromOpenerPage事件,获取上一页面通过eventChannel传送到当前页面的数据
    eventChannel.on('acceptDataFromOpenerPage', function(data) {
      console.log(data)
    })
  }
})
7.11.1.5 wx.navigateBack(Object object)

本接口从基础库版本 2.1.0 起支持在小程序插件中使用

关闭当前页面,返回上一页面或多级页面。可通过 getCurrentPages 获取当前的页面栈,决定需要返回几层。

在小程序插件中使用时,只能在当前插件的页面中调用

参数

Object object

属性类型默认值必填说明
deltanumber1返回的页面数,如果 delta 大于现有页面数,则返回到首页。
successfunction接口调用成功的回调函数
failfunction接口调用失败的回调函数
completefunction接口调用结束的回调函数(调用成功、失败都会执行)
示例代码
// 注意:调用 navigateTo 跳转时,调用该方法的页面会被加入堆栈,而 redirectTo 方法则不会。见下方示例代码

// 此处是A页面
wx.navigateTo({
  url: 'B?id=1'
})

// 此处是B页面
wx.navigateTo({
  url: 'C?id=1'
})

// 在C页面内 navigateBack,将返回A页面
wx.navigateBack({
  delta: 2
})
7.11.1.6 总结
跳转详解
  1. navigateTo是将原来的页面保存在页面栈中,在跳入到下一个页面的时候目标页面也进栈,只有在这个情况下点击手机的返回按钮才可以跳转到上一个页面

  2. redirectTo和switchTab都是先清除栈中原来的页面,然后目标页面进栈,使用这两种跳转方式,都不能通过系统的返回键回到上一个页面,而是直接退出小程序;
    redirectTo使用的时候一定要配合tabBar或是页面里面可以再次跳转按钮,否则无法回到上一个页面

  3. switchTab跳转的页面必须是tabBar中声明的页面;
    tabBar中定义的字段不能超过5个页面,小程序的页面栈层次也不能超过5层

  4. navigateBack只能返回到页面栈中的指定页面,一般和navigateTo配合使用

  5. reLaunch关闭所有页面,打开到应用内的某个页面

注意:wx.navigateTo 和 wx.redirectTo 不允许跳转到 tabbar 页面,只能用 wx.switchTab 跳转到 tabbar 页面

7.11.2 小程序事件的绑定
7.11.2.1 WXML
<input bindinput="handleInput" />
7.11.2.2 page
Page({
  // 绑定的事件
  handleInput: function(e) {
    console.log(e);
    console.log("值被改变了");
 }
})
7.11.2.3 特别注意
  1. 绑定事件时不能带参数 不能带括号 以下为错误写法

    <input bindinput="handleInput(100)" />
    
  2. 事件传值 通过标签自定义属性的⽅式 和 value

    <input bindinput="handleInput" data-item="100" />
    
  3. 事件触发时获取数据

     handleInput: function(e) {
        // {item:100}
       console.log(e.currentTarget.dataset)
          
        // 输入框的值
       console.log(e.detail.value);
     }
    
7.11.3 小程序组件
7.11.3.1 视图容器
名称功能说明
cover-image覆盖在原生组件之上的图片视图
cover-view覆盖在原生组件之上的文本视图
match-mediamedia query 匹配检测节点
movable-areamovable-view的可移动区域
movable-view可移动的视图容器,在页面中可以拖拽滑动
page-container页面容器
scroll-view可滚动视图区域
share-element共享元素
swiper滑块视图容器
swiper-item仅可放置在swiper组件中,宽高自动设置为100%
view视图容器
7.11.3.2 基础内容
名称功能说明
icon图标
progress进度条
rich-text富文本
text文本
7.11.3.2.1 rich-text的 nodes 属性

nodes

现支持两种节点,通过type来区分,分别是元素节点和文本节点,默认是元素节点,在富文本区域里显示的HTML节点 元素节点:type = node

属性说明类型必填备注
name标签名string支持部分受信任的 HTML 节点
attrs属性object支持部分受信任的属性,遵循 Pascal 命名法
children子节点列表array结构和 nodes 一致

文本节点:type = text

属性说明类型必填备注
text文本string支持entities
  • nodes 不推荐使⽤ String 类型,性能会有所下降。
  • rich-text 组件内屏蔽所有节点的事件。
  • attrs 属性不⽀持 id ,⽀持 class 。
  • name 属性大小写不敏感。 如果使⽤了不受信任的 HTML 节点,该节点及其所有子节点将会被移除。
  • img 标签仅支持网络图⽚。
7.11.3.3 表单组件
名称功能说明
button按钮
checkbox多选项目
checkbox-group多项选择器,内部由多个checkbox组成
editor富文本编辑器,可以对图片、文字进行编辑
form表单
input输入框
keyboard-accessory设置 input / textarea 聚焦时键盘上方 cover-view / cover-image 工具栏视图
label用来改进表单组件的可用性
picker从底部弹起的滚动选择器
picker-view嵌入页面的滚动选择器
picker-view-column滚动选择器子项
radio单选项目
radio-group单项选择器,内部由多个 radio 组成
slider滑动选择器
switch开关选择器
textarea多行输入框
7.11.3.3.1 button的open-type

open-type的 contact的实现流程

  1. 将小程序 的 appid 由测试号改为 自己的 appid
  2. 登录微信小程序官网,添加 客服 - 微信
  3. 为了方便演示,自己准备两个账号
    1. 普通用户 A
    2. 客服-微信 B
7.11.3.4 导航
名称功能说明
functional-page-navigator仅在插件中有效,用于跳转到插件功能页
navigator页面链接
7.11.3.5 媒体组件
名称功能说明
audio音频
camera系统相机
image图片
live-player实时音视频播放(v2.9.1 起支持同层渲染
live-pusher实时音视频录制(v2.9.1 起支持同层渲染
video视频(v2.4.0 起支持同层渲染
voip-room多人音视频对话
7.11.3.6 地图
名称功能说明
map地图(v2.7.0 起支持同层渲染,相关api wx.createMapContext
7.11.3.7 画布
名称功能说明
canvas画布
7.11.3.8 开放能力
名称功能说明
web-view承载网页的容器
adBanner 广告
ad-custom原生模板 广告
official-account公众号关注组件
open-data用于展示微信开放的数据
7.11.3.9 原生组件说明
名称功能说明
native-component## 原生组件

小程序中的部分组件是由客户端创建的原生组件,这些组件有:

7.11.3.9 原生组件的使用限制

由于原生组件脱离在 WebView 渲染流程外,因此在使用时有以下限制:

  • 原生组件的层级是最高的,所以页面中的其他组件无论设置 z-index 为多少,都无法盖在原生组件上 |
7.11.3.11 无障碍访问
名称功能说明
aria-component## 无障碍访问
为了更好地满足视障人士对于小程序的访问需求,基础库自2.7.1起,支持部分ARIA标签
7.11.3.12 导航栏
名称功能说明
navigation-bar页面导航条配置节点,用于指定导航栏的一些属性
7.11.3.13 页面属性配置节点
名称功能说明
page-meta页面属性配置节点,用于指定页面的一些属性、监听页面事件
7.11.4 自定义组件

类似vue或者react中的自定义组件

小程序允许我们使⽤⾃定义组件的方式来构建页面。

从小程序基础库版本 1.6.3 开始,小程序支持简洁的组件化编程。所有自定义组件相关特性都需要基础库版本 1.6.3或更高。

开发者可以将页面内的功能模块抽象成自定义组件,以便在不同的页面中重复使用;也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护。自定义组件在使用时与基础组件非常相似

7.11.4.1 创建自定义组件

类似于页面,一个自定义组件由 json wxml wxss js 4个文件组成。要编写一个自定义组件,首先需要在 json文件中进行自定义组件声明(将 component 字段设为 true 可将这一组文件设为自定义组件):

{
  "component": true
}

同时,还要在 wxml 文件中编写组件模板,在 wxss 文件中加入组件样式,它们的写法与页面的写法类似。具体细节和注意事项参见 组件模板和样式

代码示例:

<!-- 这是自定义组件的内部WXML结构 -->
<view class="inner">
  {{innerText}}
</view>
<slot></slot>
/* 这里的样式只应用于这个自定义组件 */
.inner {
  color: red;
}

注意:在组件wxss中不应使用ID选择器、属性选择器和标签名选择器。

在自定义组件的 js 文件中,需要使用 Component() 来注册组件,并提供组件的属性定义、内部数据和自定义方法。

组件的属性值和内部数据将被用于组件 wxml 的渲染,其中,属性值是可由组件外部传入的。更多细节参见 Component构造器

代码示例

Component({
  properties: {
    // 这里定义了innerText属性,属性值可以在组件使用时指定
    innerText: {
      type: String,
      value: 'default value',
    }
  },
  data: {
    // 这里是一些组件内部数据
    someData: {}
  },
  methods: {
    // 这里是一个自定义方法
    customMethod: function(){}
  }
})
7.11.4.2 使用自定义组件

使用已注册的自定义组件前,首先要在页面的 json 文件中进行引用声明。此时需要提供每个自定义组件的标签名和对应的自定义组件文件路径:

{
  "usingComponents": {
    "component-tag-name": "path/to/the/custom/component"
  }
}

这样,在页面的 wxml 中就可以像使用基础组件一样使用自定义组件。节点名即自定义组件的标签名,节点属性即传递给组件的属性值。

开发者工具 1.02.1810190 及以上版本支持在 app.json 中声明 usingComponents 字段,在此处声明的自定义组件视为全局自定义组件,在小程序内的页面或自定义组件中可以直接使用而无需再声明。

代码示例:

在开发者工具中预览效果

<view>
  <!-- 以下是对一个自定义组件的引用 -->
  <component-tag-name inner-text="Some text"></component-tag-name>
</view>

自定义组件的 wxml 节点结构在与数据结合之后,将被插入到引用位置内。

7.11.4.3 细节注意事项

一些需要注意的细节:

  • 因为 WXML 节点标签名只能是小写字母、中划线和下划线的组合,所以自定义组件的标签名也只能包含这些字符。
  • 自定义组件也是可以引用自定义组件的,引用方法类似于页面引用自定义组件的方式(使用 usingComponents 字段)。
  • 自定义组件和页面所在项目根目录名不能以“wx-”为前缀,否则会报错。

注意,是否在页面文件中使用 usingComponents 会使得页面的 this 对象的原型稍有差异,包括:

  • 使用 usingComponents 页面的原型与不使用时不一致,即 Object.getPrototypeOf(this) 结果不同。
  • 使用 usingComponents 时会多一些方法,如 selectComponent
  • 出于性能考虑,使用 usingComponents 时, setData 内容不会被直接深复制,即 this.setData({ field: obj })this.data.field === obj 。(深复制会在这个值被组件间传递时发生。)

如果页面比较复杂,新增或删除 usingComponents 定义段时建议重新测试一下。

7.11.4.4 其他属性
7.11.4.5 定义段与示例方法

Component 构造器可用于定义组件,调用 Component 构造器时可以指定组件的属性、数据、方法 等。

定义段类型是否必填描述
propertiesObject Map组件的对外属性,是属性名到属性设置的映射表,下文
dataObject组件的内部数据,和 properties 一同用于组件的模板渲染
observersObject组件数据字段监听器,⽤于监听 properties 和 data 的变 化,参见数据监听器
methodsObject组件的方法,包括事件响应函数和任意的⾃定义⽅法,关于 事件响应函数的使⽤,参见 组件事件
createdFunction组件生命周期函数,在组件实例刚刚被创建时执行,注意此时不能调用 setData ,参见 组件生命周期
attachedFunction组件生命周期函数,在组件实例进入页面节点树时执行,参见 组件生命周期
readyFunction组件生命周期函数,在组件在视图层布局完成后执行,参⻅ 组件⽣命 周期
movedFunction组件生命周期函数,在组件实例被移动到节点树另一个位置时执行,参见 组件生命周期
detachedFunction组件生命周期函数,在组件实例被从页面节点树移除时执行,参见 组件生命周期
7.11.4.6 组件-自定义组件传参
  1. 父组件通过属性的方式给子组件传递参数
  2. 子组件通过事件的方式向父组件传递参数
7.11.4.5.1 过程
  1. 父组件 把数据 {{tabs}} 传递到 子组件的 tabItems 属性中
  2. 父组件 监听 onMyTab 事件
  3. 子组件 触发 bindmytap 中的 mytap 事件
    1. 自定义组件触发事件时,需要使用 triggerEvent 方法,指定 事件名detail 对象
  4. 父 -> 子 动态传值 this.selectComponent("#tabs") ;

父组件代码

image.png

子组件代码

image.png

7.11.4.7 小结:
  1. 标签名 是 中划线的方式
  2. 属性的方式 也是要中划线的方式
  3. 其他情况可以使用驼峰命名
    1. 组件的文件名如 myHeader.js 的等
    2. 组件内的要接收的属性名 如 innerText
  4. 更多
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值