2019-02-22 微信小程序·开发指南

第一章 小程序介绍与开发环境

一. Hello World
// app.json 
"pages": [
    "pages/index/index"
  ]

// index/index.wxml
<text>Hello World</text>
二. 小程序介绍
1.小程序技术发展史

(1).移动web在微信的WebView中显示,微信有相关JS API ,但只对团队内部使用
(2). 网页开发工具包 JS-SDK,对外开放,缺点:体验不好,资源加载白屏/缺少操纵反馈。
(3). 小程序应运而生。

2.小程序与普通网页开发的区别

(1).网页开发中渲染线程与脚本线程是互斥的。小程序逻辑层与渲染层是分开的,可以并行的。
(2).小程序逻辑层没有一个完整浏览器对象,因而缺少相关的DOM API和BOM API。同时JSCore 的环境同 NodeJS 环境不尽相同。
(3).网页开发者需要面对的环境是各式各样的浏览器,小程序要面对的是两大操作系统 iOS 和 Android 的微信客户端,以及用于辅助开发的小程序开发者工具。


小程序运行环境
三. 小程序开发准备

在微信公众平台注册一个小程序,设置”—“开发设置” 就可以看到小程序的 AppID。
安装开发者工具
在开发者工具中创建项目 填入AppID 开始项目。

第二章 小程序代码组成

一. JSON 配置

1.在小程序中,JSON扮演的静态配置的角色。
2.小程序是无法在运行过程中去动态更新JSON 配置文件从而发生对应的变化的。
JSON 语法:
1.JSON的Key必须包裹在一个双引号中
2.JSON的值数据格式:
1.数字 包括浮点数和整数
2.字符串
3.Bool: true or false
4.数组:包裹在[]中
5.对象:包裹在{}中
6.null
其他任何格式都会触发报错
3.JSON 文件中无法使用注释,试图添加注释将会引发报错。

二.WXML模板
1.基本介绍

1.全称:WeiXin Markup Language
2.基本语法:

<!-- 在此处注释 —>
<text 属性名=“属性值”></text>
2.数据绑定

1.WXML中的属性是大小写敏感的
2.数据绑定中 变量也是大小写敏感的
3.属性值可以绑定,有所不同的是,属性值必须被包裹在引号中

<!-- 数据绑定 -->
// index.wxml
<view>当前时间:{{time}}</view>
<!-- 绑定属性值 -->
<view data-test="{{test}}">hello world</view>
<view>{{var1}}</view>
<view>{{var2}}</view>

// index.js
 data: {
    time: (new Date()).toString(),
    var2: undefined,
    var3: null
}
3.逻辑语法
<!-- 逻辑运算 -->
// index.js
data: {
    a: 10,
    b: 2,
    c: 4,
    d: 6,
    name: 'world',
    arr: ['hei',2,3]
}
// index.wxml
<view>{{a === 10?"变量a等于10":"变量a不等于10"}}</view>   //变量a等于10
<view>{{a + b}} + {{c}} + {{d}}</view>  // 12 + 4 + 6
<view>{{'hello ' + name }}</view>   // hello world
<view>hello {{name}}</view>   // hello world
<view>{{arr}}</view>  // hei,2,3
<view>{{1}}</view>   // 1
<view>{{'hhahahha'}}</view> // hhahahha
4. 条件逻辑
<!-- 条件逻辑 -->
// index.js
 arr: ['hei',2,3]
