前段时间,微信公布了这样一个重磅炸弹 – 小程序。从没想过,原本作为平台之上的产物向上提升成为平台,因为原本的android应用容量不大,作为小程序这样的存在再次存于微信这样的大体量应用上。

文档中介绍的说法是:
微信小程序是一种全新的连接用户与服务的方式,它可以在微信内被便捷地获取和传播,同时具有出色的使用体验。
我还是太年轻了,毕竟是微信,敢想也敢做,不得不说腾讯的野心真是大。

原本在他刚出现时就想体验一把了,结果并不是所有人都能体验,而且毕竟刚刚开始还是有许多问题的,所以等到现在~

安装

  1. 小程序有自己的开发软件:
    微信开发者工具
    功能还是很齐全的除了还有些坑以外还是很不错的。当然用webstorm,vscode,甚至atom,sublime也能进行开发。
    他的文件使用了自己的一套格式名称,但依旧是以js进行开发。

  2. 安装之后还需要一些其他的操作才能进入开发
    首先是需要在微信公众平台中进行注册。
    注册完成后进入小程序管理平台,在这里可以管理小程序的权限,查看数据报表,发布小程序等操作。
    接下来在菜单中的进行如下操作:
    开发设置
    保存AppID,等会儿会用到的。
    以及第四步,将需要开发的接口保存在request合法域名内,否则开发时无法进行网络访问。本次还是使用gank的接口进行开发,感谢代码家大大。当然其实这一步不需要也可以,在开发时可以进行一些设置的修改

开发

