先看一个视频,这个视频并非去演示如何使用微信,而是演示基于wepy开发的微信小程序demo。javascript
视频地址: https://v.qq.com/x/page/x0352lsswtq.htmlhtml
demo中包含的功能有:java
仿微信界面git
联系人列表github
私聊与自动回复npm
聊天记录本地存储与清除小程序
项目代码地址:https://github.com/wepyjs/wepy-wechat-demo后端
下面就讲讲是如何一步一步实现这个仿微信demo的。微信小程序
首先要肯定好自已在DEMO中想要实现的功能,微信有四个tab:微信聊天,通信录,发现,我。右上角的搜索,添加好友功能,以及发现里的朋友圈和各项菜单功能,这里主要想实现的就是聊天,还有通信录好友功能。由于考虑到小程序真机体验时只容许请求安全域名,因此数据不打算使用后端接口返回,而是采用MOCK数据模拟后端接口返回。聊天记录储存于小程序提供的Storage中。这样就能完整的模拟聊天功能,并且下载下来的DEMO能够直接在真机上体验。api
同时评估一些技术细节:
涉及的原生API
登陆相关API wx.login。
获取用户信息API wx.getUserInfo。
Storage相关 wx.getStorage,wx.setStorage,wx.clearStorage。
技术方案
样式部分使用sass
,wepy现阶段支持less
,sass
,本demo使用sass
。
代码部分使用新特性async/await
。
数据接口使用MOCK数据模拟接口返回。
按微信界面展现大体划分为两个页面,首页index
,聊天页chat
,以及若干组件,以下图:
首页index
中包含一个tab
组件和四个tab分别所对应的组件message
,contact
,discovery
,me
。并且各自还包含一些子组件,如contact
组件中包含alpha
字母列表组件,discovery
和me
组件中分别包含一些list
菜单列表组件。其中list
组件达到了很好的复用效果。
聊天页chat
中包含一个聊天面板组件chatboard
和输入框组件input
。
根据划分的组件,大体能够获得开发的目录结构:
src components alpha.wpy --- 联系人 chatboard.wpy --- "聊天面板" 组件 contact.wpy --- "联系人" 组件 discovery.wpy --- "发现" 组件 input.wpy --- 聊天页输入框组件 list.wpy --- 菜单列表组件 me.wpy --- "我" 组件 message.wpy --- message 组件 tab.wpy --- tab 组件 pages chat.wpy --- 聊天页 index.wpy --- 首页 app.wpy --- 小程序入口
直接用手机截屏而后放到Photoshop
中处理。小程序作不一样机型的适配很方便,提供了一个rpx
的单位,官方说明以下:
rpx(responsive pixel): 能够根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。
个人手机截图尺寸是 720px 1280px, 为了方便计算,直接将截图按比例调整为 750px 1333px。那么此时的单位换算就是1px = 1rpx,也就是说一个图片在Photoshop
中是 80px * 80px,那么就直接写width: 80rpx;height: 80rpx;
。
整理出各图标大小以及各元素之间的宽高间距等,方便在sass中使用。以下图:
按照第二步划分的页面组件,对组件进行基本的填充。而后页面内容就十分简单了。
src/pages/index.wpy:
<style type="sass"> .body, .tab_item { height: 100%; } </style> <template> <view class="body"> <view class="tab_item tab_message"> <component id="message"></component> </view> <view class="tab_item tab_contact"> <component id="contact"></component> </view> <view class="tab_item tab_discovery"> <component id="discovery"></component> </view> <view class="tab_item tab_me"> <component id="me"></component> </view> <component id="tab"></component> </view> </template>
src/pages/chat.wpy:
<style type="sass"> .body { height: 100%; background-color: #ededed; } </style> <template> <view class="body"> <component id="chartboard"></component> <component id="input"></component> </view> </template>
接着完成基本的重构工做:
经过需求分析获得只须要两份基础数据:
联系人数据
初始聊天记录数据
其对应的数据表结构以下:
ID | 姓名 | 头像 |
---|
谁发的 | 发给谁 | 消息类型 | 消息内容 | 发送时间 |
---|
所以咱们可使用js构建这两份数据表做为原始数据,
目录结构设计大体以下:
src mocks --- mock数据目录 users --- 用户头像目录 xxxx.png --- xxxx头像 contact.js --- 联系人mock数据 history.js --- 聊天记录mock数据
src/mock/contact.js
模拟联系人数据返回,代码以下:
// 全部联系人数据 let users = [ {id: 'jimgreen', name: 'Jim Green'}, {id: 'hanmeimei', name: '韩梅梅'} ]; users = users.sort((a, b) => a.id.charCodeAt(0) - b.id.charCodeAt(0)); let table = users.map((v) => { return { name: v.name, id: v.id, icon: `/mocks/users/${v.id}.png` }; }); export default table
src/mock/history.js
模拟初始聊天记录数据返回,代码以下 :
export default [ {'to': 'jimgrenn', 'from': 'me', 'type': 'text', 'msg': 'My name is Jim Green, nice to meet you.', 'time': 1480338091374}, {'to': 'me', 'from': 'jimgreen', 'type': 'text', 'msg': 'Nice to meet you too', 'time': 1480338091375}, ];
由于使用MOCK数据的关系,咱们能够同步吐出接口数据,但这里但愿能更接近于AJAX访问的异步效果,因此全部接口均返回setTimeout处理的Promise对象。
整理出所需功能的全部数据请求以下:
拉取聊天列表页的聊天列表(用户头像,用户名称,最后一条聊天信息)
拉取聊天页面的聊天记录 (用户头像,本身头像,聊天记录)
发送聊天信息
拉取tab我
下的我的头像以及用户昵称等信息
由于涉及到的数据接口并很少,因此单独放在src/common/api
模块下。
代码结构大体以下:
import m_contacts from '../mocks/contact'; import m_history from '../mocks/history'; export default { // 拉取用户信息 getUserInfo () {}, // 拉取与某个用户的聊天历史记录 getHistory (id) {}, // 拉取首页聊天列表 getMessageList () {}, // 发送聊天信息 sendMsg (to, msg, type = 'text') {} }
逻辑代码部分主要包括三部分:
调用数据接口,返回数据,渲染视图。
组件内事件交互。
组件之间相互通讯。
在message
组件中须要拉取聊天列表信息而且渲染,代码以下:
<template> <view class="message"> <block wx:for="{{list}}" wx:for-index="index" wx:for-item="item"> <view class="item" bindtap="select" data-wepy-params="{{item.id}}"> <view class="header"> <image class="img" src="{{item.icon}}"></image> </view> <view class="content"> <view class="name">{{item.name}}</view> <view class="lastmsg">{{item.lastmsg}}</view> </view> </view> </block> </view> </template> <script> import wepy from 'wepy'; import api from '../common/api'; export default class Message extends wepy.component { data = { list: [] }; methods = { select (evt, id) { wx.navigateTo({url: 'chat?id=' + id}); } }; async loadMessage () { this.list = await api.getMessageList(); this.$apply(); } } </script>
message
组件中只有一个数据源list
,经过自定义方法loadMessage
调用api模块获取聊天列表信息进行渲染,由于是在自定义的异步方法中进行数据绑定,因此须要执行this.$apply()
让视图渲染。
同时,组件响应页面的tap事件select
,选中聊天以后跳转至chat
页面。
在chat
页面进行聊天以后,返回到index
页面时,须要message
页面再次调用接口数据,从新渲染聊天列表页,这就须要在index
页面的onShow
方法中去让message
组件从新调用loadMessage
方法。这里能够选用 wepy 提供的$boradcast
方法或者$invoke
方法,代码以下:
// src/pages/index.wpy onShow() { this.$invoke('message', 'loadMessage'); }
这样就完成了message
组件的全部功能,进入页面渲染列表,点击消息进入聊天页面。
在index
页面中加入状态currentTab
来标记当前选中tab。并提供切换tab事件。
src/pages/index:
<template> <view class="body"> <view class="tab_item tab_message" hidden="{{currentTab != 0}}"> <component id="message"></component> </view> <view class="tab_item tab_contact" hidden="{{currentTab != 1}}"> <component id="contact"></component> </view> <view class="tab_item tab_discovery" hidden="{{currentTab != 2}}"> <component id="discovery"></component> </view> <view class="tab_item tab_me" hidden="{{currentTab != 3}}"> <component id="me"></component> </view> <component id="tab"></component> </view> </template> <script> //.... changeTab (idx) { this.currentTab = +idx; this.$apply(); } </script>
而后在tab
组件中的每一个tab中添加bindtap="change" data-wepy-params="{{index}}"
事件。
<script> import wepy from 'wepy'; export default class Tab extends wepy.component { data = { active: 0, }; methods = { change (evt, idx) { this.active = +idx; this.$parent.changeTab(idx); } }; } </script>
在tab组件中,直接经过$parent
去调用父组件的changeTab
方法,来达到实现tab切换效果:
至此已完成大体雏形,更多代码还请参考提供源代码。
结束语:wepy让用户能以组件化思惟开发小程序,加上一些新特性的引入让开发与维护变得更简单,但同时缺点又在于引入框架以及额外的polyfill,npm增长项目代码体积(压缩后170kb),在仅限1M代码体积的小程序中,代码容量时时刻刻又显得有些捉肘见襟了。但愿小程序能早日能放宽限制。