码疯窝

小程序基于wepyjs框架开发仿微信界面DEMO

2016/12/14 20:07:15    分类: 技术随笔    0人评论 次浏览

先看一个视频,这个视频并不是去演示如何使用微信,而是演示基于wepy开发的微信小程序demo。

视频地址: https://v.qq.com/x/page/x0352lsswtq.html

demo中包含的功能有:

  • 仿微信界面
  • 联系人列表
  • 私聊与自动回复
  • 聊天记录本地存储与清除

项目代码地址:https://github.com/wepyjs/wepy-wechat-demo

下面就讲讲是如何一步一步实现这个仿微信demo的。

一、需求分析

首先要确定好自已在DEMO中想要实现的功能,微信有四个tab:微信聊天,通讯录,发现,我。右上角的搜索,添加好友功能,以及发现里的朋友圈和各项菜单功能,这里主要想实现的就是聊天,还有通讯录好友功能。因为考虑到小程序真机体验时只允许请求安全域名,所以数据不打算使用后端接口返回,而是采用MOCK数据模拟后端接口返回。聊天记录储存于小程序提供的Storage中。这样就能完整的模拟聊天功能,而且下载下来的DEMO可以直接在真机上体验。

同时评估一些技术细节:

涉及的原生API

  • 登录相关API wx.login。
  • 获取用户信息API wx.getUserInfo。
  • Storage相关 wx.getStorage,wx.setStorage,wx.clearStorage。

技术方案

  • 样式部分使用sass,wepy现阶段支持lesssass,本demo使用sass
  • 代码部分使用新特性async/await
  • 数据接口使用MOCK数据模拟接口返回。

二、页面组件划分

按微信界面展示大致划分为两个页面,首页index,聊天页chat,以及若干组件,如下图:

首页index中包含一个tab组件和四个tab分别所对应的组件messagecontactdiscoveryme。而且各自还包含一些子组件,如contact组件中包含alpha字母列表组件,discoveryme组件中分别包含一些list菜单列表组件。其中list组件达到了很好的复用效果。

聊天页chat中包含一个聊天面板组件chatboard和输入框组件input

根据划分的组件,大致可以得到开发的目录结构:

  1. src
  2. components
  3. alpha.wpy --- 联系人
  4. chatboard.wpy --- "聊天面板" 组件
  5. contact.wpy --- "联系人" 组件
  6. discovery.wpy --- "发现" 组件
  7. input.wpy --- 聊天页输入框组件
  8. list.wpy --- 菜单列表组件
  9. me.wpy --- "我" 组件
  10. message.wpy --- message 组件
  11. tab.wpy --- tab 组件
  12. pages
  13. chat.wpy --- 聊天页
  14. index.wpy --- 首页
  15. 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:

  1. <style type="sass">
  2. .body, .tab_item {
  3. height: 100%;
  4. }
  5. </style>
  6. <template>
  7. <view class="body">
  8. <view class="tab_item tab_message">
  9. <component id="message"></component>
  10. </view>
  11. <view class="tab_item tab_contact">
  12. <component id="contact"></component>
  13. </view>
  14. <view class="tab_item tab_discovery">
  15. <component id="discovery"></component>
  16. </view>
  17. <view class="tab_item tab_me">
  18. <component id="me"></component>
  19. </view>
  20. <component id="tab"></component>
  21. </view>
  22. </template>

src/pages/chat.wpy:

  1. <style type="sass">
  2. .body {
  3. height: 100%;
  4. background-color: #ededed;
  5. }
  6. </style>
  7. <template>
  8. <view class="body">
  9. <component id="chartboard"></component>
  10. <component id="input"></component>
  11. </view>
  12. </template>

接着完成基本的重构工作:

四、MOCK数据设计

通过需求分析得到只需要两份基础数据:

  • 联系人数据
  • 初始聊天记录数据

其对应的数据表结构如下:

ID 姓名 头像
谁发的 发给谁 消息类型 消息内容 发送时间

因此我们可以使用js构建这两份数据表作为原始数据,
目录结构设计大致如下:

  1. src
  2. mocks --- mock数据目录
  3. users --- 用户头像目录
  4. xxxx.png --- xxxx头像
  5. contact.js --- 联系人mock数据
  6. history.js --- 聊天记录mock数据

src/mock/contact.js 模拟联系人数据返回,代码如下:

  1. // 所有联系人数据
  2. let users = [
  3. {id: 'jimgreen', name: 'Jim Green'},
  4. {id: 'hanmeimei', name: '韩梅梅'}
  5. ];
  6. users = users.sort((a, b) => a.id.charCodeAt(0) - b.id.charCodeAt(0));
  7. let table = users.map((v) => {
  8. return {
  9. name: v.name,
  10. id: v.id,
  11. icon: `/mocks/users/${v.id}.png`
  12. };
  13. });
  14. export default table

src/mock/history.js模拟初始聊天记录数据返回,代码如下 :

  1. export default [
  2. {'to': 'jimgrenn', 'from': 'me', 'type': 'text', 'msg': 'My name is Jim Green, nice to meet you.', 'time': 1480338091374},
  3. {'to': 'me', 'from': 'jimgreen', 'type': 'text', 'msg': 'Nice to meet you too', 'time': 1480338091375},
  4. ];