// index.wxml
<view wx:if="{{arr.length > 5}}">1</view>
<view wx:elif="{{length > 3}}">2</view>
<view wx:else>3</view>
5. 列表渲染
// index.js
data: {
list: [
      {
        message: 'foo',
      },
      {
        message: 'bar',
      }
    ],
    objArr: [
      { id: 5, unique: 'unique5' },
      { id: 4, unique: 'unique4' },
      { id: 3, unique: 'unique3' },
      { id: 2, unique: 'unique2' },
      { id: 1, unique: 'unique1' },
      { id: 0, unique: 'unique0' },
    ],
    numArr: [1, 2, 3, 4]
}switch: function(e){
    const length = this.data.objArr.length
    for(var i = 0;i < length; i++) {
      const x = Math.floor(Math.random() * length);
      const y = Math.floor(Math.random() * length);
      const temp = this.data.objArr[x];
      this.data.objArr[x] = this.data.objArr[y];
      this.data.objArr[y] = temp;
    }
    this.setData({
      objArr: this.data.objArr
    })
  },
  addToFront: function(e){
    const length = this.data.objArr.length
    this.data.objArr = [{id: length, unique: 'unique_' + length}].concat(this.data.objArr);
    this.setData({
      objArr: this.data.objArr
    })
  },
  addNumToFront: function(e){
    this.data.numArr = [this.data.numArr.length + 1].concat(this.data.numArr)
    this.setData({
      numArr: this.data.numArr
    })
  }

// index.wxml
<view wx:for='{{list}}'>
  {{index + 1}}: {{item.message}}
</view>

<!-- 列表渲染 指定当前元素的变量名 及其对应的下标 -->
<view wx:for="{{list}}" wx:for-index='idx' wx:for-item='itemName'>
  {{idx + 1}}: {{itemName.message}}
</view>
<!-- 列表渲染 指定唯一标识符 -->
<switch wx:for="{{objArr}}" wx:key="unique">{{item.id}}</switch>
<button bindtap="switch">switch</button>
<button bindtap="addToFront">addToFront</button>

<switch wx:for="{{numArr}}" wx:key="*this">{{item}}</switch>
<button bindtap="addNumToFront">addNumToFront</button>

使用wx:key指定列表渲染中的唯一标识符,key值两个条件:
(1)字符串,代表在 for 循环的 array 中 item 的某个 property,该 property 的值需要是列表中唯一的字符串或数字,且不能动态改变。
(2) 保留关键字 this 代表在 for 循环中的 item 本身,这种表示需要 item 本身是一个唯一的字符串或者数字
好处: 当数据改变触发渲染层重新渲染的时候,会校正带有 key 的组件,框架会确保他们被重新排序,而不是重新创建,以确保使组件 保持自身的状态,并且提高列表渲染时的效率。

4. 模版

模板中渲染的所有数据是传入对象的属性的值

<!-- 模板 -->
<template name="msg">
  <view>
    <text>{{id}}: {{unique}}</text>
  </view>
</template>
<view wx:for="{{objArr}}" wx:for-item="item" wx:for-index="index">
  <template is="msg" data="{{...item}}"></template>
</view>
<template name="even">
  <view>even</view>
</template>  
<block wx:for="{{[1, 2, 3, 4, 5]}}" wx:for-item="item">
  <template is="{{item%2 == 0?'even': ''}}" data="{{...item}}"></template>  
</block> 
5. 引用
// b.wxml
<template name='B'>
  <text>B template</text>
</template>
// 同目录下index.wxml
<import src='b.wxml' />
<template is="B"/>  
// header.wxml
<view>header</view>
// footer.wxml
<view>footer</view>
<!-- 引用 include -->
<include src='header.wxml'/>
<view>body</view>
<include src='footer.wxml'/> 
6. 共同属性
共同属性
三. javascript 脚本组成
1.小程序javascript组成
屏幕快照 2019-02-22 下午8.56.42.png
注意:

(1)没有BOM与DOM,无法使用Jquery、zepto等浏览器类库
(2) Native 模块和NPM包管理的机制,无法加载原生库与大部分NPM包

2.小程序的执行环境

1.小程序目前可以运行在三大平台:
(1).iOS平台,包括iOS9、iOS10、iOS11
(2).Android平台
(3).小程序IDE
原因:三大平台实现的 ECMAScript 的标准有所不同。

