微信小程序 【开发】 自学总结

抖音小程序与微信小程序差异
  1. 获取用户信息的方式
  • 通过button按钮,设置属性 open-type="getUserInfo",获取获得用户信息的授权
  • 兼容性,通过wx.canIUse('button.open-type.getUserInfo')的值,来判断按钮是否可用
  • 通过拥有上述属性的按钮事件bindgetuserinfo - https://developers.weixin.qq.com/miniprogram/dev/component/button.html 来获取,事件处理函数中ev.detail.userInfo对应的用户信息
  • wx.getUserInfo在用户未授权的情况下,将不再出现授权弹窗,所以才使用引导用户点击按钮的授权方式
  • 通过onload生命周期,wx.getSetting来获取用户权限,并对用户信息状态赋值
<view wx:if="{{showLoginBtn}}">
  <button 
    class="skillbtn" 
    bindgetuserinfo="loginFn" 
    open-type="getUserInfo"
  >手动授权登录</button>
</view>
<view wx:else>
  <text>请升级微信版本</text>
</view>
...
//查看授权按钮是否可以使用
if(wx.canIUse('button.open-type.getUserInfo')){
  _that.setData({
    'showLoginBtn': true
  });
};
...
//获取用户信息与请求服务器获取userid
loginFn(ev){
  var _that = this;
  //获取用户的当前授权设置
  wx.getSetting({
    success(res){
      //查看【获取用户信息】授权
      if(res.authSetting['scope.userInfo']){
        wx.getUserInfo({
          success(res){
            var _userInfo = res.userInfo;
            
            //获得code,再进行注册
            _that.registerFn(function(_code){

              //请求后台接口,获取userid

            });
          },
          fail(err){
            console.log(err);
          }
        });
      }else{
        app.showPopFn({
          'title':'授权失败',
          'icon': 'none',
          'duration': 1000
        });
      }
    }
  })
}

1-2. 如果wx:if挂载的标签上条件,与通过此条件来增加标签上的class类名时候,不会产生动画效果
解决: 需要增加额外条件,来改变class类名,可能的话,还要增加延时定时器来进行这个类名条件的设置

  1. 最新官方关于 用户信息相关接口调整 - https://developers.weixin.qq.com/community/develop/doc/000cacfa20ce88df04cb468bc52801
    【背景】:
    很多开发者在打开小程序时就通过组件方式唤起getUserInfo弹窗,如果用户点击拒绝,无法使用小程序,这种做法打断了用户正常使用小程序的流程,同时也不利于小程序获取新用户。

【注意】:

  • 建议多用getUserProfile,即便被拒绝,再次调用getUserProfile,仍可以拉起授权。
  • 授权被拒后,通过wx.openSetting打开设置菜单,是无法找到被拒的授权信息的。

官方变化:


对比图
  • 新的授权书写方式
    由于getUserProfile接口从2.10.4版本基础库开始支持(覆盖微信7.0.9以上版本),考虑到开发者在低版本中有获取用户头像昵称的诉求,对于未支持getUserProfile的情况下,开发者可继续使用getUserInfo能力
    //--wxml
<view class="container">
  <view class="userinfo">
    <block wx:if="{{!hasUserInfo}}">
      <button wx:if="{{canIUseGetUserProfile}}" bindtap="getUserProfile"> 获取头像昵称 </button>
      <button wx:else open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button>
    </block>
    <block wx:else>
      <image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>
      <text class="userinfo-nickname">{{userInfo.nickName}}</text>
    </block>
  </view>
</view>

//--js

Page({
  data: {
    userInfo: {},
    hasUserInfo: false,
    canIUseGetUserProfile: false,
  },
  onLoad() {
    if (wx.getUserProfile) {
      this.setData({
        canIUseGetUserProfile: true
      })
    }
  },
  getUserProfile(e) {
    // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认
    // 开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
    wx.getUserProfile({
      desc: '用于完善会员资料', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
      success: (res) => {
        this.setData({
          userInfo: res.userInfo,
          hasUserInfo: true
        })
      }
    })
  },
  getUserInfo(e) {
    // 不推荐使用getUserInfo获取用户信息,预计自2021年4月13日起,getUserInfo将不再弹出弹窗,并直接返回匿名的用户个人信息
    this.setData({
      userInfo: e.detail.userInfo,
      hasUserInfo: true
    })
  },
})

2-2. 区别与获取用户昵称与信息的授权,有些授权拒绝后,再次调用API,不会再次弹出授权界面
如果用户已拒绝授权,则不会出现弹窗,而是直接进入接口 fail 回调。需要发者兼容用户拒绝授权的场景
以获取用户地理信息的授权为例

授权流程图

2-3. 关于授权的过期时间

  • 在开发者工具中,点击 [ 清缓存-全部清楚 ] ,即可一次性清楚所有数据缓存与授权记录信息
  • 借用微信文档说明:在真机上,一旦用户明确同意或拒绝过授权,其授权关系会记录在后台,直到用户主动删除小程序。
    直接在微信-小程序内,找到对应的小程序将其删除,可能的话,再加上(我-设置-通用-存储空间-缓存-清理),更加干净的清理对应小程序的各种数据缓存与授权信息
  1. rpx(responsive pixel): 可以根据屏幕宽度进行自适应。
    规定屏幕宽为750rpx。
    如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。

  2. 获取用户坐标
    在app.json中,添加获取目标位置的权限:

"permission": {
    "scope.userLocation": {
      "desc": "你的位置信息将用于小程序位置接口的效果展示"
    }
}

获取用户坐标 wx.getLocation

getUserLocalFn(){
    var that = this;
    //获取用户坐标
    wx.getLocation({
      type: 'wgs84',
      success(res){
        var _userLocal = that.data.userLocal;
        _userLocal.latitude = res.latitude;
        _userLocal.longitude = res.longitude;
        that.setData({
          "userLocal": _userLocal
        });
      },
      fail(err){
        console.log(err);
      }
     });
}
  1. 图片预加载
    app.js
