
随着移动互联网的普及,各类门店都在迅速向线上化转型以赢得宝贵先机。
商厦里几乎所有奶茶店都支持提前下单点餐、各类运动场馆也在线上开放场地预约,微信小程序预约成为门店数字化转型的一个简单的切入点。
在美容美发行业,经常会遇到客户排队等待理发这样的问题,而只要为门店接入预约管理系统就能很好地解决这一问题,不仅能有效地资源配置,也能达到提升营业额的目的。
目前,水滴智店已为「Docs Barbershop男士理发店」、「Fade Room 83」等理发店提供一系列完整的小程序预约解决方案。
线上预约
客户可以在小程序内先进行对不同理发师的预约,然后再选择预约的服务项目,最后再预约时间。比如在「Docs Barbershop男士理发店」,可向一位理发师预约12种不同的服务,比如理发、传统式剃须、干刮修胡子、洗发&造型等等。
商家可在后台设置需要发布的服务安排,水滴智店拥有智能安排功能,灵活设置服务预约、周期、临时调整等维度。客户可以直接线上进行预约,大大提升消费效率。
系统支持以散客/会员两种不同身份预约,并可迅速解决付费、退订、核销的问题,以低成本实现高效率。
会员积分制
新一代会员系统可实现双向交互,让用户可以随时查询信息,预约服务或与商家沟通,商家也可以随时联系客户。
在水滴智店中,有两种会员卡体系,分别是成长型和付费型。
成长会员是基于成长值体系,客户通过消费累计成长值来获得或升级会员卡。付费会员则是客户通过支付费用来购买会员卡。不同等级的会员对应的会员权限不一样。
商家可以自定义设置活动的优惠力度。客户购买会员卡后,不仅可以享受会员专属消费折扣,还可以获得开卡礼包,比如积分或优惠券。会员系统支持通过累计积分的方式,在线上商城兑换相应的礼品或直接抵现。
通过会员体系,可大大优化服务体验,增加客户的复购率和客户忠诚度,从而提升销售转化率。
微信小程序预约管理系统,公众号在线预约服务|课程|场地|技师|挂号 - 水滴智店
数字化营销
水滴智店以客户为中心,基于小程序、公众号提供完整服务体系,实现线上线下数据的完全打通。
「Docs Barbershop男士理发店」为连锁门店,在上海静安区和浦东区拥有两家门店,客户可在小程序内进行任意切换门店,并进行服务预约。
水滴智店支持单店/连锁多模式,可将全国范围内的连锁门店统一进行线上管理,可满足线下收
主要步骤
获取用户头像
图片模板
图片合成
一、获取用户头像
制作自定义头像的第一步就是先选择图片。在【海豚趣图】的交互设计中,用户有三种选择图片的方式:微信头像、本地相册和相机拍摄。获取用户头像的产品设计如下图所示:
1、由于微信官方不再支持通过 wx.getUserInfo 接口来获取用户信息,我们必须通过使用 button 组件并将 open-type 指定为 getUserInfo 类型来获取或展示用户信息。
为优化用户体验,使用 wx.getUserInfo 接口直接d出授权框的开发方式将逐步不再支持。从2018年4月30日开始,小程序与小游戏的体验版、开发版调用 wx.getUserInfo 接口,将无法d出授权询问框,默认调用失败。正式版暂不受影响。上图中d出底部菜单的交互方式无法通过 wx.showActionSheet 来实现(因为该接口只能指定字符串文本,不能使用 button, navigator 等组件)。
因此,只能通过自定义 actionSheet 组件来实现以上功能。
mmp-action-sheet 组件
以下是 mmp-action-sheet 组件的代码。
index.wxml
<view hidden="{{!actionShow}}" class="mask {{mask}}" bindtap="actionHide"> <view class="actionSheet animated {{animation}}"><slot></slot>
<button class="close" bindtap="actionHide">{{closeText}}</button>
</view></view>
2、通过 slot 在 action-sheet 中插入自定义的内容,比如 button、navigator 等。
index.wxss
.mask{ position: fixed top: 0 left: 0 width:100% height: 100% background: rgba(0, 0, 0, 0.5) z-index: 999}.actionSheet{ width: 100% position: absolute top: 100% z-index: 1000 overflow: hidden
}.actionSheet button,.actionSheet navigator{ color: #000 text-align: center background: #fff border-radius: 0 line-height: 3.5 font-size: 32rpx border-bottom: 1rpx solid rgb(236, 236, 236) opacity: 1
}.actionSheet button:active,.actionSheet navigator:active{ color:#000 background: rgb(236, 236, 236)
}.actionSheet button::after,.actionSheet navigator::after{ border: none border-radius: 0
}.actionSheet .close{ border-bottom: none border-bottom: 50rpx solid #fff border-top: 16rpx solid rgb(236, 236, 236)
}.animated { animation-timing-function: ease-out animation-duration: 0.2s animation-fill-mode: both
}@keyframes fadeInBottom {from{ transform: translate3d(0, 0, 0)
} to { transform: translate3d(0, -100%, 0)
}
}.fadeInBottom { animation-name: fadeInBottom
}@keyframes fadeOutBottom {from{ transform: translate3d(0, -100%, 0)
} to { transform: translate3d(0, 0, 0)
}
}.fadeOutBottom { animation-name: fadeOutBottom
}@keyframes fadeIn {from{ opacity: 0
} to { opacity: 1
}
}.fadeIn { animation-name: fadeIn
}@keyframes fadeOut {from{ opacity: 1
} to { opacity: 0
}
}.fadeOut { animation-name: fadeOut
}
index.js
Component({ properties: { actionSheetStatus: { type: Boolean, value: false,observer(newVal) {
if (newVal) {
this.setData({ actionSheetStatus: true, animationMask: 'fadeIn', animationSheet: 'fadeInBottom'
})
} else { this.setData({ actionSheetStatus: false, animationMask: 'fadeOut', animationSheet: 'fadeOutBottom'
})
}
}
}, closeText: { type: String, value: '取消'
}
}, data: { animationMask: 'fadeIn', animationSheet: 'fadeInBottom'
}, methods: {
closeActionSheet() {
this.setData({ animationMask: 'fadeOut', animationSheet: 'fadeOutBottom'
})
setTimeout(() =>{
this.setData({actionSheetStatus: false})
}, 300)
}
}
})
组件只有两个参数:
actionSheetStatus 指定组件的初始展示状态,默认为false,表示不显示组件。
closeText 指定关闭按钮的名字,默认为 取消。
index.json
{ "component": true, "usingComponents": {}}
接下来在页面中调用组件,在组件中插入了3个 button 组件来实现来获取用户头像:
<action-sheet actionSheetStatus="{{actionSheetStatus}}"><button open-type="getUserInfo" bindgetuserinfo="bindGetUserInfo">使用微信头像</button>
<button bindtap="pickPic" data-source-type="album">使用本地相册</button>
<button bindtap="pickPic" data-source-type="camera">拍照</button>
</action-sheet>
以上我们通过自定义组件 mmp-action-sheet 就解决了原生的 actionsheet 无法指定 button,从而无法获取用户微信头像的问题。
该组件我已经发布到 npm 包,需要用到的同学可以通过 npm 安装,也可以在 github 上查看源码和使用文档。
二、图片模板
有了原图,接下来我们需要选择图片模板。如果模板数量不多或者模板变化不频繁,我们可以直接把模板放在本地。鉴于我提供的模板比较多,放在本地会增大小程序源码的大小,我把模板上传到了小程序的云存储中,通过云函数来动态获取图片模板,方便以后模板扩展。
云函数 tpl 的代码如下:
// 云函数入口文件const cloud = require('wx-server-sdk')cloud.init()// 云函数入口函数exports.main = async (event, context) =>{ const wxContext = cloud.getWXContext() // 1. 获取数据库引用
const db = cloud.database() const MAX_LIMIT = 100
// 2. 构造查询语句
const countResult = await db.collection('template').count() const total = countResult.total // 计算需分几次取
const batchTimes = Math.ceil(total / 100) const tasks = [] for (let i = 0i <batchTimesi++) { const promise = db.collection('template').skip(i * MAX_LIMIT).limit(MAX_LIMIT).get()
tasks.push(promise)
} return (await Promise.all(tasks)).reduce((acc, cur) =>{ return {
data: acc.data.concat(cur.data),
errMsg: acc.errMsg,
}
})
}
页面中调用云函数拉取模板:
getTpl() { const self = this// 调用云函数获取图片模板
wx.cloud.callFunction({
name: 'tpl'
}).then(res =>{
self.setData({
templates: res.result.data
})
})
}
三、问题
到这里模板的获取逻辑已经没有问题了,但在开发过程中遇到了一个问题。模板图片的链接我使用的是云文件ID,当有大量图片并行加载的时候,只有部分图片能够显示,我看了一下dom节点其实都已经存在了,image的src的地址也都是正确的。
1、微信官方自2.3.0开始已经支持在image中使用云文件ID。云文件ID的格式为: cloud://xxx.xxx/templates/01.png。我猜测可能是对微信云存储并发请求过多导致的(有知道的同学可以告知),因为我试了一下将云文件ID换成正常的HTTPS的链接是没问题的。
由此可知,可以想到有三种可行的解决方案:
2、将图片模板存储到外部OSS,使用https协议的链接。
3、使用 wx.getTempFileURL 用云文件 ID 换取真实链接,也就是https形式的链接。
4、控制图的并行加载数量。我的实践是将并行加载数量控制在20,当用户滚动的时候再发起下一次请求。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)