2.iOS9和iOS10 所使用的运行环境并没有完全的兼容到 ECMAScript 6 标准。
小程序解决方案:小程序IDE在‘右上角详情’中提供转码工具,勾选 ES6 转 ES5 后,可实现转码。

3.模块化

1.小程序中将所有javascript文件作为一个模块处理,通过module.exports 或者 exports 对外暴露接口。
2.引用模块的方法时,可采用require的方式

var aBy2 = require('./a.js');
var result = aBy2(5);
console.log(result);

3.小程序脚本执行顺序:
(1)入口文件app.js
(2)app.js中引入的模块
(3)app.js中pages属性定义的js文件顺序:

"pages": [
    "pages/wxml/index",
    "pages/index/index"
  ],

4.作用域
(1)不同文件声明的变量以及方法只在本文件有效
(2)可以通过getApp()全局方法设置全局性的属性以及方法

第三章 理解小程序宿主环境

不同版本的微信客户端提供的宿主环境版本不同,所以小程序需要做兼容!!

一.渲染层和逻辑层

小程序的运行环境分成渲染层和逻辑层,这是其运行环境的基本工作模式。

1.渲染“Hello World”页面

hello world案例:

wxml:
<view>{{ msg }}</view>
js:
Page({
  onLoad: function () {
    this.setData({ msg: 'Hello World' })
  }
})
说明:1.渲染层和数据相关。
2.逻辑层负责产生、处理数据。
3.逻辑层通过 Page 实例的 setData 方法传递数据到渲染层。

所以接下类讲述第三点描述的通讯模型。

2.通信模型

小程序的逻辑层与渲染层由2个线程管理,渲染层使用webview线程渲染,逻辑层采用JsCore线程运行脚本。一个小程序有多个界面,所以存在多个webview线程。下图为通讯模型:


小程序通讯模型

图中Native指‘微信客户端’,渲染层与逻辑通通过微信客户端做中转进行通讯。

3.数据驱动

数据驱动:将数据与状态相关联,当数据更新时,自动更新视图。
(1)Hello World 数据渲染示意图:


数据渲染

(2)更改数据后,视图更新流程示意图:


数据更新
4.双线程下的界面渲染

(1)wxml转化为对应的js对象
(2)逻辑层数据更新后,通过setData方法把数据传递到渲染层,
(3)对比前后js对象差异,更新修改的部分。


界面渲染过程
二.程序与页面
1.程序

注意: ‘小程序’指的是产品层面的程序,‘程序’指的是代码层面的程序实例。
程序构造器App():

// app.js
App({
  onLaunch: function (options) { },
  onShow: function (options) { },
  onHide: function () { },
  onError: function (msg) { },
  globalData: 'I am global data'
})
App构造器的参数

注意: App的生命周期是由微信客户端根据用户操作主动触发的。为了避免程序上的混乱,我们不应该从其他代码里主动调用App实例的生命周期函数。

上述方法中onLaunch、onShow带参数:


参数内容

以下场景支持返回referrerInfo.appId:


支持返回referrerInfo.appId

构造器中的其他数据为全局性的数据,其他页面可通过getApp()方法获取到。

2. 页面:

1.文件构成与路径:
页面分三部分: wxml、wxss负责界面;json负责配置;js负责逻辑。
页面路径在小程序根目录app.json中声明,第一个声明的是首页。

{
 "pages":[
   "pages/index/page", // 第一项默认为首页
   "pages/other/other"
 ]
}

2.页面构造器Page():

  data: { text: "This is page data." },
  onLoad: function(options) { },
  onReady: function() { },
  onShow: function() { },
  onHide: function() { },
  onUnload: function() { },
  onPullDownRefresh: function() { },
  onReachBottom: function() { },
  onShareAppMessage: function () { },
  onPageScroll: function() { }
})

其中,data为数据、前四个方法为声明周期函数、后面四个为页面的用户行为。