//图片预加载
loadImg(_link,sucFn,errFn,cmpFn){
  wx.getImageInfo({
    src: _link,
    success(res){
      sucFn && sucFn(res);
    },
    fail(err){
      errFn && errFn(err);
    },
    complete(res){
      cmpFn && cmpFn(res);
    }
  });
},
//加载所有图片
loadAllImgs(imgListArr,compFn){
  //图片预加载 相关
  var _that = this;
  var num = 0;
  
  for(var i=0; i<imgListArr.length; i++){
    ;(function(i){
      var _tempImg = imgListArr[i];
      //加载图片
      _that.loadImg(_tempImg,function(res){
        num+=1;
        if(num==imgListArr.length){
          compFn && compFn();
        }
      },function(err){
        num+=1;
        if(num==imgListArr.length){
          compFn && compFn();
        }
      },null);  
    })(i);
  }
}

使用,pages下某个页面:

//加载所有图片
app.loadAllImgs([
  'https://www.xxx.net/minisoft/zhenze/everyDayPic_1.jpg',
  'https://www.xxx.net/minisoft/zhenze/everyDayPic_2.jpg',
  'https://www.xxx.net/minisoft/zhenze/everyDayPic_3.jpg'
],function(){
  _that.setData({
    allimgLoaded: true
  });
});

通过allimgLoaded这个数据变量来切换loading条和图片
wxml结构:

<!-- 背景图 -->
<view class="imgBox">
    <!--loadingGear 加载条 -->
    <view class="loadingGear {{allimgLoaded? '':'loadingGearAc'}}">
        <view class="inset">
            <view class="blocks"></view>
            <view class="blocks"></view>
            <view class="blocks"></view>
            <view class="blocks"></view>
            <view class="blocks"></view>
            <view class="blocks"></view>
            <view class="blocks"></view>
            <view class="blocks"></view>
        </view>
    </view>  
    <image wx:if="{{allimgLoaded}}" src="{{itemEle.picSrc}}" mode="aspectFill" />
</view>

css3 loading图标,激活状态运动,否则不运动
wxss

/*loadingGear - 加载条2*/
.loadingGear { position:absolute; left:50%; top:50%; width:4rpx; height:4rpx; margin:-2rpx 0 0 -2rpx; 
z-index:-1;
}
.loadingGear .inset  { position:relative; z-index:2; width:100%; height:100%; visibility:hidden; }
  /*激活*/
.loadingGearAc { z-index:9999;}
.loadingGearAc .inset { visibility:visible;
-webkit-animation:gearAni linear 2s infinite;
-moz-animation:gearAni linear 2s infinite;
-ms-animation:gearAni linear 2s infinite;
-o-animation:gearAni linear 2s infinite;
animation:gearAni linear 2s infinite;
}
.loadingGear .inset .blocks { position:absolute; left:0; bottom:0; z-index:2; width:100%; height:300%; overflow:hidden; background:#006d78; font-size:0; line-height:0;
-webkit-transform-origin:center bottom; -moz-transform-origin:center bottom; transform-origin:center bottom;
}
.loadingGear .inset .blocks:nth-child(1){ -webkit-transform:rotate(0deg) translateY(-10rpx); -moz-transform:rotate(0deg) translateY(-10rpx); transform:rotate(0deg) translateY(-10rpx); }
.loadingGear .inset .blocks:nth-child(2){ -webkit-transform:rotate(45deg) translateY(-10rpx); -moz-transform:rotate(45deg) translateY(-10rpx); transform:rotate(45deg) translateY(-10rpx); }
.loadingGear .inset .blocks:nth-child(3){ -webkit-transform:rotate(90deg) translateY(-10rpx); -moz-transform:rotate(90deg) translateY(-10rpx); transform:rotate(90deg) translateY(-10rpx);  }
.loadingGear .inset .blocks:nth-child(4){ -webkit-transform:rotate(135deg) translateY(-10rpx); -moz-transform:rotate(135deg) translateY(-10rpx); transform:rotate(135deg) translateY(-10rpx);  }
.loadingGear .inset .blocks:nth-child(5){ -webkit-transform:rotate(180deg) translateY(-10rpx); -moz-transform:rotate(180deg) translateY(-10rpx); transform:rotate(180deg) translateY(-10rpx); }
.loadingGear .inset .blocks:nth-child(6){ -webkit-transform:rotate(225deg) translateY(-10rpx); -moz-transform:rotate(225deg) translateY(-10rpx); transform:rotate(225deg) translateY(-10rpx); }
.loadingGear .inset .blocks:nth-child(7){ -webkit-transform:rotate(270deg) translateY(-10rpx); -moz-transform:rotate(270deg) translateY(-10rpx); transform:rotate(270deg) translateY(-10rpx); }
.loadingGear .inset .blocks:nth-child(8){ -webkit-transform:rotate(315deg) translateY(-10rpx); -moz-transform:rotate(315deg) translateY(-10rpx); transform:rotate(315deg) translateY(-10rpx); }
/*gearAni*/
@-webkit-keyframes gearAni { 
  0% { -webkit-transform:rotate(0deg);  -moz-transform:rotate(0deg);  transform:rotate(0deg);}  
  100% { -webkit-transform:rotate(360deg);  -moz-transform:rotate(360deg);  transform:rotate(360deg);}  
}
@keyframes gearAni { 
  0% { -webkit-transform:rotate(0deg);  -moz-transform:rotate(0deg);  transform:rotate(0deg);}  
  100% { -webkit-transform:rotate(360deg);  -moz-transform:rotate(360deg);  transform:rotate(360deg);}  
}
  1. 使用微信小程序API时候,需要先要测试下兼容
compareVersion(v1, v2){
  v1 = v1.split('.')
  v2 = v2.split('.')
  const len = Math.max(v1.length, v2.length)
  while(v1.length < len){
    v1.push('0')
  }
  while(v2.length < len){
    v2.push('0')
  }
  for(let i = 0; i < len; i++){
    const num1 = parseInt(v1[i])
    const num2 = parseInt(v2[i])
    if (num1 > num2){
      return true;
    }else if(num1 < num2){
      return false;
    }
  }
}

