网易云小程序笔记

2021/7/19 20:41:42

本文主要是介绍网易云小程序笔记,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

这是在b站尚硅谷的官方视频
代码可以在他的官网找

搜索关键字: 待做 听说 妙 知识点 坑 思路 思考感悟

这是要做的页面

还有一些就不贴了

在这里插入图片描述
在这里插入图片描述

预备知识

  • 小程序经常用的布局:flex https://www.runoob.com/w3cnote/flex-grammar.html
  • dpr: 设备像素比,物理像素/设备独立像素 = dpr, 一般以 Iphon6 的 dpr 为准 dpr =2
  • 移动端适配有两种 一种viewport 一种rem
    • 为什么做 viewport 适配
      • 手机厂商在生产手机的时候大部分手机默认页面宽度为 980px
      • 手机实际视口宽度都要小于 980px,如: iphone6 为 375px
      • 开发需求: 需要将 980 的页面完全显示在手机屏幕上且没有滚动条
      • <meta name="viewport" content="width=device-width,initial-scale=1.0"
    • 为什么做 rem 适配
      • 在这里插入图片描述
      • https://blog.csdn.net/u013558749/article/details/78835980?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-3&spm=1001.2101.3001.4242
      • 机型太多,不同的机型屏幕大小不一样
      • 需求: 一套设计稿的内容在不同的机型上呈现的效果一致,根据屏幕大小不同的变化,页面中的内容也相应变化
    • 第三方库实现 lib-flexible + px2rem-loader

小程序特点概述

  1. 没有 DOM
  2. 组件化开发: 具备特定功能效果的代码集合
  3. 体积小,单个压缩包体积不能大于 2M,否则无法上线
  4. 小程序的四个重要的文件
    • *.js
    • *.wxml —> view 结构 ----> html
    • *.wxss —> view 样式 -----> css
    • *. json ----> view 数据 -----> json 文件
  5. 小程序适配方案: rpx (responsive pixel 响应式像素单位)
    • 小程序适配单位: rpx
    • 规定任何屏幕下宽度为 750rpx
    • 小程序会根据屏幕的宽度不同自动计算 rpx 值的大小
    • Iphone6 下: 1rpx = 1 物理像素 = 0.5px
    • 在这里插入图片描述

了解一下脚手架是什么

  • 在这里插入图片描述

命名 avatarUrl
上面那个箭头量出来的是px
如果dpr=2 代表着1px=2rpx
小程序中动态的数据都要去data中找
this代表当前页面的实参对象
this.setDate({})
vue里面是数据劫持代理
在这里插入图片描述

// Vue数据劫持代理

// 模拟Vue中data选项
let data = {
  username: 'curry',
  age: 33
}
// 模拟组件的实例
let _this = {
}

// 利用Object.defineProperty()
for(let item in data){
  // console.log(item, data[item]);
  Object.defineProperty(_this, item, {
    // get:用来获取扩展属性值的, 当获取该属性值的时候调用get方法
    get(){
      console.log('get()');
      return data[item]
    },
    // set: 监视扩展属性的, 只要已修改就调用
    set(newValue){
      console.log('set()', newValue);
      // _this.username = newValue; 千万不要在set方中修改修改当前扩展属性的值,会出现死循环
      data[item] = newValue;
    }
  })
}

console.log(_this);
// 通过Object.defineProperty的get方法添加的扩展属性不能直接对象.属性修改
_this.username = 'wade';
console.log(_this.username);
  • App
  1. 全局 app.js 中执行 App()
  2. 生成当前应用的实例对象
  3. getApp()获取全局应用实例
  • Page
  1. 页面.js 中执行 Page()
  2. 生成当前页面的实例
  3. 通过 getCurrentPages 获取页面实例
  • 修改数据
  1. this.setData({message: ‘修改之后的数据’}, callback)
  2. 特点:
    1. 同步修改: this.data 值被同步修改
    2. 异步更新: 异步将 setData 函数用于将数据从逻辑层发送到视图层(异步)
  • 事件分类
  1. 冒泡事件(bind)
    a) 定义:冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递。
    b) 冒泡事件列表:
    https://mp.weixin.qq.com/debug/wxadoc/dev/framework/view/wxml/event.html
  2. 非冒泡事件(catch)
    a) 定义:当一个组件上的事件被触发后,该事件不会向父节点传递。
    b) 非冒泡事件:表单事件和自定义事件通常是非冒泡事件
    https://mp.weixin.qq.com/debug/wxadoc/dev/framework/view/wxml/event.html

事件流的三个阶段
1. 捕获: 从外向内

2. 执行目标阶段
3. 冒泡: 从内向外

  • 路由跳转
    https://developers.weixin.qq.com/miniprogram/dev/api/route/wx.switchTab.html
    其中的success fail complete和promise里的then catch finally很像
    比vue和react中注册路由 使用路由链接简单
  • 生命周期函数(都是钩子函数)
    https://developers.weixin.qq.com/miniprogram/dev/framework/app-service/page-life-cycle.html
    在这里插入图片描述
    上面单词的意思 一个是视图层 一个逻辑层(相当于MVC里面的M层+C层[控制层])
  • 注意debagger的用法
    *知识点 按钮到组件里找 有一个open-type属性 可以获取用户信息 他只发生一次 就是说你点了允许以后再点就跳不出来了
    在这里插入图片描述
    在这里插入图片描述
    关于获取信息(这种情况下一刷新又没了)在这里插入图片描述
  1. 用户未授权(首次登陆)
    1. button open-type=‘getUserInfo’
  2. 用户已经授权(再次登陆)
    1. wx.getUserInfo
      https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserInfo.html
      加上这个就ok了
  onl oad: function (options) {
    var that=this
    wx.getUserInfo({
      success:function(e){
        console.log("success被调用")
        that.setData({
         userInfo:e.userInfo
        })
      },
      fail:function(e){
      }
    })
  },

在这里插入图片描述
这时候按钮应该没了 这个该怎么写呢 ? 需要用到wx:if wx:elif wx:else

<view class="container">
  <button wx:if="{{!userInfo.nickName}}" open-type="getUserInfo" bindgetuserinfo="getUerInfo">点击获取用户信息</button>
  <image wx:else src="{{userInfo.avatarUrl}}" />
  <text>{{userInfo.nickName}}</text>
</view>
onLoad: function (options) {
    var that=this
    wx.getUserInfo({
      success:function(e){
        // console.log(e)
        that.setData({
          userInfo:e.userInfo
        })
      },
      fail:function(e){
        // console.log(e)
      }
    })
  },
 getUerInfo:function(e){
    console.log(e)  //可以看到已经获取到信息了
    this.setData({
      userInfo:e.detail.userInfo
    })
  },
page{
  height: 100%;
}
.container{
  height: 50%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-evenly; 
}
image{
  width: 200rpx;
  height: 200rpx;
  border-radius: 50%;
}

正式开始

1

  • 用阿里巴巴图标库可以保存到项目 然后选fontclass粘贴到网页里 fehelper可以美化css代码 把代码粘贴到微信小程序 因为导航页面的图标其他地方也要引用 所以选择在app.wxss里面引用
  • line-height的用法:https://www.w3school.com.cn/cssref/pr_dim_line-height.asp
  • 知识点 有一个问题在这里插入图片描述
    这里父容器设了宽度但是下面的汉字是好的字母还有单词都不换行 解决办法:https://blog.csdn.net/sheng_li/article/details/75315374
    word-break:break-all;只对英文起作用,以字母作为换行依据,数字也可以换行.

想让他横着滚动怎么办怎么办呢,

| enable-flex | boolean | false | 否 | 启用 flexbox 布局。开启后,当前节点声明了 display: flex 就会成为 flex container,并作用于其孩子节点。 |
单行文本溢出隐藏加省略号三件套:
在这里插入图片描述

一个坑

但是失效了 这是为什么呢 因为这是作用在text上面 而text是内联元素不是块级元素 是靠里面的东西撑开的 没有能力切割 所以要把它变成块级元素

那多行文本溢出怎么做呢

在这里插入图片描述

2 发请求

如何拿到后端的数据呢? 先找到那个文件夹 然后看教程 npm start 以后浏览器输入http://localhost:3000/banner就能看到数据了 然后确定能拿到数据以后就可以在小程序里发送request请求了
https://developers.weixin.qq.com/miniprogram/dev/framework/ability/network.html

前后端交互

  1. 语法: wx.request()
  2. 注意点:
    1. 协议必须是https协议
    2. 一个接口最多配置20个域名
    3. 并发限制上限是10个
  3. 开发过程中设置不校验合法域名: 开发工具 —> 右上角详情 ----> 本地设置 —> 不校验

因为request请求很多都是差不多的 可以封装在utils里面

  • 关于封装的注意事项
 1. 封装功能函数
*   1. 功能点明确
*   2. 函数内部应该保留固定代码(静态的)
*   3. 将动态的数据抽取成形参,由使用者根据自身的情况动态的传入实参
*   4. 一个良好的功能函数应该设置形参的默认值(ES6的形参默认值) //因为数组可以用foreach这些遍历但是如果是空或者是个对象 会报错  所以需要形参默认值
* 2. 封装功能组件
*   1. 功能点明确
*   2. 组件内部保留静态的代码
*   3. 将动态的数据抽取成props参数,由使用者根据自身的情况以标签属性的形式动态传入props数据
*   4. 一个良好的组件应该设置组件的必要性及数据类型
*    //和vue相关
*     props: {
*       msg: {
*         required: true,
*         default: 默认值,
*         type: String
*       }
*     }