五、接口API设计

因为使用MOCK数据的关系,我们可以同步吐出接口数据,但这里希望能更接近于AJAX访问的异步效果,所以所有接口均返回setTimeout处理的Promise对象。

整理出所需功能的所有数据请求如下:

  • 拉取聊天列表页的聊天列表(用户头像,用户名称,最后一条聊天信息)
  • 拉取聊天页面的聊天记录 (用户头像,自己头像,聊天记录)
  • 发送聊天信息
  • 拉取tab下的个人头像以及用户昵称等信息

因为涉及到的数据接口并不多,所以单独放在src/common/api模块下。
代码结构大致如下:

  1. import m_contacts from '../mocks/contact';
  2. import m_history from '../mocks/history';
  3. export default {
  4. // 拉取用户信息
  5. getUserInfo () {},
  6. // 拉取与某个用户的聊天历史记录
  7. getHistory (id) {},
  8. // 拉取首页聊天列表
  9. getMessageList () {},
  10. // 发送聊天信息
  11. sendMsg (to, msg, type = 'text') {}
  12. }

六、逻辑代码开发

逻辑代码部分主要包括三部分:

  • 调用数据接口,返回数据,渲染视图。
  • 组件内事件交互。
  • 组件之间相互通信。

message组件中需要拉取聊天列表信息并且渲染,代码如下:

  1. <template>
  2. <view class="message">
  3. <block wx:for="{{list}}" wx:for-index="index" wx:for-item="item">
  4. <view class="item" bindtap="select" data-wepy-params="{{item.id}}">
  5. <view class="header">
  6. <image class="img" src="{{item.icon}}"></image>
  7. </view>
  8. <view class="content">
  9. <view class="name">{{item.name}}</view>
  10. <view class="lastmsg">{{item.lastmsg}}</view>
  11. </view>
  12. </view>
  13. </block>
  14. </view>
  15. </template>
  16. <script>
  17. import wepy from 'wepy';
  18. import api from '../common/api';
  19. export default class Message extends wepy.component {
  20. data = {
  21. list: []
  22. };
  23. methods = {
  24. select (evt, id) {
  25. wx.navigateTo({url: 'chat?id=' + id});
  26. }
  27. };
  28. async loadMessage () {
  29. this.list = await api.getMessageList();
  30. this.$apply();
  31. }
  32. }
  33. </script>

message组件中只有一个数据源list,通过自定义方法loadMessage调用api模块获取聊天列表信息进行渲染,因为是在自定义的异步方法中进行数据绑定,所以需要执行this.$apply()让视图渲染。
同时,组件响应页面的tap事件select,选中聊天之后跳转至chat页面。
chat页面进行聊天之后,返回到index页面时,需要message页面再次调用接口数据,重新渲染聊天列表页,这就需要在index页面的onShow方法中去让message组件重新调用loadMessage方法。这里可以选用 wepy 提供的$boradcast方法或者$invoke方法,代码如下:

  1. // src/pages/index.wpy
  2. onShow() {
  3. this.$invoke('message', 'loadMessage');
  4. }

这样就完成了message组件的所有功能,进入页面渲染列表,点击消息进入聊天页面。

index页面中加入状态currentTab来标记当前选中tab。并提供切换tab事件。

src/pages/index:

  1. <template>
  2. <view class="body">
  3. <view class="tab_item tab_message" hidden="{{currentTab != 0}}">
  4. <component id="message"></component>
  5. </view>
  6. <view class="tab_item tab_contact" hidden="{{currentTab != 1}}">
  7. <component id="contact"></component>
  8. </view>
  9. <view class="tab_item tab_discovery" hidden="{{currentTab != 2}}">
  10. <component id="discovery"></component>
  11. </view>
  12. <view class="tab_item tab_me" hidden="{{currentTab != 3}}">
  13. <component id="me"></component>
  14. </view>
  15. <component id="tab"></component>
  16. </view>
  17. </template>
  18. <script>
  19. //....
  20. changeTab (idx) {
  21. this.currentTab = +idx;
  22. this.$apply();
  23. }
  24. </script>

然后在tab组件中的每个tab中添加bindtap="change" data-wepy-params="{{index}}"事件。

  1. <script>
  2. import wepy from 'wepy';
  3. export default class Tab extends wepy.component {
  4. data = {
  5. active: 0,
  6. };
  7. methods = {
  8. change (evt, idx) {
  9. this.active = +idx;
  10. this.$parent.changeTab(idx);
  11. }
  12. };
  13. }
  14. </script>

在tab组件中,直接通过$parent去调用父组件的changeTab方法,来达到实现tab切换效果:

至此已完成大致雏形,更多代码还请参考提供源代码。

结束语:
wepy让用户能以组件化思维开发小程序,加上一些新特性的引入让开发与维护变得更简单,但同时缺点又在于引入框架以及额外的polyfill,npm增加项目代码体积(压缩后170kb),在仅限1M代码体积的小程序中,代码容量时时刻刻又显得有些捉肘见襟了。希望小程序能早日能放宽限制。

继续查看有关 技术随笔的文章

0个访客评论