放在app.js内,pages使用js调用
如:音频API的兼容

var _btn = app.compareVersion(version,'1.6.0');
if(_btn){
  //基础库 1.6.0 开始支持,低版本需做兼容处理。
  InnerAudioContext = wx.createInnerAudioContext();
}else{
  wx.showModal({
    title: '提示',
    content: '当前微信版本过低,无法使用音频功能,请升级到最新微信版本后重试。'
  });
};
  1. 跳转到第三方小程序,需要获知对方的小程序APPID
    也可以放在app.js内方便,统一调用
jumpOtherMinisoft(_appid,_path,sucFn,errFn){
  var _that = this;

  //兼容
  var version = wx.getSystemInfoSync().SDKVersion;
  var _btn = _that.compareVersion(version,'1.3.0');
  
  if(_btn){
    //基础库 1.3.0 开始支持
    wx.navigateToMiniProgram({
      appId: _appid,
      path: _path,
      success(res){
        sucFn && sucFn(res);
      },
      fail(err){
        errFn && errFn(err);
      }
    });
  }else{
    wx.showModal({
      title: '提示',
      content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。'
    });
  };
}

pages页面内js直接使用

app.jumpOtherMinisoft('wx2c348cf579062e56','page/index/index?id=123',null,function(err){
  console.log(err);
  app.showPopFn({
    'title':'跳转小程序失败'
  });
});

7-2. 通过标签navigater跳转

<navigator 
    target="miniProgram" 
    open-type="navigate" 
    app-id="{{detailData.AppId}}" 
    path="{{detailData.Path}}" 
    extra-data="" 
    version="release"
>查看详情</navigator>

7-3. 获取三方小程序的APPID以及详细页的path

  • 如何查看小程序的appid,
    打开微信 - 搜索小程序 - 进入 - 点击右上角三个点 - 点击 弹出层小程序的名称 - 更多资料 - 即可查看 APPID
  • 如何获取详细页的链接
    登录后台 - 工具 - 生成小程序码 - 输入小程序APPID - 获取更多页面路径 - 添加自己微信号 - 开启 ( 注:复制功能仅保留10分钟,10分钟后功能消失 )
  • 打开小程序详细页 - 右上三个点 - 选择复制链接 (如果不存在,则说明上面一步时间过期了,或没有开发者权限)
    针对京东来说,需要将sku字段保留,其余删除,包括".html"
  • 将复制的链接黏贴至上一步左侧输入框,生成小程序码
  • 注:如果复制链接,提示 【网络错误稍后重试】, 排除断网的情况,可能是微信版本过低,重新安装最新版本微信即可

7-4. 配置跳转三方小程序的白名单,
在app.json内,配置navigateToMiniProgramAppIdList
补充说明:

  • 每个小程序可跳转的其他小程序数量限制为不超过 10 个 从 2.4.0 版本以及指定日期(具体待定)开始,开发者提交新版小程序代码时,如使用了跳转其他小程序功能,则需要在代码配置中声明将要跳转的小程序名单,限定不超过 10 个,否则将无法通过审核。
  • 该名单可在发布新版时更新,不支持动态修改。
  1. 转发以及分享到朋友圈
    转发的触发方式为,wxml内放入一个button,open-type,此外在页面的js内要定义onShareAppMessage与onShareTimeline,分别用来定义转发和分享到朋友圈
//用户点击右上角转发  图片长宽比是 5:4
//基础库 1.2.0 开始支持,低版本需做兼容处理
onShareAppMessage(res){
  //兼容
  var version = wx.getSystemInfoSync().SDKVersion;
  var _btn = app.compareVersion(version,'1.2.0');
  if(_btn){
    // 来自页面内转发按钮
    if(res.from === 'button'){};
    return {
      'title': res.target.dataset.tit  || app.globalData.shareObj.sharemes,
      'path':  res.target.dataset.path || app.globalData.shareObj.sharePath,
      'imageUrl': app.globalData.shareObj.shareSendPic
    }
  }else{
    wx.showModal({
      title: '提示',
      content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。'
    });
  };
},
//用户点击右上角转发到朋友圈  query-当前页面路径携带的参数  图片长宽比是 1:1
//从基础库 2.11.3 开始支持 
onShareTimeline(){
  //兼容
  var version = wx.getSystemInfoSync().SDKVersion;
  var _btn = app.compareVersion(version,'2.11.3');
  if(_btn){
    return {
      'title': app.globalData.shareObj.sharemes,
      'query': null,
      'imageUrl': app.globalData.shareObj.shareFriendPic
    }
  }else{
    wx.showModal({
      title: '提示',
      content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。'
    });
  };
},

注: 分享的图片比例分别为 5:4、1:1 (可以是本地文件路径、代码包文件路径或者网络图片路径。支持PNG及JPG)
在wxml内调用,添加自定义属性,可能会方便些

<button 
    class="insetBtn" 
    open-type="share"
    data-tit="{{listsData[nowIndex].tit}}"
    data-path="/pages/find_food_detail/index?sendindex={{nowIndex}}"
></button>
  1. 通过坐标,打开微信小程序内置地图,进行导航
//使用微信内置地图查看位置
openLocationFn(_lat,_lon,sucFn,errFn){
  if(_lat=='' || _lon==''){
    this.showPopFn({
      'title': '坐标错误'
    });
    return false;
  }else{
    wx.openLocation({
      'latitude': _lat,
      'longitude': _lon,
      'scale': 17,
      success(res){
        sucFn && sucFn(res);
      },
      fail(err){
        errFn && errFn(err);
      }
    });
  };
}

调用

goOpenLocationFn(){
  app.openLocationFn(30.91185865822398,120.50376582713723,null,function(err){
    console.log(err);
    app.showPopFn({
      'title': '调用失败',
      'icon': 'success',
      'duration': 500
    })
  })
}
  1. 小程序音频使用
    声明音频对象
var version = wx.getSystemInfoSync().SDKVersion;
var _btn = app.compareVersion(version,'1.6.0');
if(_btn){
  InnerAudioContext = wx.createInnerAudioContext();
}else{
  wx.showModal({
    title: '提示',
    content: '当前微信版本过低,无法使用音频功能,请升级到最新微信版本后重试。'
  });
};