utils--require.js里面:
//data默认是一个对象 ; 因为method默认是post 要是传入post的话第三个参数就不用写了 比较简便  如果是get请求就需要写第三个参数
export default(url,data={},method="POST")=>{           //到后面发现我写错了
  wx.request({
    url: url,
    //参数
    method:method,
    data:data,
    success:function(e){
      console.log(e);
    },
    fail:function(e){
      console.log(e);
    }
  })
}
然后index.js里面接收请求
import request from '../../utils/request'
onload:
request('http://localhost:3000/banner',{type:2})

这样就能输出信息了 但是呢 我们需要把数据放到index.js里面 然后setdata 放到data里面 wxml页面才能引用

  • 那在request.js里return一个e 然后index.js里面接收可以吗
    var a=request(‘http://localhost:3000/banner’,{type:2})
    console.log(a)
    这样是不行的
    因为他会先打印a再执行上面那条给a赋值的语句
  • 这时候我们就要用到处理异步的方法:(async和await)
 onl oad: async function (options) {
    var result=await request('http://localhost:3000/banner',{type:2}) 
    console.log(result)
},
同时,await后面通常返回promise的实例
这时候request.js就变成了这样
export default  (url, data={}, method='GET') => {
 return new Promise((resolve, reject) => {
   // 1. new Promise初始化promise实例的状态为pending
   wx.request({
     url: url,
     data,
     method,
     success: (res) => {
       resolve(res.data); // resolve修改promise的状态为成功状态resolved
     },
     fail: (err) => {
       // console.log('请求失败: ', err);
       reject(err); // reject修改promise的状态为失败状态 rejected
     }
   })
 }
}
  • 需要再完善一下每次url的内容写的太多了 http://localhost:3000没必要写 那可以这样写
   wx.request({
       url: 'http://localhost:3000'+url,
       ……
   }
但是呢 require.js是专门发请求的地方,以后要修改的话不应该到发请求的地方修改 我们应该创建一个文件专门改地址(耦合度)
  • config.js:专门配置服务器相关信息
// config.js:
export default{
    host:'http://localhost:3000'  //host:服务器主机的意思
}
//require.js:
import config from './config'
wx.request({
    url:config.host+url
}
  • 关于异步解决方案https://blog.csdn.net/qq_34645412/article/details/81170576

3

  • wx:key https://developers.weixin.qq.com/miniprogram/dev/reference/wxml/list.html
    要注意的是不需要{{item.xxx}} 直接xxx 其中xxx是对象里每一项元素的标识属性(例如id)
  • 如果数据没有请求成功 可以去network的xhr里面看看是没发出去还是其他的原因
  • 组件:在这里插入图片描述
  • scroll-view 设置为flex横向排列后高度仍然以纵向排列高度为准
    可以看到这里的样式都是一样的 可以利用自定义组件来复用 https://developers.weixin.qq.com/miniprogram/dev/reference/api/Component.html
  1. 先把样式和内容提取出来
    在这里插入图片描述
    在这里插入图片描述
  2. index.js里引用
    在这里插入图片描述
  3. index.wxml里输入
    <navHeader></navHeader> 会发现已经出来了
  4. 文字变动态
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 关于文字居中
    在这里插入图片描述
    文字并没有居中 因为是内联的 所以不行 可以给父元素加display:flex 他可以把子元素都变成块级
  • 关于swiper 因为swiper有一个默认高度所以这里高度显示不全
    在这里插入图片描述
  • swiper中next-margin的用法https://developers.weixin.qq.com/miniprogram/dev/component/swiper.html
    单行文本溢出
    在这里插入图片描述
  • 在电脑上是用本地的服务器 要在手机上调试该怎么调呢
    了解一下内网穿透–utools
  • 有的时候获取不到数据是因为服务器卡住了 回车敲一下就ok

4 登录界面(personal&login)

  • 登录界面手指下滑逻辑
let startY = 0; // 手指起始的坐标
let moveY = 0; // 手指移动的坐标
let moveDistance = 0; // 手指移动的距离
Page({
  data: {
    coverTransform: 'translateY(0)',
    coveTransition: '',
  },
  handleTouchStart(event){
    this.setData({
      coveTransition: ''
    })
    // 获取手指起始坐标
    startY = event.touches[0].clientY;
  },
  handleTouchMove(event){
    moveY = event.touches[0].clientY;
    moveDistance = moveY - startY;

    if(moveDistance <= 0){
      return;
    }
    if(moveDistance >= 80){
      moveDistance = 80;
    }
    // 动态更新coverTransform的状态值
    this.setData({
      coverTransform: `translateY(${moveDistance}rpx)`
    })
  },
  handleTouchEnd(){
    // 动态更新coverTransform的状态值
    this.setData({
      coverTransform: `translateY(0rpx)`,
      coveTransition: 'transform 1s linear'
    })
  },
  <view
      class="cover-container"
      bindtouchstart="handleTouchStart"
      bindtouchmove="handleTouchMove"
      bindtouchend="handleTouchEnd"
      style="transform: {{coverTransform}}; transition: {{coveTransition}}"
  >
  • 登录流程
    1. 收集表单项数据
    2. 前端验证
      1. 验证用户信息(账号,密码)是否合法
      2. 前端验证不通过就提示用户,不需要发请求给后端
      3. 前端验证通过了,发请求(携带账号, 密码)给服务器端
    3. 后端验证
      1. 验证用户是否存在
      2. 用户不存在直接返回,告诉前端用户不存在
      3. 用户存在需要验证密码是否正确
      4. 密码不正确返回给前端提示密码不正确
      5. 密码正确返回给前端数据,提示用户登录成功(会携带用户的相关信息)

用户输入数据的时候 实时收集内容 (类似vue 的v-model的双向数据绑定效果)
如果password和phone用同一个事件处理函数
区分bindinput和change的区别 如何向这个事件处理函数传参呢? 了解一下里面的id
那如何分辨是哪一个传过来的呢

事件委托

一个案例:要给ul下面所有的li都绑定事件 可以用循环遍历(效率差)而且如果以后还想再添加li进去 后来的li没有事件
这时候事件委托就派上用场了 就是说在ul上绑定事件,只要绑定一次就好 而且后来添加的元素也能享用

  1. 什么是事件委托
    1. 将子元素的事件委托(绑定)给父元素
  2. 事件委托的好处
    1. 减少绑定的次数
    2. 后期新添加的元素也可以享用之前委托的事件
  3. 事件委托的原理
    1. 冒泡
  4. 触发事件的是谁
    1. 子元素
  5. 如何找到触发事件的对象
    1. event.target
  6. currentTarget VS target
    1. currentTarget要求绑定事件的元素一定是触发事件的元素
    2. target绑定事件的元素不一定是触发事件的元素
  • 接上次的内容 如何区分到底是哪个传来的数据呢 --用id
    这时候用event.currentTarget.id就可以区分到底是哪个传过来的
    把密码的id设为了passworld 电话的id设为phone
    handleInput:function(event){
    console.log(event.detail.value,event.currentTarget.id);
    },
    结果:
    login.js:19 f phone
    login.js:19 fd phone
    login.js:19 f password
    login.js:19 fd password
    login.js:19 fds password
    更新到data
  • 在这里插入图片描述
    用data-传参和用id传参有什么区别吗 ? data-可以穿多个参数
  • 登录的回调
    前端验证
    • 手机号验证:
      1. 内容为空
      2. 手机号格式不正确
      3. 手机号格式正确,验证通过
      let{phone,password}=this.data 这句话什么意思呢 phone=this.data.phone password=this.data.password
      判断手机号是否合法 第一位是1 第二位 3-9
       //按钮点击
 login:function(){
   let{phone,password}=this.data
   // 验证手机号是否为空
   if(!phone){
     wx.showToast({
       title: '请输入手机号',
       icon:'none'
     })
     return;
     // 老师说因为保险起见 异步加个return为什么呢?
     //为啥呢 后面没有必要走了
   }
   // 验证手机号是否合法
   // 第一位1 第二位3-9 后9位随意
   let phoneReg=/^1(3|4|5|6|7|8|9)\d{9}/
   if(!phoneReg.test(phone)){
     wx.showToast({
       title: '输入正确的手机号',
       icon:'none'
     })
     return;
   }
   if(!password){
     wx.showToast({
       title: '请输入密码',
       icon:'none'
     })
     return;
   }
 },
  • 后端验证 先验证成功还是先验证失败呢 --先验证成功200 然后手机号错误是400 密码错误是502
  • 有个问题 老师写的这个页面如果登录上了后面再把密码输错还是能登上 去??有时候账号密码是对的还是显示错??
  //验证手机号密码
  login:async function(){
    let{phone,password}=this.data //相当于let phone=this.data.phone ,     password同理
    //判断phone是否为空
    if(!phone){
      wx.showToast({
        title: '请输入手机号码',
        icon:'none'
      })
      return; //这个都没通过后面也没必要执行了
    }
    //接下来判断手机号合不合法  第一位1 第二位3-9 后9位任意
    let phoneReg=/^1(3|4|5|6|7|8|9)\d{9}/
    if(!phoneReg.test(phone)){
      wx.showToast({
        title: '请输入正确的手机号',
        icon:'none'
      })
      return;
    }
    //接下来判断密码  这里只判断了是否为空
    if(!password){
      wx.showToast({
        title: '请输入密码',
        icon:'none'
      })
      return;
    }
    result=''
    let result= await request('/login/cellphone',{phone,password})
    console.log(result);
    if(result.code===200){
      wx.showToast({
        title: '登录成功',
      })}else{
      wx.showToast({
        title: '输入错误 请重新输入',
        icon:'none'
      })
    }
  },
  • 登录成功了以后就要回到personal页面 不能用navigateTo 可以用switchTab
    成功了personal页面要显示用户信息
  • 这是两个页面 那么怎么才能把数据拿到然后放到personal里面呢?

本地存储

https://developers.weixin.qq.com/miniprogram/dev/api/storage/wx.setStorageSync.html

json.stringify

在这里插入图片描述
在这里插入图片描述

一个坑

  • 可以看到第一张图已经把数据传到缓存了 但是personal里面还是没取到 再刷新一次就有了
  • 为什么呢 因为在personal里面的onload只执行一次
  • 在这里插入图片描述
onLoad: function (options) {
    let userinfo=wx.getStorageSync('userinfo')
    if(userinfo){
      this.setData({userinfo:JSON.parse(userinfo)})   //注意这里json.parse的应用
    }
  },

老师说跳转的时候可以用wx.relaunch(他可以销毁所有页面 这样回到personal里的onload就可以用了)
我试了一下 也可以把上面的代码放到onshow他是监听页面显示的(性能不好)

总结缓存主要知识点:

1.  语法: wx.setStorage() || wx.setStorageSync() || .....
2.  注意点: 
    1. 建议存储的数据为json数据
    2. 单个 key 允许存储的最大数据长度为 1MB,所有数据存储上限为 10MB
    3. 属于永久存储,同H5的localStorage一样

5播放记录

我们的播放记录里面没有唯一标识id 那该怎么办呢 --用map

  async getUserRecentPlay(userId){
  let recentPlayListData=await request('/user/record',{uid:userId ,type:0})
  let index=0;
  let recentPlayList=recentPlayListData.weekData.splice(0,10).map(item=>{
    item.id=index++
    return item                   //千万不要忘记return
  })
  this.setData({
    recentPlayList
  })
},

无论是用户登录了没数据还是没有登录recentPlayList都是空
然后要在页面上如果没有播放记录就显示暂无播放记录 如果有的话就显示图片用到wxif wxelse 判断什么呢 判断recentPlayList.length 不能判断recentPlayList因为这是判断他存不存在的 无论有没有数据它都存在

6 video

  • 知识点
    /flex-grow: 可拉伸 flex-shrink: 可压缩 flex-basis: 当前元素的宽度/
    /flex默认值: flex-grow: 0, flex-shrink: 1, flex-basis: auto/
    /flex:1 flex-grow: 1, flex-shrink: 1, flex-basis: 0%/
    /flex:auto flex-grow: 1, flex-shrink: 1, flex-basis: auto/
    /flex: 1会导致父元素宽度自动为100%/

一个坑

通过id传参 如果传的是数字 他会转化成字符串 我们可以用>>>0、 *1、 == 或者直接用data-传参

  onl oad: function (options) {
        this.getVideoGroup()
        this.getVideoList(this.data.navId)
},

像这样的 第一个函数执行了setdata 把navId放到了data里面 第二个函数传入了data里面的navid
会出问题 为什么呢 第一个函数没执行完就执行第二个了 第二个就取不到navId 那该怎么办呢 --把第二个函数放到第一个函数里面

关于cookie

是这样的 要拿到视频数据需要cookies
以前封装的函数只是拿到了e.data data和cookies是并列的

在这里插入图片描述

这时候也要拿到cookies 要去request.js里面改一下代码
因为要在视频页用 所以可以放缓存里面 不是这个原因

获取视频页

这是要取出来的代码

  async getVideoList(navId){
    let videoListData= await request('/video/group',{id:navId})
    console.log(videoListData);
  },
但是他要cookies  我们就要在请求的时候拿到cookies  这时候要修改request.js里面的内容

现在变成这样了
export default(url,data={},method="POST")=>{
 return new Promise((resolve,reject)=>{
  wx.request({
    url:config.host+url,
    method:method,
    data:data,
    //这里的header是给视频页获取准备的 因为它需要cookies才能获取到数据
    header:{                       //这个header的作用是携带数据

      cookie:wx.getStorageSync('cookies')?(wx.getStorageSync('cookies').find(item=>item.indexOf('MUSIC_U=6')!==-1)):' ' 
     // 本来是这样的 cookie:wx.getStorageSync('cookies').find(item=>item.index('MUSIC_U=6')):'' 
     //这里为什么要加!==-1呢 因为等于负一他也算对的  -1转换成布尔值也是对的
      //注意这里为什么要加三元运算符呢 因为一开始是没有cookies缓存的 这时候在用find可能就会出错 所以加了个三元运算符  让cookies等于空

    success:function(e){
        //let result= await request('/login/cellphone',{phone,password,isLogin:true})  这是login里写的
      if(data.isLogin){        //如果是在login页面调用的
        wx.setStorage({      //把他放缓存里  可以给header里面的cookie用
          data: e.cookies,
          key: 'cookies'
        })
      }
      resolve(e.data)
    },

渲染

注意里面的元素没有唯一的id需要用map加工一下
在这里插入图片描述

用户体验

showloading
以及点击把当前的页面变白色(清空) 把videoList:[]
  • 点击一个导航元素该导航元素在第一个
changeNav(event){
    let navId = event.currentTarget.id; // 通过id向event传参的时候如果传的是number会自动转换成string
    // let navId = event.currentTarget.dataset.id;
    this.setData({
      navId: navId>>>0,
      videoList: []
    })
    // 显示正在加载
    wx.showLoading({
      title: '正在加载'
    })
    // 动态获取当前导航对应的视频数据
    this.getVideoList(this.data.navId);
  },

因为scroll-into-view不能用数字开头 所以前面加了个scroll字符串(妙啊) 然后navItem的id也要跟着换了

  <!-- 导航区域 -->
  <scroll-view scroll-x enable-flex class="navScroll" scroll-into-view="{{'scroll'+navId}}" >
    <view class="navItem " wx:for="{{videoGroupList}}" wx:key="id" data-id="{{item.id}}" id="{{'scroll'+item.id}}" >
      <view class="navContent {{item.id===navId?'active':''}}" bindtap="changeNav" id="{{item.id}}">{{item.name}}</view>
    </view>
  </scroll-view>

过渡的太生硬了 有个scroll-with-animation

解决多个视频同时播放的问题(单例模式)(※※)

知识点

  • 单例模式:
    *
    1. 需要创建多个对象的场景下,通过一个变量接收,始终保持只有一个对象,
    *
    2. 节省内存空间
  • 1 为什么永远播放不了呢 因为是把当前的视频停掉了
  handlePlay(event){
    console.log(event);
    let vid=event.currentTarget.id
    let videoContext=wx.createVideoContext(vid)
    videoContext.stop()
  },
  • 我们需要找到上一个播放的视频
    重点看这两句话
    第一个一开始没有保存东西 第二个保存了
    然后第二次点击其他的 第一个就会保存上次的视频
 handlePlay(event){
    // console.log(event);
    // let vid=event.currentTarget.id
    this.videoContext
    this.videoContext=wx.createVideoContext(vid)
    // videoContext.stop()
  },
 handlePlay(event){
    // console.log(event);
    // let vid=event.currentTarget.id
    this.videoContext.stop()                 //会报错的  因为一开始他是没有值的  ,play()当然不行
    this.videoContext=wx.createVideoContext(vid)
    // videoContext.stop()
  },
 handlePlay(event){
    // console.log(event);
    let vid=event.currentTarget.id
    this.videoContext&&this.videoContext.stop()  //注意这种写法 当前面的为true走后面的
    this.videoContext=wx.createVideoContext(vid)
    // videoContext.stop() 
  },

我们要确保当前的视频跟之前的不是同一个视频

 handlePlay(event){
    // console.log(event);
    let vid=event.currentTarget.id
    this.vid!==vid&&this.videoContext&&this.videoContext.stop()  //和上面有点像
    this.videoContext=wx.createVideoContext(vid)
    // videoContext.stop() 
  },

多个视频在页面转圈圈官方bug

性能优化:(使用image图片代替video)https://developers.weixin.qq.com/community/develop/doc/000e4ef22583d8961919efb6b56009

用wxif和wx else决定显示哪个 也可以盖在上面
在这里插入图片描述

图片和视频共用一个class 和函数
一点击图片会把videoId更新到data中 然后判断videoId与视频哪一项匹配 哪一项匹配就显示video而不是图片

  this.setData({
      videoId:vid
    })
  <view class="videoItem" wx:for="{{videoList}}"wx:key="id">
      <video src="{{item.data.urlInfo.url}}"
      bindplay="handlePlay"
      id="{{item.data.vid}}"
      poster="{{item.data.coverUrl}}"
      class="common"
      wx:if="{{videoId===item.data.vid}}"
      ></video>
      <!-- //性能优化 -->
      <image wx:else class="common" bindtap="handlePlay" id="{{item.data.vid}}" src="{{item.data.coverUrl}}"></image>

//这样的话我们也没有必要写那样一长串了 因为不存在多视频同时播放的情况
但是虽然切换了 我们的视频还是不能自动播放

handlePlay(event){
    let vid=event.currentTarget.id
    // this.vid!==vid&&this.videoContext&&this.videoContext.stop()  //注意这种写法 铛前面的为true走后面的x
    // this.vid=vid
    this.setData({
      videoId:vid
    })
    this.videoContext=wx.createVideoContext(vid)
    this.videoContext.play()
  },

有个aspectfit可以解决视频左右黑条条
就是什么呢 先image和video设同一个函数 然后点击image的时候呢 就把vid放到data里 然后video判断vid和他的item.videoList.vid香不相同 如果相同 就显示相应的视频

头部固定

如何计算出下面tabbar的高度呢 ?
有一个calc 可以计算出动态css的值
算出来我们顶部的rpx值
在视频列表height:calc(100vh-152rpx)
记得运算符左右加空格

再次点击播放回到原来进度

我们要知道 视频的id和视频播放到哪儿了
data里创建一个数组 videoUpdateTime:[]
每一项数据:
let videoTimeObj={vid:event.currentTarget.id,currentTime:event.detail.currentTime}
let{videoUpdateTime}=this.data //取出data里面的videoUpdateTime
然后把videoTimeObj给push进去 然后再setdata videoUpdateTime

但是这样是不行的 他会把每一秒的记录都存进去 会有多个重复的vid
我们要判断数组中之前有没有这个对象
如果有 --修改播放时间
没有 – 在数组中添加当前视频的播放对象

let videoItem=videoUpdateTime.find(item=>item.id===videoTimeObj.vid)
如果不相等为undefined

    if(videoItem){
           //疑惑 难道videoItem里面的 东西改变  videoUpdateTime也会改变吗
      videoItem.currentTime=event.detail.currentTime
    }else{
      videoUpdateTime.push(videoTimeObj)
    }

然后更新

// 监听视频播放进度
  handleTimeUpdata:function(event){
    console.log(event);
    let videoTimeObj={vid:event.currentTarget.id,currentTime:event.detail.currentTime}
    // this.data.videoUpdateTime.push(videoTimeObj)
    let{videoUpdateTime}=this.data   //取出data里面的videoUpdateTime
    let videoItem=videoUpdateTime.find(item=>item.vid===videoTimeObj.vid)
    if(videoItem){
      videoItem.currentTime=event.detail.currentTime
    }else{
      videoUpdateTime.push(videoTimeObj)
    }
    this.setData({
      videoUpdateTime
    })
  },

目的是什么呢 就是点击的时候能够更新到当前的进度
点击的事件添加:
在这里插入图片描述

这样不断的向videoUpdateTime里堆数据也不好 我们要视频看结束了 就把他删除掉

handleEnded(event){
    let{videoUpdateTime}=this.data
    //找数组下标:findindex
    videoUpdateTime.splice( videoUpdateTime.findIndex(item=>item.item.vid===event.currentTarget.id) ,1)
    this.setData({
      videoUpdateTime
    })
  }

下拉刷新 上拉加载

自定义下拉刷新被触发:bindrefresherrefresh 每次调用都获取一次数据
但是他不能自己关掉 需要用 refresher-triggered这个东西 他是个布尔类型的 我们要先将里面的值变成false 每次获取数据它都变成false
https://developers.weixin.qq.com/miniprogram/dev/component/scroll-view.html

上拉加载 :后端 前端
后端:发一次请求给10条 比如 你发一次请求给你十条 再发一次再给十条 由后端控制给你几次 要携带指定数据
前端的话就是后端一次性给100条数据 前端拿到以后一次显示十条 上拉触底以后再截10-19
但是呢 我们可以造一个假的

三点运算符是什么呢 他有拆包的功能 就是说可以把数组变成对象

  handleToLower(){
    console.log('scroll-view 上拉触底');
    // 数据分页: 1. 后端分页, 2. 前端分页
    console.log('发送请求 || 在前端截取最新的数据 追加到视频列表的后方');
    console.log('网易云音乐暂时没有提供分页的api');
    // 模拟数据
    let newVideoList = [
      ……
    ];
    let videoList = this.data.videoList;
    // 将视频最新的数据更新原有视频列表数据中
    videoList.push(...newVideoList);
    this.setData({
      videoList
    })
  },

分享

https://developers.weixin.qq.com/miniprogram/dev/reference/api/Page.html#onShareAppMessage-Object-object

onShareAppMessage:function({from}){
    console.log(from);
    if(from=="button"){
      return{
        title:"来自btn",
        page:"/pages/video/video"
      }
    }else{
      return{
        title:"来自menu",
        page:"/pages/video/video"
      }
    }

7 recommendSong

待做:排行给123添加样式
注意:定位的代码最好写在样式块的最上面
想把下面的数字放到图片的中间怎么办呢
在这里插入图片描述

用定位的方式
但是数字那个盒子设成top50% height50%了 会发现只是左上角顶点在中间 那该怎么办呢 应该用margintop :-高度的一半 marginleft -宽度的一半 这样把他往左上角拉

本来是播放全部 更多 这样布局的 如果把更多变成float left 就会变成更多 播放全部

听说:图片太多可以用七牛云免费存储

一个坑 加了圆角 怎么能有这个效果呢

在这里插入图片描述
这样是不行的

在这里插入图片描述

我们应该把他设为relative 然后往上提20rpx 还要把背景颜色设为白色
注意当一个盒子设为flex以后 他下面的子元素都会变成block(块元素)

如果用display flex 然后 justify-content: space-evenly; 以后如果发现元素定位不对 可以用 margin 来进行微调 就像这样
在这里插入图片描述

8播放页面

一个很妙的地方在这里插入图片描述

/* 磁盘  */
.discContainer{
  width: 598rpx;
  height: 598rpx;
  position: relative;
  top: -130rpx;
}
.disc {
  width: 598rpx;
  height: 598rpx;
  border: 1rpx solid red;

}
/* 唱盘图片 */
.musicImg{
  width: 360rpx;
  height: 360rpx;
  border-radius: 50%;
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  margin: auto;
}

注意这里top0 right0 bottom0 left0 和margin auto的妙处

关于摇杆和唱盘动画注意事项

直接translate:rotate(-20deg)不行 因为他是在中间旋转的 我们要以左上角为顶点
要用到

  transform-origin: top left;
  transform: rotate(-20deg);

我们先初始化一个isPlay来表示是否播放 从而控制摇杆的样式 needleRotated是在播放时摇杆的样式

  <image class="needle {{isPlay?'needleRotated':''}}" src="/static/images/song/needle.png"></image>

一个很妙的地方 {{isPlay?'needleRotated':''}}=》isPlay&&'needleRotated'

知识点:
@keyframes: 设置动画帧

  1. from to
    - 使用于简单的动画,只有起始帧和结束帧
    - 北京 - 上海 直达
  2. 百分比
    - 多用于复杂的动画,动画不止两帧
    - 北京 - 上海 ---> 北京 -- 天津 --- 深圳 --- 上海
    - 0% - 100%, 可以任意拆分

这两个都是根据判断isPlay为真还是假做的动画 摇杆一开始就播放 但是图片旋转需要等一秒才行(增加真实性)
在这里插入图片描述

待做 isPlay为false的时候 图片停了 在继续播放的时候他不能等两秒再旋转 该怎么解决呢

底部按钮

播放暂停两个思路:
思路1

    <text wx:if="{{isPlay==true}}" class="iconfont icon-bofang1"></text>
    <text wx:else class="iconfont icon-bofang"></text>

思路2

    <text  class="iconfont {{isPlay?'icon-bofang1':'icon-bofang'}} big" ></text>

一个justify-content:space-evenly的坑:因为那个暂停的按钮太小了 我给他放大了一下然后就出现了这种情况 :其他的按钮也动了 其实他本来全体都会往下动的 因为我设了align-item:center 然后给父盒子加了个高度就好了(用height和line-height也可以 因为不会随着其他的动而动)
在这里插入图片描述
解决办法

  font-size: 60rpx;
  /* color: #fff; */
  width: 20%;
  text-align: center;

群里听说:
微擎
自己做点小程序搭建好,放那边。有相同需求的直接小改动给他- -
或者直接买微擎商店的小程序

点击播放暂停 点击暂停播放:

  // 点击播放或暂停   也可以用if else
  handleMusicPlay(){
    let isPlay=!this.data.isPlay
    this.setData({
      isPlay
    })
  },

如果用if else就长了

  handleMusicPlay(){
    if(this.data.isPlay==true){
      this.setData({
        isPlay:false
      })
    }
    else{
      this.setData({
        isPlay:true
      })
    }
  },

9 继续recommendSong

现在要携带数据跳转到播放页面
要携带什么数据呢 --携带item
<view class="scrollItem" wx:for="{{recommendList}}" wx:key="id" bindtap="toSongDetail" data-song="{{item}}">
然后js接收song

  toSongDetail(event){
    let song=event.currentTarget.dataset.song
    //路由跳转传参 支持query参数
    wx.navigateTo({
      url: '/pages/songDetail/songDetail?song='+song,
    })
  },

然后该怎么做呢
然后音乐播放页面onload接收
在这里插入图片描述
然后发现不行啊
变成了这个{song: “[object Object]”}
一个坑 为什么呢 url地址里面不能有js对象 如果有 他会自动帮你调用tostring去转换成字符串 转换之前是个object他就会变成上面那样有个中括号 然后里面有object
那如何解决呢 在传参的时候 要先给他转换成字符串
url: '/pages/songDetail/songDetail?song='+JSON.stringify(song) ,
如果传的是字符串 他就不会帮忙转换了
然后onload接收的时候再转换过来 (json.parse)
console.log(JSON.parse(options.song)); 但是报错了Unexpected end of JSON input
这意味着什么呢 意味着json.parse里面传参传错了 为什么错了呢 因为里面要放的是个json的对象 但是事实上并不是 我们打印出来options.song是什么东西 发现他只打印到picUrl 后面的东西都没了 都被截掉了
这又是为什么呢因为原生小程序中路由传参 对参数长度有限制,如果参数长度过长会自动截取掉
那怎么办啊 我们要传入一个标识告诉页面传的是哪个音乐 有一个接口: /song/detail?ids=347230
我们这时候要传id进去(而不是song对象作为参数传递 长度过长了)
这时候我恍然大悟 为什么军政的那个项目把数据都放到公共的里面去了

思路1:
我是想着这样写的 onl oad接收到参数以后就放到data里面 然后通过函数来发请求然后调用data里面的id
在这里插入图片描述
思路2:
老师写的 是在函数里面传参 就不用把id放到data里了 简便了很多 但是如果后面要用到id的话还是需要放到公共区data里面的
我发现老师经常会写什么什么Data(发请求后接收的数据)

  • 听说
  • 后盾人 向大叔是谁 据说要先学js然后es6 然后vue react等了 群友推荐网站
    • https://www.shamqu.cn/tag/%e5%b0%8f%e7%a8%8b%e5%ba%8f
    • 在这里插入图片描述
    • 组件化是什么

知识点:窗口标题

//动态修改窗口标题
wx.setNavigationBarTitle({
title: this.data.song.name,
})
在这里插入图片描述

10 继续播放页面(背景音乐播放暂停)

    if(isPlay==true){   //音乐播放
      //获取链接
      let link=await request('/song/url',{id:musicId})
      console.log("发送了一次请求");
      link=link.data[0].url
      let music=wx.getBackgroundAudioManager()
      music.src=link
      music.title= this.data.song.name
    }else{
      music.pause()      //这里是不行的 因为let让music只能在isplay=true的时候使用
    }

那该怎么办呢 应该把 let music=wx.getBackgroundAudioManager()提到if-else上面

我认为老师上面写的不太好(可能)因为每次播放它都会发一次请求(后面第70集老师讲到了怎么优化)
待做:只用发一次请求
老师原来的代码:

  data: {
    isPlay: false, // 音乐是否播放
    song: {}, // 歌曲详情对象
    musicId: '', // 音乐id
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onl oad: function (options) {
    // options: 用于接收路由跳转的query参数
    // 原生小程序中路由传参,对参数的长度有限制,如果参数长度过长会自动截取掉
    // console.log(JSON.parse(options.songPackage));
    let musicId = options.musicId;
    this.setData({
      musicId
    })
    // 获取音乐详情
    this.getMusicInfo(musicId);
    this.musicControl(this.data.isPlay, musicId)
    },
  // 获取音乐详情的功能函数
  async getMusicInfo(musicId){
    let songData = await request('/song/detail', {ids: musicId});
    // songData.songs[0].dt 单位ms
    this.setData({
      song: songData.songs[0],
    }) 
    // 动态修改窗口标题
    wx.setNavigationBarTitle({
      title: this.data.song.name
    })
  },
  // 点击播放/暂停的回调
  handleMusicPlay(){
    let isPlay = !this.data.isPlay;
    this.setData({
      isPlay
    })
    let {musicId} = this.data;
    this.musicControl(isPlay, musicId);
  },
  // 控制音乐播放/暂停的功能函数
  async musicControl(isPlay, musicId){
     var backgroundAudioManager=wx.getBackgroundAudioManager()
    if(isPlay){ // 音乐播放
        // 获取音乐播放链接
        let musicLinkData = await request('/song/url', {id: musicId});
        let musicLink = musicLinkData.data[0].url;
        this.setData({
          musicLink
        })
      backgroundAudioManager.src = musicLink;
      backgroundAudioManager.title = this.data.song.name;
    }else { 
      backgroundAudioManager.pause();
    }
  },
 data: {
    isPlay:false,  //标识是否在播放
    song:{          //渲染页面用的
      title:'',
      singer:'',
      coverImg:''
    }  ,
    musicId:'',         //音乐的id
    musicPlay:{      //播放音乐用的
      musicTitle:'',
      musicUrl:''
    }
  },
  music:null,
  /**
   * 生命周期函数--监听页面加载
   */
  onl oad: function (options) {
    //接收路由跳转的query参数
  // console.log(options.musicId);
    let musicId=options.musicId
    this.setData({musicId})
    this.getMusicInfo(musicId)       //
    this.getMusicDetail(musicId)    //这两个是用来获取song和musicPlay里面的东西的
  },
  //获取音乐详情功能函数
  async getMusicInfo(id){
    let songData=await request('/song/detail',{ids:id})
    console.log(songData);
    this.setData({
      'song.singer':songData.songs[0].ar[0].name,
      'song.title':songData.songs[0].al.name,
      'song.coverImg':songData.songs[0].al.picUrl,
      'musicPlay.musicTitle':songData.songs[0].al.name
    })
    //动态修改窗口标题
    wx.setNavigationBarTitle({
      title: this.data.song.name,
    })
  },
  // 点击播放或暂停
  handleMusicPlay(){
    if(this.data.isPlay==true){
      this.setData({
        isPlay:false
      })
      this.musicPlay()
    }
    else{
      this.setData({
        isPlay:true
      })
      this.musicPlay()
    }
  },

  //获取音乐src
  async getMusicDetail(musicId){
      //获取链接
      let link=await request('/song/url',{id:musicId})
      console.log("发送了一次请求");
      this.setData({
        'musicPlay.musicUrl':link.data[0].url
      })
  },
  // 音乐播放功能
musicPlay:function(){
  this.music=wx.getBackgroundAudioManager(),
  this.music.src=this.data.musicPlay.musicUrl
  this.music.title=this.data.musicPlay.musicTitle
  if(this.data.isPlay){
    console.log(this.data.isPlay);
    this.music.play()
  }else{
    this.music.pause()
  }

    //  this.music.play()
    //  console.log(music);
},

补充:老师后来改的

在这里插入图片描述


一个坑:背景音乐有一个是系统控制 ![在这里插入图片描述](https://www.www.zyiz.net/i/ll/?i=20210117001740136.png) 在系统控制的时候 我们的isPlay不能更新他的状态 导致页面显示在播放 但是其实并没有 如何解决呢 /\* \* 问题: 如果用户操作系统的控制音乐播放/暂停的按钮,页面不知道,导致页面显示是否播放的状态和真实的音乐播放状态不一致 \* 解决方案: \* 1. 通过控制音频的实例 backgroundAudioManager(我的是music) 去监视音乐播放/暂停 \* \* \*/

在这里插入图片描述

我们需要事先监视播放暂停

  this.music=wx.getBackgroundAudioManager(),
  this.music.onPlay(()=>{
    console.log('播放');
  }),
  this.music.onPause(()=>{
    console.log('暂停');
  })
musicPlay:function(){
  this.music=wx.getBackgroundAudioManager(),
  this.music.onPlay(()=>{
    // console.log('播放');
    this.setData({
      isPlay:true
    })
  }),
  this.music.onPause(()=>{
    // console.log('暂停');})
    this.setData({
      isPlay:false
    })
  })
  this.music.src=this.data.musicPlay.musicUrl
  this.music.title=this.data.musicPlay.musicTitle
  if(this.data.isPlay){
    console.log(this.data.isPlay);
    this.music.play()
  }else{
    this.music.pause()
  }

  // this.music.onpause()
    //  this.music.play()
    //  console.log(music);
}

还有一个点叉叉以后应该暂停的在这里插入图片描述

就要用到这个

  this.music.onStop(()=>{
    this.setData({
      isPlay:false
    })
  })

注意 这时候我们发现代码重合度太高了

  // 音乐播放功能
musicPlay:function(){
  this.music=wx.getBackgroundAudioManager(),
  //监听播放暂停停止
  this.music.onPlay(()=>{
    // console.log('播放');
    this.setData({
      isPlay:true
    })
  }),
  this.music.onPause(()=>{
    // console.log('暂停');})
    this.setData({
      isPlay:false
    })
  })
  this.music.onStop(()=>{
    this.setData({
      isPlay:false
    })
  })
  this.music.src=this.data.musicPlay.musicUrl
  this.music.title=this.data.musicPlay.musicTitle
  if(this.data.isPlay){
    console.log(this.data.isPlay);
    this.music.play()
  }else{
    this.music.pause()
  }

}

我们需要专门写一个修改播放状态的功能函数

  changePlayState(isPlay){
    this.setData({
      isPlay
    })
  }

修改后:

//监听播放暂停停止
  this.music.onPlay(()=>{
    this.changePlayState(true)
  }),
  this.music.onPause(()=>{
    this.changePlayState(false)
  })
  this.music.onStop(()=>{
    this.changePlayState(false)
  })

知识点:出去了以后再回来是暂停状态(如何解决页面销毁音乐播放问题)
在这里插入图片描述
这里有个知识点 getApp的使用 我们需要一个全局的东西来保存isPlay的状态 以前学了缓存 现在学app.js
https://developers.weixin.qq.com/miniprogram/dev/reference/api/getApp.html

  1. app.js:
    isMusicPlay:false,   //是否有音乐在播放
    musicId:''
  },
// 获取全局实例
const appInstance=getApp

在这里插入图片描述
上面这个太冗余了
可以这样 这样就把状态和id保存下来了(妙)
在这里插入图片描述
3. 保存下来是为了下次再进来时 判断是不是上次的状态是不是播放

    if(appInstance.globalData.isMusicPlay&&appInstance.globalData.musicId==musicId){
      // 修改当前页面音乐播放状态为true
      this.setData({
        isPlay:true
      }) 
    }

解决两个页面通信问题:缓存 appdata

11 继续播放页面

一个知识点

上一曲下一曲
妙 在播放页面切歌只用一个函数就行了 只需要判断按钮传入的id就知道点的哪个
但是播放页面没有推荐页面那些歌曲的信息啊 只有当前的信息
所以我们要把上一首下一首交给推荐页面
思路:点的上一曲还是下一曲我们要传给推荐页面 然后推荐页面把id传回来
知识点 页面通信—npm(我想也可以通过缓存和公共js来解决)
在这里插入图片描述
在这里插入图片描述
知识点

  1. 终端输入npm init 一路回车 或者直接npm init -y 不用回车直接默认了
  2. 然后就看到package.json 这里面就是我们的项目的说明书 必须的两个东西 一个是包名一个是版本号 npm install +包名 +@版本号
  3. 然后把不要的都删了只剩下了
{
  "name": "1",
  "version": "1.0.0"
}
  1. 勾选允许使用npm模块
  2. pubsub可以解决那个问题
    在这里插入图片描述

那个MY TOPIC 要用一个消息的名称替换 消息的订阅和发布 相当于vue中的自定义事件
知识点 自定义事件

  1. 分类
    1. 标准DOM事件
    2. 自定义事件
  2. 标准DOM事件
    1. 举例: click,input。。。
    2. 事件名固定的,事件由浏览器触发
    3. 自定义事件
    1. 绑定事件
      1. 事件名
      2. 事件的回调
      3. 订阅方: PubSub.subscribe(事件名,事件的回调)
      4. 订阅方式接受数据的一方
    2. 触发事件
      1. 事件名
      2. 提供事件参数对象, 等同于原生事件的event对象
      3. 发布方: PubSub.publish(事件名,提供的数据)
      4. 发布方是提供数据的一方

所以播放页面应该是发布方
推荐页是订阅方
思考:页面之间传参与订阅模式
上面几步完成以后
然后在推荐页面引入他import PubSub from 'pubsub.js'
报错
VM4612 WAService.js:2 Error: module “pages/recommendSong/pubsub.js” is not defined
知识点
我们看到报错信息是在recommendSong里面找不到pubsub
当然找不到了 因为pubsub不是安装在推荐页面的
所以新增第六步 构建npm包
在这里插入图片描述
在这里插入图片描述
是什么意思呢要把原来的包转化成小程序能使用的包
小程序加载第三方包的时候只会到miniprogram npm里面找
如果没有这个文件夹或者文件夹下没有要找的包 就从当前目录出发去找(就出现上文报错的情况)

关于订阅发布

先订阅再发布
先在推荐页面订阅

    PubSub.subscribe('switchType',(msg,data)=>{
      console.log(msg,data);
    });

然后播放页面

  handleChange(event){
    let type=event.currentTarget.id
    console.log(type);
    // 发送消息给推荐页面
    PubSub.publish('switchType',type)
  }

一个坑 我的这样写总是报错CreateListFromArrayLike called on non-object
但是老师的没有问题 因为老师下的包是pubsub-js而我下的是pubsub.js 排查了好长时间啊
不过我的也能用 不过就是要把type变成数组
然后订阅的那个msg是不能打印的 只能打印data(老师的是都能打印的)
我修改后的代码
播放页面:

  handleChange(event) {
    let type = event.currentTarget.id
    let index={'haha':'fdsa'}
    // 发送消息给推荐页面
    PubSub.publish('hello/world',[type]);
  }

推荐页面:onload里面

    PubSub.subscribe('hello/world', function(data) {
      console.log(data);
    });

因为订阅只要触发一次就够了 所以选择放到onload里面

现在我们需要知道下标方便推荐页面知道上一首歌下一首歌是哪一首
在这里插入图片描述
这时候 wxml里面循环遍历的那里应该多加一个data-index={{index}}了 然后setdata一下
方便取到下标
我是这样写的

    let song=event.currentTarget.dataset.song
    console.log(song);
    let index=event.currentTarget.dataset.index

老师的:
let {index,song}=event.currentTarget.dataset

订阅函数里面也可以简写

      let{index,recommendList}=this.data         //获取data里面的index和recommendList

注意 本来我在订阅函数里是照着老师写的 但是总是报错 我又把我的pubsub.js换成老师的pubsub-js发现还是报错 说明不是这个的原因 然后我简化了一下找找到底是哪里错了
在这里插入图片描述
这时候我才发现箭头函数的好处

这样我们就能获取index了

PubSub.subscribe('hello/world', (mag,data)=>{
      let{index}=this.data
      if(data=='pre'){//上一首
        index=index-1
      }else{             //下一首
        index=index+1
      }
      this.setData({
        index
      })
      console.log(index);
    });

然后就可以取到音乐id
let musicId=recommendList[index].id
但是这个页面没用 我们要把它再传到详情页
详情页有了id就可以通过id播放对应的歌曲
所以我们又要用到订阅发布了
这时候显示播放页订阅 然后推荐页发布
什么时候订阅呢 在上一次发布之前
在这里插入图片描述
在这里插入图片描述

我试了不行 肯定哪里有问题 一般来说应该就打印一次啊 为什么每次都会多一次呢
在这里插入图片描述
我们把订阅消息的回调放到了点击切歌里面 会累加的
他的底层是怎么做的呢?
{musicId:[{第一个回调},{第二个回调},{后面添加的回调}……{}]}
一个坑注意 订阅了多次 会导致数组有多个回调 这时候我们打印出来他会把里面的东西全部打印
这时候就需要取消订阅

    // 订阅来自推荐页面的音乐id
    PubSub.subscribe('musicId',(msg,musicId)=>{
      console.log(musicId);
      PubSub.unsubscribe('musicId')
    })

主要思路是什么呢 推荐页面接收到了歌曲页面点的是上一曲还是下一曲 然后根据这个让当前的commendList下标+1-1 然后再判断这条数据的id 然后再传给播放页面
然后播放页面再根据传入的id 就知道播放哪一首歌曲了

待做
但是我写的有bug 现在暂时没找到bug在哪里

var PubSub = require('pubsub-js');
// pages/songDetail/songDetail.js
import request from '../../utils/request'
// 获取全局实例
const appInstance = getApp()
Page({
  data: {
    isPlay: false, //标识是否在播放
    song: { //渲染页面用的
      title: '',
      singer: '',
      coverImg: ''
    },
    musicId: '', //音乐的id
    musicPlay: { //播放音乐用的
      musicTitle: '',
      musicUrl: ''
    }
  },
  music: null,
  /**
   * 生命周期函数--监听页面加载
   */
  onl oad: function (options) {
    //接收路由跳转的query参数
    // console.log(options.musicId);
    let musicId = options.musicId
    this.setData({
      musicId
    })
    // 判断之前的音乐是否在播放
    if (appInstance.globalData.isMusicPlay && appInstance.globalData.musicId == musicId) {
      // 修改当前页面音乐播放状态为true
      this.setData({
        isPlay: true
      })
    }
    this.getMusicInfo(musicId) //
    this.getMusicDetail(musicId) //这两个是用来获取song和musicPlay里面的东西的

  },
  //获取音乐详情功能函数
  async getMusicInfo(id) {
    let songData = await request('/song/detail', {
      ids: id
    })
    console.log(songData);
    this.setData({
      'song.singer': songData.songs[0].ar[0].name,
      'song.title': songData.songs[0].name,
      'song.coverImg': songData.songs[0].al.picUrl,
      'musicPlay.musicTitle': songData.songs[0].name
    })
    //动态修改窗口标题
    wx.setNavigationBarTitle({
      title: this.data.song.name,
    })
  },
  // 点击播放或暂停
  handleMusicPlay() {
    if (this.data.isPlay == true) {
      this.setData({
        isPlay: false
      })
      this.musicPlay()
    } else {
      this.setData({
        isPlay: true
      })
      this.musicPlay()
    }
  },

  //获取音乐src
  async getMusicDetail(musicId) {
    //获取链接
    let link = await request('/song/url', {
      id: musicId
    })
    console.log("发送了一次请求");
    this.setData({
      'musicPlay.musicUrl': link.data[0].url
    })
  },
  // 音乐播放功能
  musicPlay: function () {
    this.music = wx.getBackgroundAudioManager(),
      //监听播放暂停停止
      this.music.onPlay(() => {
        // console.log('播放');
        this.changePlayState(true)
        //全局
        appInstance.globalData.musicId = this.data.musicId
      }),
      this.music.onPause(() => {
        // console.log('暂停');})
        this.changePlayState(false)
      })
    this.music.onStop(() => {
      this.changePlayState(false)
    })
    this.music.src = this.data.musicPlay.musicUrl
    this.music.title = this.data.musicPlay.musicTitle
    if(this.data.isPlay){
      this.music.play()
    }else{
      this.music.pause()
    }
  },
  // 修改播放状态
  changePlayState(isPlay) {
    this.setData({
      isPlay
    })
    // 全局状态
    appInstance.globalData.isMusicPlay = isPlay
    console.log(appInstance.globalData.isMusicPlay);
  },
  handleChange(event) {
    // this.changePlayState(false)
    let type = event.currentTarget.id
    this.music.stop()
    // 订阅来自推荐页面的音乐id
    PubSub.subscribe('musicId',(msg,musicId)=>{
      console.log(musicId);
      this.setData({
        musicId
      })
        this.getMusicInfo(musicId) //
        this.getMusicDetail(musicId)
        this.musicPlay()
      PubSub.unsubscribe('musicId')
    })
    // 发送消息给推荐页面
    PubSub.publish('switchType',type);
  }
})

我知道了 又是异步的问题
在这里插入图片描述

可以这样
在这里插入图片描述

群里听说:现在基本上面试,算法+设计模式会占50的比重
最重要的是知道 各个组件的特性 知道什么时候 用什么技术最合适

进度条

这里我没有听老师讲 自己写的 因为我看小程序有他自己的进度条组件 更方便点
这里有个错我又查了好长时间
注意
在这里插入图片描述
本来我是写的currentTime的 找了好长时间的错误

 sliderChange:function(e){
    // this.music.pause()
    console.log(e.detail.value);
    this.music.seek(e.detail.value*this.data.duration/100)
  },
   formatTime:function(time){
    var minute=Math.floor(time/60)%60
    var second=Math.floor(time)%60
    return ((minute>10?minute:'0'+minute)+":"+(second>10?second:'0'+second))
  }

e.detail.value是百分比
这里的seek要传currentTime

    this.music.onTimeUpdate(()=>{
      this.setData({
        'sliderDetail.duration':this.formatTime(this.music.duration),
        'sliderDetail.currentTime':this.formatTime(this.music.currentTime),
        'sliderDetail.percent':this.music.currentTime/this.music.duration*100,
        duration:this.music.duration      //这里这样写是因为上面那个要用原始的数据而不是xx:xx
      })


    })

听说:
作文推荐你们看潘赟的
英一鸭的准

妙啊 老师推荐:momentjs:专门处理日期和时间的一个库 我们拿到的数据已经有长度了

下一曲

在播放页面 点击的时候有一个根据传入的是pre还是next在推荐页面订阅了id
我们也可以拿来用 但是呢 我觉得要把它拿出来放到data里面 因为没有点击的话(自动下一曲没有点击)就订阅不了 所以我就想到放到data里面
到这里就差不多告一段落了
贴出来播放页面的代码吧

var PubSub = require('pubsub-js');
// pages/songDetail/songDetail.js
import request from '../../utils/request'
// 获取全局实例
const appInstance = getApp()
Page({
  data: {
    isPlay: false, //标识是否在播放
    song: { //渲染页面用的
      title: '',
      singer: '',
      coverImg: ''
    },
    musicId: '', //音乐的id
    musicPlay: { //播放音乐用的
      musicTitle: '',
      musicUrl: ''
    },
    sliderDetail:{
      duration:'',
      currentTime:'',
      percent:''
    }
  },
  music: null,
  /**
   * 生命周期函数--监听页面加载
   */
  onl oad: function (options) {
    //接收路由跳转的query参数
    // console.log(options.musicId);
    let musicId = options.musicId
    this.setData({
      musicId
    })
    // 判断之前的音乐是否在播放
    if (appInstance.globalData.isMusicPlay && appInstance.globalData.musicId == musicId) {
      // 修改当前页面音乐播放状态为true
      this.setData({
        isPlay: true
      })
    }
    this.getMusicInfo(musicId) //
    this.getMusicDetail(musicId) //这两个是用来获取song和musicPlay里面的东西的
    // 订阅来自推荐页面的音乐id  因为自动切换到下一曲也要用到订阅 那个只是局限于点击以后的
    PubSub.subscribe('musicId',(msg,musicId)=>{
      this.setData({
        musicId
      })
        this.getMusicInfo(musicId) //
        this.getMusicDetail(musicId)
       PubSub.unsubscribe('musicId')
    })
  },
  //获取音乐详情功能函数
  async getMusicInfo(id) {
    let songData = await request('/song/detail', {
      ids: id
    })
    this.setData({
      'song.singer': songData.songs[0].ar[0].name,
      'song.title': songData.songs[0].name,
      'song.coverImg': songData.songs[0].al.picUrl,
      'musicPlay.musicTitle': songData.songs[0].name
    })
    //动态修改窗口标题
    wx.setNavigationBarTitle({
      title: this.data.song.name,
    })
  },
  // 点击播放或暂停
  handleMusicPlay() {
    if (this.data.isPlay == true) {
      this.setData({
        isPlay: false
      })
      this.musicPlay()
      console.log('handleMusic里面执行musicPlay 状态改为false');
    } else {
      this.setData({
        isPlay: true
      })
      this.musicPlay()
      console.log('handleMusic里面执行musicPlay 状态改为true');
    }
  },
  //获取音乐src
  async getMusicDetail(musicId) {
    //获取链接
    let link = await request('/song/url', {
      id: musicId
    })
    this.setData({
      'musicPlay.musicUrl': link.data[0].url
    })
    console.log("获取了一次地址");
      this.musicPlay()
  },
  // 音乐播放功能
  musicPlay: function () {
    this.music = wx.getBackgroundAudioManager(),
      //监听播放暂停停止
      this.music.onPlay(() => {
        // console.log('播放');
        this.changePlayState(true)
        //全局
        appInstance.globalData.musicId = this.data.musicId
        console.log("musicPlay里面音乐播放");

      }),
      this.music.onPause(() => {
        // console.log('暂停');})
        this.changePlayState(false)
        console.log("musicPlay里面音乐暂停");
      })
    this.music.onStop(() => {
      this.changePlayState(false)
      console.log("musicPlay里面音乐停止");

    })
    this.music.onTimeUpdate(()=>{
      this.setData({
        'sliderDetail.duration':this.formatTime(this.music.duration),
        'sliderDetail.currentTime':this.formatTime(this.music.currentTime),
        'sliderDetail.percent':this.music.currentTime/this.music.duration*100,
        duration:this.music.duration
      })
     this.music.onEnded(()=>{
      console.log("播放完了");
      PubSub.publish('switchType','next');

     })

    })
    this.music.src = this.data.musicPlay.musicUrl
    this.music.title = this.data.musicPlay.musicTitle
    if(this.data.isPlay){
      this.music.play()
    }else{
      this.music.pause()
    }
    //slider

  },
  // 修改播放状态
  changePlayState(isPlay) {
    this.setData({
      isPlay
    })
    // 全局状态
    appInstance.globalData.isMusicPlay = isPlay
  },
  handleChange(event) {
    // this.changePlayState(false)
    let type = event.currentTarget.id
    console.log("点击了"+type);
    this.music.stop()
    // 订阅来自推荐页面的音乐id  (放在onload里面了)
    // 发送消息给推荐页面
    PubSub.publish('switchType',type);
  },
  sliderChange:function(e){
    // this.music.pause()
    console.log(e.detail.value);
    this.music.seek(e.detail.value*this.data.duration/100)
  },
   formatTime:function(time){
    var minute=Math.floor(time/60)%60
    var second=Math.floor(time)%60
    return ((minute>10?minute:'0'+minute)+":"+(second>10?second:'0'+second))
  }

})

待做

随机播放 和 单曲循环
随机思路 1注意下标不要超出 2如果随机到了当前的音乐 要再随机一次

到现在的感悟

我是真的感受到了有人说的 厉害的代码有很高的可读性,一看就能看懂。像我这种遇到个功能改来改去的把我自己都绕晕了,一段代码一会搬到这儿 过一会加个功能发现好像不能放着儿,又搬到那边 ,虽然最后功能都实现了 但是还是感觉支离破碎的 可能是因为我还没有找到面向对象的感觉吧

听说:liveserver

12 搜索

搜索初始化

搜索框 右侧搜索固定 左侧输入框自适应
在这里插入图片描述
在这里插入图片描述
这个可以用height lineheight 让文字居中然后margin来实现横线的效果

知识点:流式布局
在这里插入图片描述

没必要这样写:
在这里插入图片描述

可以直接初始化数据请求两个数据

  async getInitData(){
    let placeholderData = await request('/search/default'); //搜索框
    let hotListData=await request("/search/hot/detail")   //热歌榜数据
    this.setData({
      placeholder:placeholderData.data.realkeyword,
      hotList:hotListData.data
    })
  },

感悟:太强了 原来人家连图标地址都给出来了(热歌,飙升的图标)
在这里插入图片描述

在这里插入图片描述
看到这样的样式 那个图片稍微再字的上面 可以这样 : 给父盒子设个宽度 然后让字上下居中 然后图片就可以在字的上面了

搜索功能

知识点 :输入框input事件与change事件的区别:change失去焦点 input实时
一个坑
在这里插入图片描述
这是什么东西呢 这是对象的意思 输入为text怎么会显示对象呢 因为async返回值是一个promise对象
解决:发请求再封装一个方法,用async 。这样的话searchContent就不会返回对象了
还有一个要注意的是节流的知识点 我们如果输入的很快 他会请求很多次 其实是没有必要的
所以第二种方法 可以设一个定时器 然后在定时器上加async

在这里插入图片描述
这样也不够 他还是请求那么多次
妙啊
在这里插入图片描述
这样还是不对啊 哪里不对呢
在这里插入图片描述
我们看这个 会发现每次它都会置为false 所以每次都会发请求 我们应该放到函数外面
在这里插入图片描述

这种也不行 因为第一次发请求也要等300ms

 issend:false,  //节流用
   //监听input输入
   handleInputChange(event){
    console.log(event.detail.value);
    this.setData({
      searchContent:event.detail.value.trim()
    })
    if(this.issend){
      return; 
    }
    this.issend=true
    setTimeout(async() => {
      let searchListData=await request('/search',{keywords:this.data.searchContent,limit:10})
      console.log(searchListData);
      this.issend=false
    }, 300);
  },

我们可以把请求提到外面
但是又遇到了那个async的问题
在这里插入图片描述
这时候就要单独封装一个请求的函数

  //监听input输入
   issend:false,  //防抖节流用
   handleInputChange(event){
    console.log(event.detail.value);
    this.setData({
      searchContent:event.detail.value.trim()
    })
    if(this.issend){
      return; 
    }
    this.searchList()
    this.issend=true
    setTimeout(async() => {
      this.issend=false
    }, 300);           //平常做节流一般都是300ms
  },
  //请求搜索数据
  async searchList(){
    let searchListData=await request('/search',{keywords:this.data.searchContent,limit:10})
    console.log(searchListData);
  },

搜索内容展示

当时那个搜索图标 我给设成position是absolute了 所以在搜索内容盒子里的图标全都跑到上面去了
在这里插入图片描述
因此上面的搜索图标应该给他单独起个样式名字

在这里插入图片描述
可以看到还是有问题的
在他为空的时候不应该发请求
在这里插入图片描述

在这里插入图片描述
然后这两个是互斥的关系
知识点:
两个都加一个block 然后用条件判断决定显示哪一个
在这里插入图片描述

block有什么好处呢 他可以帮你圈中多个元素 但是在页面结构中 并没有显示block如图
在这里插入图片描述

但是呢 还有一个问题 当显示列表为空的时候,他变不回热搜列表
这时候我们还要改一下js里面的代码 如果输入框没东西那么searchList为空数组(因为判断显示哪个block就是根据searchList是不是空决定的) (其实本来就应该这么做的 要不然输入框为空 数组里还有东西)

听说:angular+nest.js是很好的开发框架 angular以后出去ts无压力
听说:https://www.52pojie.cn/forum.php?mod=viewthread&tid=1301167&extra=page%3D1%26filter%3Dtypeid%26typeid%3D283 用react写网易云
听说:在这里插入图片描述
听说 微信小程序云开发-从0打造云音乐全栈小程序 【最新3-9小节】接口不能用了
https://www.52pojie.cn/thread-1304174-1-1.html
我知道了 coderwhy就是王红元老师 讲的确实很好

历史记录

历史记录应该跟热搜榜一起显示的 然后搜索的时候两个都隐藏 所以历史记录应该写在wx:else里面
在这里插入图片描述
静态搭建的时候 有一个删除的图标一直在最后一行 怎么做呢 可以用绝对定位来做

然后我们要考虑动态的历史记录了
首先一个知识点(unshift方法)https://www.w3school.com.cn/jsref/jsref_unshift.asp
在请求数据里面加

    //将搜索的关键字放到历史记录里面
    let{searchContent,historyList}=this.data
    historyList.unshift(searchContent)
    this.setData({
      historyList
    })

但是一刷新就没有了 所以是不对的 应该存到本地
然后还有一个要注意的是要找一下以前有没有添加过这个记录
可以用find 还有indexof查找存不存在
如果不存在就添加 但是如果存在呢?如果是这种情况[a,b,c] 然后这次搜索的是b 我们就要把b添加进去 然后把后面的b删掉
代码放在请求数据函数里面

    //将搜索的关键字放到历史记录里面
    let{searchContent,historyList}=this.data
    if(historyList.indexOf(searchContent) !== -1){ //以前存在
       //以前存在就删掉对应的下标对应的信息并添加信息到第一个
      historyList.splice(historyList.indexOf(searchContent),1)
      historyList.unshift(searchContent)
    }else{    //以前不存在
      historyList.unshift(searchContent)
    } 
    wx.setStorageSync('searchHistory', historyList)

清空搜索内容:x号
那么一个x如何控制表单项的内容呢 我们可以通过value让他和我们搜集的数据关联起来
value={{searchContent}}
思路:我们通过事件处理函数来控制searchContent 为空 然后通过searchContent来控制value的值
在这里插入图片描述

  clearSearchContent(){
    this.setData({
      searchContent:''
    })
  },

但是并没有清空 在函数里随便打印个东西 发现并没有执行
一个天坑 这是为什么呢 其实我们点的是表单项 ×号其实在表单项下面 我们要把它的层级提高一点
在这里插入图片描述
可以用这个小箭头测试一下能不能测出来叉号

有的时候点叉号命名输入框都没东西了,搜索出来的数据还是显示在上面
这时候我们还要把searchList置为空数组

听说:语义化是什么

在这里插入图片描述
这个的功能怎么做呢 很简单 清空缓存和数组就ok啦
可以再完善一点 再删除之前问用户同不同意
放到模板消息里面

听说:群里小伙伴发的面试题https://blog.csdn.net/qq_45018844/article/details/112008216

在这里插入图片描述

还可以再优化一下
当没有记录的时候 不显示记录的那个盒子 这样就没有“记录”这两个字和删除按钮了
判断历史记录数组的长度

还可以再优化一下
在没有内容的时候要隐藏掉那个叉号
也很简单 判断searchContent是不是空就好了
知识点 当然也可以hidden=true还是false 等同于vue里面的v-show
用户频繁输入删除 最好用hidden 性能要高

     <text class="clear" bindtap="clearSearchContent" hidden="{{!searchContent}}">X</text>

听说:群友发的
小程序云开发数据库照抄 mogodb 还能抄的这么蠢
loopup 关联的 唯一索引, 关联结果还用数组
mogodb 关联的唯一索引的, 结果直接是单条数据
待做:mogodb是什么

13 这样大致就写完了 接下来是一些没有在课上用到的知识点

定义模板

https://developers.weixin.qq.com/miniprogram/dev/reference/wxml/template.html
注意与上次的组件对比
在这里插入图片描述
在这里插入图片描述
如何向模板内部动态导入数据?
在这里插入图片描述
在这里插入图片描述
另外一个知识点:es6新特性 三点运算符 用来拆包

听说:进销存什么意思

还有一个群友发的图
在这里插入图片描述

获取用户唯一标识

知识点 fly库(与axios库类似) 支持的平台多

有一个加密数据知识点:jasonwebtoken (jwt)

听说:学会node 然后mongodb数据库

分包

分为三种:常规分包 独立分包 分包预下载

  • 常规分包:
    • 官方文档https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/basic.html
      在这里插入图片描述

然后我们会发现很多路径都错了
在这里插入图片描述
都要注意修改一下

  • 独立分包

    • 官方文档https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/independent.html

独立分包是小程序中一种特殊类型的分包,可以独立于主包和其他分包运行。从独立分包中页面进入小程序时,不需要下载主包。当用户进入普通分包或主包内页面时,主包才会被下载。

开发者可以按需将某些具有一定功能独立性的页面配置到独立分包中。当小程序从普通的分包页面启动时,需要首先下载主包;而独立分包不依赖主包即可运行,可以很大程度上提升分包页面的启动速度
我们用这个的时候会发现对应分包的iconfont没了 必须在当前的页面再引入

在这里插入图片描述

一个坑 比如你独立分包的页面以前受到了app.wxss影响,现在分包了以后很多样式会出问题 需要再把app.wxss里面的内容复制过来

  1. 设置 independent 为 true
  2. 特点:
    a) 独立分包可单独访问分包的内容,不需要下载主包
    b) 独立分包不能依赖主包或者其他包的内容
  3. 使用场景
    a) 通常某些页面和当前小程序的其他页面关联不大的时候可进行独立分包
    b) 如:临时加的广告页 || 活动页
  • 分包预下载(相当于图片懒加载)
    官方文档:https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages/preload.html
    1. 配置
      a app.json 中设置 preloadRule 选项
      b key(页面路径): {packages: [预下载的包名 || 预下载的包的根路径])}
    2. 特点:
      a) 在加载当前包的时候可以设置预下载其他的包
      b) 缩短用户等待时间,提高用户体验
      在这里插入图片描述

小程序支付

  1. 用户在小程序客服端下单(包含用户及商品信息)
  2. 小程序客户端发送下单支付请求给商家服务器
  3. 商家服务器同微信服务器对接获取唯一标识 openID
  4. 商家服务器根据 openId 生成商户订单(包含商户信息)
  5. 商家服务器发送请求调用统一下单 API 获取预支付订单信息
    a) 接口地址: https://api.mch.weixin.qq.com/pay/unifiedorder
  6. 商家对预支付信息签名加密后返回给小程序客户端
    a) 签名方式: MD5
    b) 签名字段:小程序 ID, 时间戳, 随机串,数据包,签名方式
    c) 参 考 地 址 :
    https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=3
  7. 用户确认支付(鉴权调起支付)
    a) API: wx.requestPayment()
  8. 微信服务器返回支付结果给小程序客户端
  9. 微信服务器推送支付结果给商家服务器端
    官网对应地址
    https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_3&index=1


这篇关于网易云小程序笔记的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程