Page构造器的参数

当使用wx.navigateTo切换到其它页面、底部Tab切换时触发onHide方法;
当使用wx.redirectTo或者wx.navigateBack返回其他页面时,页面被销毁调用onUnload方法。

Page构造器传参实例:

// pages/list/list.js
// 列表页使用navigateTo跳转到详情页
wx.navigateTo({ url: 'pages/detail/detail?id=1&other=abc' })

// pages/detail/detail.js
Page({
  onLoad: function(option) {
        console.log(option.id)
        console.log(option.other)
  }
})

注意:页面URL上的value如果涉及特殊字符(例如:&字符、?字符、中文字符等,详情参考URI的RFC3986说明 ),需要采用UrlEncode后再拼接到页面URL上。

4.页面的数据:
(1)data参数是页面第一次渲染时从逻辑层传递到渲染层的数据。

<!-- page.wxml -->
<view>{{text}}</view>
<view>{{array[0].msg}}</view>

// page.js
Page({
  data: {
    text: 'init data',
    array: [{msg: '1'}, {msg: '2'}]
  }
})

(2)使用setData函数传递数据,传递过程中渲染层、逻辑层在两个线程中运行,故该过程是异步的。setData方法中有callback回调,在渲染完成后触发。

// page.js
Page({
  onLoad: function(){
    this.setData({
      text: 'change data'
    }, function(){
      // 在这次setData对界面渲染完毕后触发
    })
  }
})

(3)在通过setData方法重设字段时,不必把所有字段重设,只需把改变的值重设,原则是每次只设置需要改变的最小单位数据。
注意:
必须通过this.setData设置数据
每次设置的数据不应超过1024kb
不要把任意一项value设置为undefined,

  1. 页面的用户行为:
    (1) 监听用户下拉刷新事件 onPullDownRefresh
    需在app.json的window选项中或者页面配置page.json中设置enablePullDownRefresh为true,
    处理完数据刷新后,可使用wx.stopPullDownRefresh停止页面下拉刷新。
    (2)监听用户上拉触底 onReachBottom
    可在app.json的window选项中或者页面配置page.json中设置触发距离onReachBottomDistance,在触发距离内滑动,该事件只触发一次。
    (3)监听用户滑动页面事件 onPageScroll
    参数为object,包含scrollTop字段,表示页面在垂直方向已经滚动的距离(单位:px)
    (4) 用户转发 onShareAppMessage:
    定义此事件后,右上角显示转发按钮,该事件需要返回一个object,包含 title、path
// page.js
Page({
onShareAppMessage: function () {
 return {
   title: '自定义转发标题',
   path: '/page/user?id=123'
 }
}
})
  1. 页面跳转和路由
    (1) 页面栈:例子:在多次使用(少于等于10)wx.navigateTo后,页面层级会有多层,行程的页面层级为页面栈。如下图:


    页面栈

为方便,使用[ pageA, pageB, pageC ]表述页面栈,其中pageA在最下面,pageC在最上面。
下面使用以下方法表述页面栈的变化:
wx.navigateTo({ url: 'pageD' }) 页面栈变为 [ pageA, pageB, pageC, pageD ];
wx.navigateBack() 页面栈变为 [ pageA, pageB, pageC ];
wx.redirectTo({ url: 'pageE' }) 页面栈变为 [ pageA, pageB, pageE ];
当页面栈到达10层没法再新增的时候,往往就是使用redirectTo这个API进行页面跳转。

小程序Tabbar 声明:

// app.json
{
  "tabBar": {
    "list": [
      { "text": "Tab1", "pagePath": "pageA" },
      { "text": "Tab1", "pagePath": "pageF" },
      { "text": "Tab1", "pagePath": "pageG" }
    ]
  }
}