配置音频属性,以及调用方法

//监听播放结束后,关闭对播放结束的监听
InnerAudioContext.src = _nowAudioSrc;
InnerAudioContext.play()
InnerAudioContext.onEnded(function(){
  InnerAudioContext.offEnded();
  InnerAudioContext.stop(); 
  ...业务样式等操作...
  console.log('播放完毕');
});

注: 上述监听结束的方法,最好结束后调用stop,重置音频

10-1. 播放进度与获取音频时长

//2. 监听播放、并获取音频的时长、播放位置与 进度百分比
nowAudioObj.offPlay();
nowAudioObj.onPlay(() => {
  
  //获取总时长与当前播放位置
  nowAudioObj.offTimeUpdate();
  nowAudioObj.onTimeUpdate(()=>{ 
    //初始化 -- 获取音频总时长
    _listsData[_index].audio.duration = nowAudioObj.duration; 
    //实时更新 -- 获取当前播放的位置
    _listsData[_index].audio.startTime = nowAudioObj.currentTime;
    //实时更新 -- 进度条百分比
    _listsData[_index].audio.nowTimePercent = Math.floor(nowAudioObj.currentTime/nowAudioObj.duration*100);

    _that.setData({
      'listsData': _listsData
    });
  });
});

//3. 播放结束监听
nowAudioObj.offEnded();
nowAudioObj.onEnded(() => {
  //重置播放位置、重置样式
  _listsData[_index].audio.startTime = 0;
  _listsData[_index].audio.nowTimePercent = Math.floor(0/nowAudioObj.duration*100);
  _listsData[_index].audio.playStatue = false;

  app.showPopFn({
    'title': '播放完毕'
  });

  //停止监听
  nowAudioObj.stop();
  nowAudioObj.offPlay();
  nowAudioObj.offEnded();
  nowAudioObj.offTimeUpdate();
  _that.setData({
    'listsData': _listsData
  });
});

//4. 捕获错误
nowAudioObj.offError();
nowAudioObj.onError(function(errMsg,errCode){
  console.log(errMsg);
  var _errMes = '';
  switch(errCode){
    case 10001:
      _errMes = '系统错误';
      break;
    case 10002:
      _errMes = '网络错误';
      break; 
    case 10003:
      _errMes = '文件错误';
      break;
    case 10004:
      _errMes = '格式错误';
      break;
    case -1:
      _errMes = '未知错误';
      break;          
  };
  app.showPopFn({
    title:_errMes,
    duration:600
  });
});  

注:

  • 音频不能使用 中文名字 ,否则不能被捕获报错信息
  • 音频在页面之前切换不可以停止,所以在切换页面之前,需要关闭音频,
    onHide -- wx.navigateTo 或 底部tab切换到其他页面触发
    onUnload -- wx.redirectTo 或 wx.navigateBack时候触发
  1. 输入框表单,通过键盘进行搜索,而非一个自定义按钮
    通过bindconfirm事件,可以在键盘敲击回车、在手机键盘输入法触摸完成后,即可进行搜索
<input 
    type="text" 
    bindinput="txtFn" 
    bindconfirm="inputConfirmFn"
    value="{{txtval}}" 
    placeholder="搜索内容..." 
/>

通过事件对象 ev.detail.value 可以完成内容获取

inputConfirmFn(ev){
  var _val = ev.detail.value;
  _val = _val.replace(/\s+/g,""); 
  if(_val != ''){
    console.log('提交的内容为',_val);
  }else{
    app.showPopFn({
      'title': '内容不能为空',
      'icon': 'none',
      'duration': 600
    });
  };
}
  1. 小程序自带的顶部工具栏,可以通过在app.json的window内,通过"navigationStyle":"custom"关闭默认导航
    只保留右上角三个点
"navigationStyle":"custom"

定义一个自定义状态栏组件,可以使用的属性与插槽,官方手册 - https://developers.weixin.qq.com/miniprogram/dev/extended/weui/navigation.html
问题:
不同设备的状态栏的高度不一样,因此自定义导航图标,可能会因设备不一样而跑偏,
所以,为了统一这个比例,可以通过wx.getSystemInfo获取系统信息,通过系统信息返回statusBarHeight获得状态栏高度( 单位px )
再通过 wx.getMenuButtonBoundingClientRect()方式是获得胶囊按钮的空间信息,以及系统状态栏的高度,之后进行对应的计算
【注】:getMenuButtonBoundingClientRect 部分手机有获取设备 信息延迟 - https://developers.weixin.qq.com/community/develop/doc/0006eeb2db0ae022a098c58f85d800,所以官方建议加100MS延迟,等待信息!

具体的方法是:
app.js onLaunch声明周期中

//获取状态栏高度
wx.getSystemInfo({
  success(res){
    _that.globalData.sysBarHeight = res.statusBarHeight;
    console.log(res.statusBarHeight);
  },
  fail(err){
    console.log('系统信息错误',err);
  }
});

//获取胶囊按钮的空间信息
//基础库 2.1.0 开始支持
if(_that.compareVersion(version,'2.1.0')){
  var ButtonBoundingRes = wx.getMenuButtonBoundingClientRect();
  if(ButtonBoundingRes){
    _that.globalData.ButtonBoundingHright = (ButtonBoundingRes.top - _statusBarHeight)*2 + ButtonBoundingRes.height
  };
}else{
  wx.showModal({
    title: '提示',
    content: '当前微信版本过低,无法获取胶囊按钮尺寸功能,请升级到最新微信版本后重试。'
  });
};
截图说明截图

通过上面方式计算结果 自定义导航栏的高度应该为

statusBarHeight 下面的胶囊实际占位 最终自定义导航高度
20 (24-20)*2+32 60
20 + (24-20)*2+32 = 60

上面计算的单位为px,如果在页面内使用,需要使用rpx,因为px是固定值,不同手机设备高度不一,可能引起适配问题:
在app.js的onLaunch生命周期内,获取当前设备的px与rpx比例,将px转化为rpx,在页面内使用更精准些