那么首先,安装完微信开发者工具后打开并登陆,右下角点击添加新项目,这里除了项目目录项目名称外还需要上面步骤中获取到的appId,新建项目。

  1. 目录结构:
    本次使用干货集中营的api。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    ├── pages                       页面文件夹
    │ ├── index 首页文件夹
    │ │ └── index.js 首页js
    │ │ └── index.wxml 首页html
    │ │ └── index.wxss 首页style
    │ ├── main 主页文件夹
    │ │ └── main.js 主页js
    │ │ └── main.wxml 主页html
    │ │ └── main.wxss 主页style
    │ ├── article 文章页文件夹
    │ │ └── article.js 文章页js
    │ │ └── article.wxml 文章页html
    │ │ └── article.wxss 文章页style
    ├── utils 工具文件夹
    │ └── Constants.js 全局共享常量
    │ └── util.js 工具类
    ├── app.js 配置js
    ├── app.json 小程序配置页
    ├── app.wxss 配置style
    └── project.config.json 工具配置
  2. 首先是首页:
    效果:
    首页
    点击banner上的图片后:
    首页2

    • index.wxml:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      <view class="container">
      <view class="userinfo">
      <button wx:if="{{!hasUserInfo && canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button>
      <block wx:else>
      <image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" background-size="cover"></image>
      <text class="userinfo-nickname">{{userInfo.nickName}}\n小程序试手小Demo</text>
      </block>
      </view>
      <modal no-cancel="true" hidden="{{modalHidden}}" bindconfirm="onConfirmClick">
      <view >
      <image class="modalImage" src="{{meizhi[current].url}}" mode="widthFix"></image>
      </view>
      </modal>
      <swiper class='banner' indicator-dots="true" autoplay="{{isScroll}}" bindchange="EventHandle">
      <block wx:for="{{meizhi}}">
      <swiper-item>
      <image src="{{item.url}}" mode="aspectFit" class="banner-image" bindtap="onImageClick" binderror="imageError"/>
      </swiper-item>
      </block>
      </swiper>
      <button class="enter-main" bindtap="enterMain">进入主页 → </button>
      </view>

      上方获取微信头像和昵称是新疆项目时本身自带的内容,修改了一些显示方式和位置,在下方留下足够空间添加了一个banner和一个进入主页的按钮,并且点击banner时能够跳出一个浏览大图的dialog。

    • index.js:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      const app = getApp()
      var Constants = require('../../utils/Constants.js');
      var Util = require('../../utils/util.js');

      Page({
      data: {
      userInfo: {},
      hasUserInfo: false,
      modalHidden: true,
      canIUse: wx.canIUse('button.open-type.getUserInfo'),
      meizhi: [],
      current: 0,
      isScroll: true,
      },
      //事件处理函数
      bindViewTap: function() {
      wx.navigateTo({
      url: '../logs/logs'
      })
      },
      onLoad: function () {
      that = this;
      if (app.globalData.userInfo) {
      this.setData({
      userInfo: app.globalData.userInfo,
      hasUserInfo: true
      })
      } else if (this.data.canIUse){
      // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
      // 所以此处加入 callback 以防止这种情况
      app.userInfoReadyCallback = res => {
      this.setData({
      userInfo: res.userInfo,
      hasUserInfo: true
      })
      }
      } else {
      // 在没有 open-type=getUserInfo 版本的兼容处理
      wx.getUserInfo({
      success: res => {
      app.globalData.userInfo = res.userInfo
      this.setData({
      userInfo: res.userInfo,
      hasUserInfo: true
      })
      }
      })
      }
      requestMeizhi();
      },
      getUserInfo: function(e) {
      console.log(e)
      app.globalData.userInfo = e.detail.userInfo
      this.setData({
      userInfo: e.detail.userInfo,
      hasUserInfo: true
      })
      },
      imageError: function (e) {
      this.setData({
      meizhi: ['../logo.png', '../logo.png', '../logo.png', '../logo.png', '../logo.png']
      })
      },
      EventHandle:function(event){
      console.info(event)
      this.setData({
      current: event.detail.current,
      })
      },
      onImageClick: function (event) {
      this.setData({
      modalHidden: false,
      isScroll: false,
      })
      },
      onConfirmClick: function (event) {
      this.setData({
      modalHidden: true,
      isScroll: true
      });
      },
      enterMain: function(e) {
      wx.navigateTo({
      url: Constants.PAGE_MAIN,
      })
      },

      /**
      * 生命周期函数--监听页面显示
      */
      onShow: function () {
      this.setData({
      isScroll: true,
      })
      },
      /**
      * 生命周期函数--监听页面隐藏
      */
      onHide: function () {
      this.setData({
      isScroll: false,
      })
      },
      });
      var that;

      function requestMeizhi() {
      wx.request({
      url: "https://gank.io/api/data/福利/8/1",
      header: {
      "Content-Type": "application/json"
      },
      success: function (res) {
      if (res == null ||
      res.data == null ||
      res.data.results == null ||
      res.data.results.length <= 0) {
      console.error(Constants.ERROR_DATA_IS_NULL);
      return;
      }
      that.setData({
      meizhi: res.data.results,
      })
      }
      });
      }

      包括几个部分,一个是userinfo,这是自带的部分,请求也写好了的。然后是下方banner的请求,数据保存于meizhi数组中,每次只请求前8张。然后是dialog显示的部分,显示时获取当前的资源用于dialog显示,以及停止banner的自动播放。最后是一些点击事件。

    • index.wxss:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      .userinfo {
      display: flex;
      flex-direction: row;
      align-items: center;
      margin-top: -50px;
      }

      .userinfo-avatar {
      width: 128rpx;
      height: 128rpx;
      margin: 10rpx;
      border-radius: 50%;
      align-items: right;
      }

      .userinfo-nickname {
      margin-left: 15px;
      font-size: 15px;
      color: #666666;
      }

      .usermotto {
      margin-top: 50px;
      }

      .banner-image{
      display: initial
      }

      .banner{
      box-shadow: 1px 0px 6px #cccccc;
      padding: 5px;
      border-radius: 1%;
      margin: 10px;
      margin-top: 40px;
      width: 70%;
      height: 600rpx;
      position: center;
      }

      .modalImage{
      margin: auto;
      width: 100%;
      height: 1000rpx
      }

      .enter-main{
      box-shadow: 1px 1px 1px #cccccc;
      position: relative;
      border-radius: 0;
      color: #fff;
      font-size: 14px;
      margin-top: 40px;
      background-color: #26a69a;
      }

      android里有的cardview,同样这里也可以使用一些style属性表现出来。

  3. 列表主页:
    效果预览:
    列表页

    • main.wxml:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      <view>
      <loading hidden="{{hidden}}">
      加载中...
      </loading>
      <view class="card" wx:for="{{AndroidList}}">
      <view class="title-container" id="{{item.url}}&title={{item.desc}}" bindtap="onItemClick">
      <image class="tag" src="./text.png" ></image>
      <text class="title">{{item.desc}}</text>
      </view>
      <view class="text-container" id="{{item.url}}&title={{item.desc}}" bindtap="onItemClick">
      <text class="author">作者:{{item.who}}</text>
      <text class="time">发布时间:{{item.publishedAt}}</text>
      </view>
      </view>
      </view>

      一个请求时显示的加载中的loading组件,主页上为一个列表。

    • main.js:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      const app = getApp()
      var Constants = require('../../utils/Constants.js');
      var Util = require('../../utils/util.js');

      Page({
      data: {
      AndroidList: [],
      hidden: false,
      nextPage: 1,
      },
      onLoad: function () {
      that = this;
      this.setData({
      nextPage: 1,
      })
      requestAndroid(this.data.nextPage);
      },
      onPullDownRefresh(){
      console.info(e.detail)
      this.setData({
      hidden: false,
      })
      requestAndroid(this.data.nextPage);
      },
      onReachBottom(){
      this.setData({
      hidden: false,
      })
      requestAndroid(this.data.nextPage);
      },
      onItemClick(event){
      console.info(event.currentTarget.id)
      wx.navigateTo({
      url: Constants.PAGE_ARTICLE + '?id=' + event.currentTarget.id
      });
      }
      })

      var that;

      function requestAndroid(page){
      console.info(page);
      wx.request({
      url: 'https://gank.io/api/data/Android/20/' + page,
      header: {},
      success: function (res) {
      res.data.results = modifyTime(res.data.results);
      if (page == 1) {
      that.setData({
      AndroidList: res.data.results,
      nextPage: page + 1,
      hidden: true,
      })
      } else {
      that.setData({
      AndroidList: that.data.AndroidList.concat(res.data.results),
      nextPage: page + 1,
      hidden: true,
      })
      }
      }
      })
      }

      function modifyTime(AndroidList){
      for(var i=0;i<AndroidList.length;i++){
      AndroidList[i].publishedAt = AndroidList[i].publishedAt.substring(5,10);
      }
      return AndroidList;
      }

      主要是列表的请求以及loading是否显示的标志位,另外添加了一个上拉加载功能。

    • main.wxss:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      .card{
      box-shadow: 0px 1px 2px #999;
      margin: 15px;
      padding: 10px;
      }
      .title-container{
      flex-direction: row;
      }
      .tag{
      width:14px;
      height: 14px;
      margin-right: 5px;
      margin-top: 2px;
      }
      .text-container{
      margin-top: 10px;
      display: flex;
      flex-direction: row;
      justify-content: space-between;
      align-items: center;
      }
      .title{
      font-size: 15px;
      }
      .author{
      color: #999999;
      font-size: 12px;
      }
      .time{
      color: #999999;
      position: right;
      font-size: 12px;
      }

      同样列表页使用一个card的样式显示,这里遇到了一个问题,单个item内标题前添加一个小的图标,没有找到text自带的方法,就自己添加了一个image,又想做到标题长度超过两行省略的动作,但是这样的代码会导致图标和标题不在同一行,暂时还未解决这个问题。

  4. 文章页面:
    小程序非常坑爹的不能打开外链,就是他并没有webview这个功能。很气,又拿他没办法。
    效果预览:
    列表页

    • article.wxml:

      1
      2
      3
      4
      5
      6
      7
      <view class="container">
      <text class="description">小程序不能打开外链,这有啥用??</text>
      <text class="title">标题:{{title}}</text>
      <image class="imangry" src="./shengqi.jpg" mode="widthFix"></image>
      <text class="url">链接:{{url}}</text>
      <button class="copy" bindtap='onClick'>{{isCopy?"已复制":"复制"}}</button>
      </view>
    • article.js:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      Page({
      data: {
      url: '',
      title: '',
      isCopy: false,
      },
      onLoad: function (options) {
      console.info(options.id);
      console.info(options.title);
      this.setData({
      url: options.id,
      title: options.title,
      })
      },
      onClick: function(event){
      wx.setClipboardData({
      data: this.data.url,
      })
      this.setData({
      isCopy: true,
      })
      wx.getClipboardData({
      success: function(res) {
      console.info(res)
      },
      fail: function(res) {},
      complete: function(res) {},
      })
      }
      })

      将列表页由点击事件传递过来的url和标题显示于本页,并提供复制。

    • article.wxss:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      .container{
      padding: 10px;
      }
      .description{
      margin: 20px;
      font-size: 18px;
      color: #555;
      align-items: left;
      }
      .title{
      font-size: 14px;
      align-items: left;
      margin: 10px;
      color: #555;
      }
      .imangry{
      width: 70%;
      }
      .url{
      font-size: 12px;
      color: #777;
      margin: 20px;
      }
      .copy{
      box-shadow: 1px 1px 1px #cccccc;
      position: relative;
      width: 70%;
      border-radius: 0;
      color: #fff;
      font-size: 14px;
      margin-top: 20px;
      background-color: #26a69a;
      }

      无奈的添加一个表情。

  5. 各个配置页面只列出修改了的文件:

    • Constants.js:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      //url相关
      var BASE_URL = "https://gank.io/api";

      //error相关
      var ERROR_DATA_IS_NULL = "获取数据为空,请重试";

      //各个page的URL
      var PAGE_MAIN = "/pages/main/main";
      var PAGE_INDEX = "/pages/index/index";
      var PAGE_ARTICLE = "/pages/article/article";


      module.exports = {
      BASE_URL: BASE_URL,
      ERROR_DATA_IS_NULL: ERROR_DATA_IS_NULL,
      PAGE_MAIN: PAGE_MAIN,
      PAGE_INDEX: PAGE_INDEX,
      PAGE_ARTICLE: PAGE_ARTICLE
      }

      一些全局共享的常量。

    • app.json

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      {
      "pages":[
      "pages/index/index",
      "pages/main/main",
      "pages/article/article"
      ],
      "window":{
      "backgroundTextStyle":"light",
      "navigationBarBackgroundColor": "#26a69a",
      "navigationBarTitleText": "干货集中营",
      "navigationBarTextStyle":"white"
      },
      "networkTimeout": {
      "request": 10000,
      "connectSocket": 10000,
      "uploadFile": 10000,
      "downloadFile": 10000
      },
      "debug": true
      }

总结

总的来说,小程序开发还是很方便的,作为纯安卓开发者入手也是很快,只需要一些js基础,不过js和java在逻辑上还是很像的。当然他提供的功能还有许多尚未完善,也有许多的坑还没有填上,期待其发展。