接着上面的页面栈变化:
wx.switchTab({ url: 'pageF' }) 页面栈变为 [pageF ];(其中除pageA外,其它页面被销毁)。
wx. reLaunch({ url: 'pageH' })重启小程序,页面栈为 [ pageH ]
注意:wx.navigateTo和wx.redirectTo只能打开非TabBar页面,wx.switchTab只能打开Tabbar页面。

(2) 页面路由触发方式及页面生命周期函数的对应关系。


页面触发方式与声明周期函数

(3) Tab 切换对应的生命周期(以 A、B 页面为 Tabbar 页面,C 是从 A 页面打开的页面,D 页面是从 C 页面打开的页面为例) 注意Tabbar页面初始化之后不会被销毁。


页面路由触发方式及页面生命周期函数的对应关系
三.组件

1.小程序页面WXML的基本组成单位是:组件。

  1. 小程序的宿主环境提供了一系列基础组件。
    3.组件样式:
    (1) 通常包含开始标签和结束标签,该标签的属性用来描述该组件。
    (2) 所有组件名和属性都是小写,多个单词会以英文横杠 "-" 进行连接。
    (3) 所有组件的公共样式和事件绑定:


    组件公共样式、事件绑定

    (4) 组件都拥有各自自定义的私有属性,可以对该组件的功能或者样式进行修饰。以image为例:

 <image mode="scaleToFill" src="img.png"></image>

(5) 基础组件地址:
https://mp.weixin.qq.com/debug/wxadoc/dev/component/

四.API

1.定义:
以wx.navigate为例,wx是小程序宿主环境的全局对象。
所有小程序的API(构造器Page/App除外)都挂载在wx对象下。
API通指wx对象下的方法。
2.API按照功能分为网络、媒体、文件、数据缓存、位置、设备、界面、界面节点信息以及开放接口。
3.API一般调用的约定:
(1) wx.on开头的API是监听某个事件发生的API接口,接受一个callback回调,该事件触发时,回调。
(2) 多数API为异步接口、接受一个object参数
(3) API的Object参数一般由success、fail、complete三个回调来接收接口调用结果.

API接口参数回调

(4) wx.get
开头的API:获取宿主环境数据
(5). wx.set*开头的API:写入宿主环境数据

wx.request({
url: 'test.php',
data: {},
header: { 'content-type': 'application/json' },
success: function(res) {
 // 收到https服务成功后返回
 console.log(res.data)
},
fail: function() {
 // 发生网络错误等情况触发
},
complete: function() {
 // 成功或者失败后触发
}
})

详细了解API文档地址: https://mp.weixin.qq.com/debug/wxadoc/dev/api/

五.事件:
1.定义:

用户在渲染层的行为反馈或者组件的部分状态反馈(播放的进度条)。
渲染层传递给逻辑层的事件。


事件触发过程
2.事件类型与事件对象:

1.组件的部分状态反馈的事件可参考https://mp.weixin.qq.com/debug/wxadoc/dev/component/

2.常见的事件类型:


常见事件类型

以上事件都是冒泡事件,除此之外都不是。

3.事件触发后的事件对象属性:


事件对象属性

4.事件对象属性中target和currentTarget详情:


target和currentTarget

两者区别:currentTarget为当前事件所绑定的组件,而target则是触发该事件的源头组件

<!-- page.wxml -->
<view id="outer" catchtap="handleTap">
  <view id="inner">点击我</view>
</view>

// page.js
Page({
  handleTap: function(evt) {
       // 当点击inner节点时
    // evt.target 是inner view组件
       // evt.currentTarget 是绑定了handleTap的outer view组件
       // evt.type == “tap”
       // evt.timeStamp == 1542
       // evt.detail == {x: 270, y: 63}
       // evt.touches == [{identifier: 0, pageX: 270, pageY: 63, clientX: 270, clientY: 63}]
       // evt.changedTouches == [{identifier: 0, pageX: 270, pageY: 63, clientX: 270, clientY: 63}]
  }
})