var _windowWidth = wx.getSystemInfoSync().windowWidth
var _rate = 750 / _windowWidth
_that.globalData.rateRpx = _rate;
  1. 小程序组件lifetimes 可以补充onload、onshow的生命周期
    js
lifetimes: {
  attached: function() {
    // 在组件实例进入页面节点树时执行
    ...
  },
  detached: function() {
    // 在组件实例被从页面节点树移除时执行
    ...
  },
},
  1. 通过分享进来,没有返回主页入口的困局
    问题:
    如果分享页面是详细页面,通过分享点击进来,无法通过常规后退按钮返回,因此需要一个返回首页的按钮
    但是需要一个判断的条件,即 如何判断此时页面是否为分享点击进来的。
    方案:
    在app.js内,可以在App的onLaunch和onShow,或wx.getLaunchOptionsSync中获取上述场景值,
    来获取 scene (小程序的场景值)
    官方的场景值对应表 - https://developers.weixin.qq.com/miniprogram/dev/reference/scene-list.html
    通过将获取的scene来判断是否显示一个返回主页的入口,与表格对比可知,
1007  --  是分享给朋友,朋友通过链接点击过来的
1008  --  群聊会话中的小程序消息卡片

app.js

App({
  onLaunch(option){
    //可以查看scene值
    if(option.scene==1007 || option.scene==1008){
      _that.globalData.formShare = true;
    };
    ...

pages/xx/xx.wxml内

<!-- 后退 -->
<view 
    wx:if="{{!homeBackBtn}}"
    bindtap="backFn" 
    class="backIcon"
>
    <view class="icoInset iconTag backIco"></view>
</view>
<!-- 返回首页 -->
<view 
    wx:else
    bindtap="backHomeFn" 
    class="backIcon"
>
    <view class="icoInset iconTag homeIco"></view>
</view>

pages/xx/xx.js内

onLoad:function(option){
  //是否显示返回主页按钮
    if(app.globalData.formShare){
      _that.setData({
        'homeBackBtn': true
      })
    };
},
backHomeFn(){
    //重置状态
    app.globalData.formShare = false;
    this.setData({
      'homeBackBtn': false
    });
    //完成跳转
    app.returnHomeFn();
}
  1. wx.navigateTo 与 wx.redirectTo 的使用差异
    不同点:
    wx.navigateTo 用于页面流程为串行的操作,最多纪录10层历史路径,可以通过wx.navigateBack进行后退,
    如果超过10层,会进行报错。
navigateTo webview cont limit exceed

wx.redirectTo 可以用来代替上页面跳转的问题,但是无法使用历史路径。
相同点:
都不能跳到 tabbar 页面

  1. 复杂数据的修改,通过字符串拼接好数据,包裹上[ ]来进行解析
    推荐的方式:
var _nowIndex = this.data.nowIndex;
this.setData({
    ['listsData['+_nowIndex+'].sonIndex']: 新的数据
});

var _dataStr = "mapDatas.tabLists["+_mapDatas.tabIndex+"].sonLists";
 _that.setData({
    [_dataStr]: _tempArr
});

引起问题的方式:

var _sonIndex = ev.detail.current;
var _listsData = this.data.listsData;
var _nowIndex = this.data.nowIndex;

_listsData[_nowIndex].sonIndex = _sonIndex;
this.setData({
  'listsData': _listsData
});

出现的具体问题是:

  • listsData下某个无需修改数据,重新覆盖,会引起undefined的渲染错误,比如地图组件的经纬度;
  • 此外对局部的数据修改,是被建议的
  • 渲染大小的建议
由于小程序运行逻辑线程与渲染线程之上,setData的调用会把数据从逻辑层传到渲染层,数据太大会增加通信时间。
**得分条件:`setData`的数据在`JSON.stringify`后不超过 256KB**
  1. scroll-view组件内,存在子元素,包裹一个swiper组件和同级增加一个浮层,滚动scroll-view时候,浮层会消失
    解决方法:
    将浮层从swiper同级提取到更高一层,可以解决问题。

  2. wx.navigateTo跳转,会出现重复跳转
    原因:如 wx.navigateTo、wx.redirectTo跳转官方没有做节流
    具体操作:

//修复BUG、节流timer
var navigaterTimer = null;
var redirectTimer = null;

App({
onLaunch(option){
var _that = this;
  ...
//跳转 wx.navigateTo 小程序中页面栈最多十层
jumpLink(_link,sucFn,errFn){
clearTimeout(navigaterTimer);
navigaterTimer = setTimeout(function(){
  wx.navigateTo({
    url: _link,
    success(res){
      sucFn && sucFn(res);
    },
    fail(err){
      errFn && errFn(err);
    }
  })
},600);
},
//跳转
jumpLink2(_link,sucFn,errFn){
clearTimeout(redirectTimer);
redirectTimer = setTimeout(function(){
  wx.redirectTo({
    url: _link,
    success(res){
      sucFn && sucFn(res);
    },
    fail(err){
      errFn && errFn(err);
    }
  });
},500);
},

注:延迟时间越久,修复效果越好,如果延迟时间设置300ms,在模拟器上仍可能出现重复跳转的情况,理解可能是设备性能来不及反应?

  1. text标签内如果要添加长文本,为了方便用户浏览时copy可以,为text添加user-select属性,
    此外,首行缩进(text-indent:2em;)不能添加到父级标签,否则会溢出屏幕

  2. 针对富文本,处理带有HTML标签的文案

<rich-text nodes="{{itemEle.backMes.detmes}}" user-select></rich-text>
  1. 获取rpx与px的转化比例值
    已知:小程序规定屏幕宽为750rpx,这个无关机型(rpx(responsive pixel): 可以根据屏幕宽度进行自适应)
    未知:当前设备的宽度
let windowWidth = wx.getSystemInfoSync().windowWidth
let rate = 750 / windowWidth
  1. 小程序加载分页的格式


    小程序分页流程图

22-2. 分页代码格式
js:

'commentLists':{
  'list':[],
  'loaded': false,
  'isLoading': false, 
  'noMore': false,
  'totalcount':0,
  'pageindex': 0
}

wxml:

<!--加载完毕-->
<view 
    class="commentLists"
    wx:if="{{isLoaded}}"
>
    <!-- 有数据 -->
    <view
        wx:if="{{commentLists.list.length>0}}"
    >
        <!--数据列表-->
        <view class="listBox">
            <!-- ele -->
            <view 
                class="ele"
                wx:for="{{commentLists.list}}" 
                wx:for-index="idx" 
                wx:for-item="itemEle"
                wx:key="idx"
            >
                <view class="phootBox borderR">
                    <image src="{{itemEle.userPhoto}}" mode="aspectFill" />
                </view>
                <view class="say">
                    <text>{{itemEle.mes}}</text>
                </view>
            </view>
        </view>
        <!-- 暂无更多数据 -->
        <view wx:if="{{noMore}}" class="noDataTxt">
            <text>暂无更多评论</text>
        </view> 
    </view>
    <!-- 无数据 -->
    <view wx:if="{{commentLists.noMore}}">
        <view class="noDataTxt">
            <text>暂无任何数据</text>
        </view> 
    </view>
</view>
<!--加载中-->
<view wx:else>
    <view class="noDataTxt">
        <text>加载中...</text>
    </view> 
</view>
  1. 小程序跳转第三方小程序中某个页面
    具体流程:
  • 配置app.json ---(已经废弃)
    小程序限定不超过10个,否则将无法通过审核。
    此名单可在发布新版时更新,不支持动态修改。
"navigateToMiniProgramAppIdList": [
    "wx91d27dbf599dff74",
    "wx0e6ed4f51db9d078"
]

备注:
从2020年4月24日起,使用跳转其他小程序功能将无需在全局配置中声明跳转名单 ,跳转其他小程序将不再受数量限制

  • 获取跳转小程序的appid
    微信 - 搜索小程序 京东 - 打开小程序 - 右上角三个点 - 点击浮层内小程序名称 - 更多资料 - 点击即可查看AppID
  • 查看该商品是否可以获取详情地址,
    打开要跳转的小程序目标页面,右上角三个点,发送给朋友,
    如果点击这个链接可以达到目前链接,则继续后续步骤。
    如果仅能跳转至目标小程序的首页,那么说明无法获取商品详情地址,则可以停止后续流程了
  • 打开微信公众平台,登录账号后,顶部菜单 工具-生成小程序码,将上面获取的appid输入,搜索小程序;
    然后将自己的小程序账号放进去,点击"开启"即可显示复制链接按钮;
    【注】: 这个"开启"按钮有一个有效时间,过了时间就不显示复制链接按钮了,需要再次这一步开启


    配置微信的截图

    上面开启有时间限制,如果过了一段时间,跳转的小程序内,点击右上角三个点,弹出层内可能会不显示"复制链接"

  • 编辑链接地址
    点击这个分享的图文框,再点击右上角的三个点,浮层内,点击复制链接;
    将复制的链接,在记事本内编辑,删除.html与id参数之后的内容;
    例如:
pages/gs/sight/newDetail.html?sightId=25603&mktshare=eyJhbGxpYW2IjNyojIklWZj5ODQsInNpZCI6NzExNDY1LCJvdWlkIjoiIiwic291cmNlaWQiOjU1NTUyNjg5LCJmcm9tYWxsaWFuY2VpZCI6MCwiZnJvbXNpZCI6MCwiZnJvbW91aWQiOiIiLCJmcm9tc291cmNlaWQiOjAsImZyb21vcGVuaWQiOiIyMTVhZGVhZi1lMmRiLTQ3YWUtODRiZC1hMWU2ZWViMWNlMTAifQ()()KY&allianceid=262684&sid=711465&ouid=&sourceid=55552689

处理链接为:

pages/gs/sight/newDetail?sightId=25603

wxml标签内使用,方式为:

<navigator 
    target="miniProgram" 
    open-type="navigate" 
    app-id="{{pageDetail.appid}}" 
    path="{{pageDetail.path}}" 
    extra-data="" 
    version="release"
>查看详情</navigator>
  1. pinker针对数组包裹对象的形式,需要处理一份用于展示的pinker数据
  • 原始数据:
deptData:{
  indexnum:0,
  list: [
    { id:1, name:'服装艺术与工程学院' },
    { id:2, name:'材料设计与工程学院' }
  ]
}
  • 处理后数据:
deptData:{
  indexnum:0,
  list: [
    { id:1, name:'服装艺术与工程学院' },
    { id:2, name:'材料设计与工程学院' }
  ],
  pinkerlist: [
    '服装艺术与工程学院','材料设计与工程学院'
  ]
}

结构:

--wxml--
<picker 
    class="pickerBox"
    bindchange="changeDeptFn" 
    range="{{deptData.pinkerlist}}"
    value="{{deptData.indexnum}}" 
>
    <view class="mes">
        <text class="tit">选择学院:</text>
        <text class="pinkmes">{{deptData.pinkerlist[deptData.indexnum]}}</text>
    </view>
</picker>

--js--
changeDeptFn(ev){
    var _that = this;
    var _indexnum = ev.detail.value;
    _that.setData({
      ['deptData.indexnum']: _indexnum
    });
    //根据学院,加载专业
    _that.getSpecDataFn();
}
  1. 组件的使用
    组件目录位置:同pages相同目录的components文件夹
  • 设置组件
    配置组件目录下的 .json文件
{
  "usingComponents": {},
  "component": true
}
  • 使用组件
    配置组件目录下的 .json文件
{
  "usingComponents": {
    "workDetail": "/components/workdetail/index"
  }
}

引入组件

<workDetail
    userCollect="{{userCollect.data}}"
    userData="{{userData}}"
    worksid="{{worksid}}"
    bind:addCollectFn="addCollectFn"
    bind:delCollectFn="delCollectFn"
></workDetail>

25-1. 组件内通信 官方链接 - https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/events.html

  • 父组件 -> 子组件
    通过挂载属性的方式实现,数据挂载
//--wxml
<workDetail
  bind:addCollectFn="addCollectFn"
></workDetail>
//--js
addCollectFn(e){
  console.log(e.detail);
}
  • 子组件 -> 父组件
//--js
this.triggerEvent('addCollectFn', _addObj);

25-2. 父组件使用子组件内的方法

//--wxml
<workDetail id="workDetailCmp"></workDetail>
//--js
onReady(){
  this.selectComponent('#workDetailCmp').fn1();
}
  1. 通过option设置数值,并将数据挂载至组件上,子组件内通过properties获取数据,
    在子组件的生命周期 lifetimes 的attached内调用,存在延迟问题
  • 问题产生的原因:
    lifetimes 生命周期 - https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/lifetimes.html 内,attached调用,父组件挂载的数据还没过来,等父组件的数据更新后,attached内方法不会再执行
lifetimes: {
    attached: function() {
      // 在组件实例进入页面节点树时执行
      //初始化菜单索引位置
      this.getProductDetFn();
    },
    detached: function(){
      // 在组件实例被从页面节点树移除时执行
      
    }
}
  • 解决:
    挂载的方法,可以对数据设置数据开关,等数据加载完毕后,打开开关进行组件数据挂载
    如:
'userCollect': {
  'data': [],
  'loaded': false
}
<view wx:if="{{worksid && userCollect.loaded}}">
    <workDetail
        userCollect="{{userCollect.data}}"
        userData="{{userData}}"
        worksid="{{worksid}}"
        bind:addCollectFn="addCollectFn"
        bind:delCollectFn="delCollectFn"
    ></workDetail>
</view>
  1. 设置数据 setData 中的问题
  • 设置数据时候,如果下面书写会报错
data:{
   'userCollect': {
      'data': [],
      'loaded': false
    }
},
fn1(e){
        //错误
    //var _userCollect = _that.data.userCollect;
    //_userCollect.data.push(_tempAddObj);
    //正确
    var _userCollect = _that.data.userCollect.data;
    _userCollect.push(_tempAddObj);
    _that.setData({
    ['userCollect.data']: _userCollect
    });
}
  1. webview相关
  • 创建小程序,使用企业身份申请,完成转账等身份验证
  • 配置开发管理-开发设置-业务域名,下载验证文件,上传至业务域名服务器的根下
  • wxml添加 <web-view src="业务域名的H5文件"></web-view>
  • 查看 官方文档 - https://developers.weixin.qq.com/miniprogram/dev/component/web-view.html, 下载 JSSDK 1.3.2 - https://res.wx.qq.com/open/js/jweixin-1.3.2.js,以便在H5页面内使用小程序的API(如返回路由)
  1. 点击事件,阻止冒泡之 catchtap
  • bindtap是冒泡的
  • catchtap是非冒泡的
  1. 监听用户上拉触底事件
  • 可以在app.json的window选项中或页面配置中设置触发距离onReachBottomDistance。
  • 在触发距离内滑动期间,本事件只会被触发一次。
  • onReachBottom生命周期内处理事件
  1. 地图API、组件
  • 地图组件 <map>
<map
    id="myMap"
    latitude="{{mapDatas.tabLists[mapDatas.tabIndex].wxMapDataList[mapDatas.sonindex].latitude}}"
    longitude="{{mapDatas.tabLists[mapDatas.tabIndex].wxMapDataList[mapDatas.sonindex].longitude}}"
    markers="{{mapDatas.tabLists[mapDatas.tabIndex].wxMapDataList}}"
    show-location
    show-compass
    show-scale
    bindmarkertap="bindmarkerFn"
></map>
  • 地图属性
latitude -- 地图中心纬度 (-90 ~ 90) - 【特别注意都是数字类型】
longitude -- 地图中心经度 (-180 ~ 180) - 【特别注意都是数字类型】
  • 地图遮盖物点击事件:bindmarkertap
  • 地图获取上下文方法: wx.createMapContext(string mapId, Object this) -- 版本要求:1.9.6
  • 地图移动地图中心点的方法
_that.mapCtx.moveToLocation({
    longitude: Number(新的中心点经度),
    latitude: Number(新的中心点纬度),
    success(){},
    fail(err){
      console.log(err);
    }
});
  • 地理位置授权 app.json
"permission": {
    "scope.userLocation": {
      "desc": "你的位置信息将用于小程序位置接口的效果展示"
    }
  },
  1. 监听组件上的参数变化
//--wxml
<workDetail
    userCollect="{{userCollect.data}}"
    userData="{{userData}}"
    worksid="{{worksid}}"
    onlyRead="{{onlyRead}}"
    bind:addCollectFn="addCollectFn"
    bind:delCollectFn="delCollectFn"
></workDetail>

//--js
lifetimes:{
...
},
observers:{
//基础库版本 2.6.1 开始支持
'worksid': function(newV) {
  var version = wx.getSystemInfoSync().SDKVersion;
  var _btn = app.compareVersion(version,'2.6.1');
  if(_btn){
    // console.log(newV);
    //加载作品详情
    this.getProductDetFn();
  }else{
    wx.showModal({
      title: '提示',
      content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。'
    });
  };
}
}
  1. 页面渲染截断的情况
  • 产生原因:
    setData接口的调用涉及逻辑层与渲染层间的线程通信,通信过于频繁可能导致处理队列阻塞,界面渲染不及时而导致卡顿,应避免无用的频繁调用。
  • 解决方案:
    每秒调用setData的次数不超过 20 次
  1. 小程序页面性能监测 官方地址 - https://developers.weixin.qq.com/miniprogram/dev/framework/audits/best-practice.html
    小程序开发工具 在调试器区域切换到 Audits 面板,
    点击”开始“按钮,然后自行操作小程序界面,运行过的页面就会被“体验评分”检测到。

    性能分析工具截图

  2. 小程序获取用户信息 新要求
    推荐使用 wx.getUserProfile ,不推荐使用getUserInfo
    具体范例:

  • 不推荐使用getUserInfo获取用户信息,预计自2021年4月13日起,getUserInfo将不再弹出弹窗,并直接返回匿名的用户个人信息
    如下:
<button wx:else open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button>
  • 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认
    //--wxml
<button wx:if="{{canIUseGetUserProfile}}" bindtap="getUserProfile"> 获取头像昵称 </button>

//--js

onLoad() {
    if (wx.getUserProfile) {
      this.setData({
        canIUseGetUserProfile: true
      })
    }
},
getUserProfile(e) {
    // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认
    // 开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
    wx.getUserProfile({
      desc: '用于完善会员资料', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
      success: (res) => {
        this.setData({
          userInfo: res.userInfo,
          hasUserInfo: true
        })
      }
    })
},
  1. 一个两列的瀑布流如何实现,(支持分页)
  • 原理:
    第一步,将一个数据列表,拆为两个数据列表,并定义两个列表的初始高度
    第二步,每次请求后台获取过来的数据,对比一下两列累积高度,哪一侧小,就优先将数据插入至哪一侧,并将对应侧的高度累加
    第三步,将更新后的两列数据以及累加后的两个高度值,返回
    第四步,与后台接口开发人员,提前告知返回图片宽、高 字段(如例子中的ImgWidth,ImgHeight),用于计算列表高度使用

注:
第一,由于列表高度考量因素是图片高度,如果列表单元,是一个图文配,那么需要将文字部分设置统一高度,文字溢出省略号,才可以(单行省略号或多行省略号)
目的还是将判断哪一列插入数据的考量,缩小为图片高度一个判断依据
第二,瀑布流中两列的宽度必须一致(如列中:375屏宽下,单列162rpx宽),否则两列宽度不统一,通过图片(widthFix模式)获取的等比高度,将不能用于判断

  • 数据结构:
'mainNews':{
      'loaded': false,
      'isLoading': false,
      'noMore': false,
      'totalcount':0,
      'pageindex': 0,
      /*变量名代表:左侧数据列表,右侧数据列表,左侧列表累积高度,右侧列表累积高度*/
      'listL': [],
      'listR': [],
      'leftNowH':0,
      'rightNowH':0
},
  • 全局app.js内,工具函数
/*瀑布流排序 - 计算高度,重新排序
* 如果存在分页 
* 传入值: datalist- 待处理数据 | oldLeftH- 旧的左侧高度 | oldRightH- 旧的右侧高度
* 输出值: leftArr- 左侧数据 | rightArr- 右侧数据 | leftNowH - 左侧累积值 | rightNowH - 右侧累计值
*/
sortListFn(datalist,oldLeftH,oldRightH){
  var _leftH = oldLeftH || 0;
  var _rightH = oldRightH || 0;
  var _leftArr = [];
  var _rightArr = [];

  datalist.forEach(function(item,index){
    //162(rpx)为一个页面布局宽度,计算值为布局宽度内的图片高度,仅比大小,不用那么精细
    var _layoutH = Math.ceil((item.ImgHeight*162)/item.ImgWidth);
    if(_leftH>_rightH){
      _rightArr.push(datalist[index]);
      _rightH+=_layoutH;
    }else{
      _leftArr.push(datalist[index]);
      _leftH+=_layoutH;
    };
  });
  //每次更新后的,两侧累积高度:
  //console.log(_leftH,_rightH);
  return {
    leftArr: _leftArr,
    rightArr: _rightArr,
    leftNowH: _leftH,
    rightNowH: _rightH
  };
}
  • 如何使用工具函数
... 某事件回调内
var _that = this;
_that.getDataFn(function(res){
    var _mainNews = _that.data.mainNews;
    var _sortRes = app.sortListFn(res.data.list,_mainNews.leftNowH,_mainNews.rightNowH);
    var _listL = _mainNews.listL.concat(_sortRes.leftArr);
    var _listR = _mainNews.listR.concat(_sortRes.rightArr);
    //有无更多数据
    var _totalcount = res.data.totalcount;
    //设置数据
    _that.setData({
        ["mainNews.listL"]: _listL,
        ["mainNews.listR"]: _listR,
        ["mainNews.leftNowH"]: _sortRes.leftNowH,
        ["mainNews.rightNowH"]: _sortRes.rightNowH,
        ["mainNews.loaded"]: true,
        ["mainNews.totalcount"]: _totalcount
    });
    //有无更多数据
    if((_listL.length+_listR.length) == _totalcount){
      _that.setData({
        ["mainNews.noMore"]: true
      });
    }else{
      _that.setData({
        ["mainNews.noMore"]: false
      });
    };
},function(err){
    console.log(err);
});
...
  1. 通过微信生成小程序二维码,然后扫码进入小程序,并传递参数的实现
    后端:
{
 "page": "pages/index/index",
 "scene": "a=1",
 "check_path": true,
 "env_version": "release"
} 

注意:

  • 开发环境要对应上 env_version 正式版为 "release",体验版为 "trial",开发版为 "develop"。默认是正式版。
  • 踩坑,不要在连接地址上使用下划线'_',否则会获取不到参数,如:'pages/index/index',不能写成 'pages/abc_index/index'

前端:
在扫码对应的页面内,onload声明周期中获取参数,参数在scene中,这个scene不是app.js的onlunch的场景值,而是一个扫码进入某个页面的参数载体

Page({
  onLoad (query) {
    // scene 需要使用 decodeURIComponent 才能获取到生成二维码时传入的 scene
    const scene = decodeURIComponent(query.scene)
  }
})

此外,调试的时候,可以通过开发者工具,扫描为了开发板生成的二维码,实现本地调试功能


本地开发环境扫码调试
  1. 小程序内调用扫一扫功能
wx.scanCode({
  // 只允许从相机扫码
  onlyFromCamera: true,
  // 扫码类型 - 二维码
  scanType: qrCode,
  success (res.path) {
    var _resPath = decodeURIComponent(res.path);
    console.log(_resPath );
  }
})

开发者工具,通过上面API,可以直接上传扫码的图片,完成扫码,数据格式如下:


扫一扫返回数据格式

占坑....

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容