5.事件对象属性中touch和changedTouches详情:


touch和changedTouches
3.事件绑定与冒泡捕获:

1.写法:
key="value"
自基础库版本1.5.0起,key 以bind或catch开头,接:,如: bind:tap、catch:tap、
capture-bind:tap、capture-catch-tap
value为字符串
2.功用:
bind和capture-bind的含义分别代表事件的冒泡阶段和捕获阶段;
触发的顺序:


捕获与冒泡触发顺序

描述: 捕获为从外到内,冒泡为从内到外,先捕获后冒泡

<view id="outer" bind:tap="handleTap4" capture-bind:tap="handleTap1">
  outer view
  <view id="inner" bind:tap="handleTap3" capture-bind:tap="handleTap2">
    inner view
  </view>
</view>
// 点击 inner  view 事件触发顺序为: handleTap1、handleTap2、handleTap3、handleTap4

3.bind事件绑定不会阻止冒泡事件向上冒泡,catch事件绑定可以阻止冒泡事件向上冒泡

<view id="outer" bind:tap="handleTap4" capture-catch:tap="handleTap1">
  outer view
  <view id="inner" bind:tap="handleTap3" capture-bind:tap="handleTap2">
    inner view
  </view>
</view>
只会触发handleTap1,catch事件阻止了tap事件冒泡
六. 兼容:

1.影响微信小程序的实现的几个因素:
手机品牌/操作系统/微信版本/小程序基础库版本号
2.可通过 wx.getSystemInfo 或者 wx.getSystemInfoSync 获取小程序使用环境信息

wx.getSystemInfoSync()
/*
  {
    brand: "iPhone",      // 手机品牌
    model: "iPhone 6",    // 手机型号
    platform: "ios",      // 客户端平台
    system: "iOS 9.3.4",  // 操作系统版本
    version: "6.5.23",    // 微信版本号
    SDKVersion: "1.7.0",  // 小程序基础库版本
    language: "zh_CN",    // 微信设置的语言
    pixelRatio: 2,        // 设备像素比
    screenWidth: 667,    // 屏幕宽度
    screenHeight: 375,     // 屏幕高度
    windowWidth: 667,    // 可使用窗口宽度
    windowHeight: 375,     // 可使用窗口高度
    fontSizeSetting: 16   // 用户字体大小设置
  }
 */

3.判断API是否兼容:

if (wx.openBluetoothAdapter) {
  wx.openBluetoothAdapter()
} else {
  // 如果希望用户在最新版本的客户端上体验您的小程序,可以这样子提示
  wx.showModal({
    title: '提示',
    content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。'
  })
}

4.wx.canIUse
作用:用于判断接口或者组件在当前宿主环境是否可用
参数格式为: API.method.param.options或者component.attribute.option
(1).API 代表 API 名字
(2).method 代表调用方式,有效值为return, success, object, callback
(3).param 代表参数或者返回值
(4).options 代表参数的可选值

// 判断接口及其参数在宿主环境是否可用
wx.canIUse('openBluetoothAdapter')
wx.canIUse('getSystemInfoSync.return.screenWidth')
wx.canIUse('getSystemInfo.success.screenWidth')
wx.canIUse('showToast.object.image')
wx.canIUse('onCompassChange.callback.direction')
wx.canIUse('request.object.method.GET')

component 代表组件名字
attribute 代表组件属性
option 代表组件属性的可选值

// 判断组件及其属性在宿主环境是否可用
wx.canIUse('contact-button')
wx.canIUse('text.selectable')
wx.canIUse('button.open-type.contact')

5.兼容办法
做向前兼容,保证小程序在旧版微信中的运行
在小程序管理后台设置“基础库最低版本设置”。例子:
当小程序只支持1.5.0版本以上的宿主环境时,运行低版本时,微信会提示版本过低之类的。

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

推荐阅读更多精彩内容