李森的博客 - 小程序
type: channel
link: https://lisen.cc/tag/%E5%B0%8F%E7%A8%8B%E5%BA%8F/
items: UniApp开发小程序生成海报
items: 油耗笔记OilNote集成腾讯地图实现载在图选择加油站充电站功能
items: 油耗笔记OilNote周边搜索能力由百度地图切换成腾讯地图
items: 油耗笔记OilNote周边搜索能力由高德地图切换成百度地图
items: 解决小程序报错the api need to be declared in the requiredPrivateInfos field in app.json
items: 微信小程序获取用户头像后上传到七牛云
items: 微信小程序自定义不一样的tabBar
items: Spring Boot 封装微信小程序工具类

UniApp开发小程序生成海报
type: item
date: 2024-06-30T14:15:00+08:00
description: 具体实现效果就是上面这个图片。里面的元素都是自己根据自己实际情况拼接进去的。在开发小程序时，我们经常会遇到【分享】这个功能，在实现分享功能时，我们一般会基于当前页面，生成一个海报进行分享。在实现时，基本上是通过canvas画一个图片，然后生成一个图片，具体的可以看下面的代码。视图代码 <view :style="{marginTop: '30rpx'}"> <view class="container-price"> <view class="container-price-left"> <view style="display: flex;flex-direction: column;align-items: center;margin-left: 30rpx;" @click="shareing"> <view> <image src="../../static/img/share.svg" style="height: 46rpx;width: 46rpx;"></image> </view> <view style="font-size: 16rpx;height: 30rpx;">分享</view> </view> <view style="display: flex;flex-direction: column;align-items: center;margin-left: 30rpx;" @click="subscribeMsg4Bid"> <view> <image src="../../static/img/subscribe.svg" style="height: 46rpx;width: 46rpx;"> </image> </view> <view style="font-size: 16rpx;height: 30rpx;">订阅</view> </view> </view> </view> </view> <uni-popup ref="popupPosters" type="bottom" border-radius="10px 10px 0 0"> <view class="popup-posters-wrap"> <image :src="posterImg" mode="aspectFill" :style="{height:'420px',width:'100%'}"> </image> <view class="popup-footer" :style="{marginBottom : (0-safeAreaHeight) + 'px'}"> <view style="text-align: center;font-size: 20rpx;">立即分享</view> <view style="display: flex;flex-direction: row;font-size: 20rpx;margin: 0 20rpx;"> <view style="display: flex;flex-direction: column;align-items: center;"> <u-icon size="80" color="#03de6d" name="weixin-circle-fill"></u-icon> <view>微信好友</view> </view> <view style="display: flex;flex-direction: column;align-items: center;margin-left: 20rpx;"> <view @click="saveToLocal"> <u-icon size="80" color="#18a5f0" name="photo-fill"></u-icon> </view> <view>保存海报</view> </view> </view> <view :style="{bottom:(safeAreaHeight+5)+'px',left:'50%',position:'fixed',fontSize:'24rpx',transform:'translate(-50%,-50%)',padding:'5rpx 15rpx'}" @click="this.$refs.popupPosters.close()"> <view>取消</view> </view> </view> </view> </uni-popup>js代码//生成海报--微信端 createPoster() { let _this = this const canvasId = "myCanvas" let ctx = uni.createCanvasCont…

油耗笔记OilNote集成腾讯地图实现载在图选择加油站充电站功能
type: item
date: 2022-11-19T20:36:00+08:00
description: 油耗笔记OilNote经过两轮迭代，将地图服务由高德地图切换到百度地图，最终定格到了腾讯地图。在前面，我们已经实现了逆地址解析及周边搜索功能。接下来我们要实现打开地图，在图直接选择加油站功能。我们使用腾讯地图提供的微信小程序JavaScript SDK实现在线地图功能。一、微信小程序JavaScript SDK介绍腾讯位置服务为微信小程序提供了基础的标点能力、线和圆的绘制接口等地图组件和位置展示、地图选点等地图API位置服务能力支持，使得开发者可以自由地实现自己的微信小程序产品。 在此基础上，腾讯位置服务微信小程序JavaScript SDK是专为小程序开发者提供的LBS数据服务工具包，可以在小程序中调用腾讯位置服务的POI检索、关键词输入提示、地址解析、逆地址解析、行政区划和距离计算等数据服务，让您的小程序更强大！微信小程序JavaScript SDK开发文档二、微信小程序JavaScript SDK使用2.1、小程序域名设置在小程序管理后台 -> 开发 -> 开发管理 -> 开发设置 -> “服务器域名” 中设置request合法域名，添加https://apis.map.qq.com2.2、引入SDK下载微信小程序JavaScriptSDK，微信小程序JavaScriptSDK v1.1 JavaScriptSDK v1.2，并将解压后的.js文件引入工程中。2.3、页面引入2.3.1、在页面引入脚本文件 // 引入SDK核心类，js文件根据自己业务，位置可自行放置 var QQMapWX = require('../../common/utils/qqmap-wx-jssdk.min.js'); //引入下载好的sdk var qqmapsdk; // 实例化API核心类 qqmapsdk = new QQMapWX({ key: LocationUtils.TENCENT_MAP_KEY });2.3.2、查询加油站//页面加载 onLoad(options) { let that = this; this.latitude = uni.getStorageSync('latitude'); this.longitude = uni.getStorageSync('longitude'); this.oilType = options.oilType === '电' ? '充电站' : '加油站' let iconPath = 'https://oss.lisen.cc/oilnote/images/chargingPile.png'; if (options.oilType !== '电') { iconPath = 'https://oss.lisen.cc/oilnote/images/oilStation.png'; } // 调用接口 qqmapsdk.search({ keyword: encodeURI(that.oilType), location: { latitude: that.latitude, longitude: that.longitude }, page_size: 20, auto_extend: '1', success: function(res) { var selfLocation = { id: -1, latitude: that.latitude, longitude: that.longitude, width: 20, height: 20, iconPath: 'https://oss.lisen.cc/oilnote/images/location.png', title: "我的位置", callout: { content: '我的位置', color: '#adec9c', display: 'BYCLICK', borderRadius: 5, } }; that.makers.push(selfLocation); if (res.status === 0) { res.data.forEach((item, index) => { let obj = { id: index, latitude: item.location.lat, longitude: item.location.lng, width: 20, height: 20, iconPath: iconPath, title: item.title, callout: { content: item.title, bgColor: '#adec9c', display: 'ALWAYS', borderRadius: 5, } }; that.makers.push(obj); }) }…

油耗笔记OilNote周边搜索能力由百度地图切换成腾讯地图
type: item
date: 2022-11-18T21:51:00+08:00
description: 油耗笔记OilNote其实对地图搜索能力，其实就是查询周边加油站的功能进行过一次升级，上次升级是将地图搜索能力由高德地图切换成了百度地图，切换原因也非常简单，高德地图无耻的将每日额度改成了100，根本没法用了。具体切换过程，感兴趣的可以看看。油耗笔记OilNote周边搜索能力由高德地图切换成百度地图 - 李森的博客 (lisen.cc)一、为什么又不用百度地图了1.1、对搜索结果不满意其实，从高德地图切换到百度地图也不过一天的时间。之所以不使用百度地图，最根本的原因，是百度地图所谓的圆形搜索能力真的太差劲了。好多东西根本就搜不到。而且作为记录油耗的一个小程序，搜索周边加油站其实是最基础最基本的一个功能，但是百度地图的圆形搜索能力简直就弱爆了。明明旁边就有加油站，死活就是搜索不到。但是百度地图自己却能搜索到。比如这个地图是当时使用百度地图的搜索加油站的结果下面这个是切换到腾讯地图在相同位置的搜索加油站的结果但是，百度地图搜索的充电站数量好像也不少，跟腾讯地图也有差不多。1.2、坐标转换比较麻烦使用uni.getLocation()方法虽然获取到的坐标是gcj02，但是在百度地图下，得使用百度的坐标，必须转换一次，不然坐标误差是非常大的。1.3、官方文档写的太乱百度地图的官方文档写的，真的是一言难尽。1.4、资料很少貌似很多人都用高德的地图API，网上一搜很多，但是百度地图的相对就比较少了。虽然腾讯地图网上资料比较少，但是官网的开发文档还是很清晰明了的，这在一定程度上弥补了网上资料少的缺点。二、腾讯地图额度信息腾讯地图也是比较良心的，每日额度是三家里面最多的10000次/天，是高德地图的100倍，百度地图的2倍。缺点就是并发比较少，只有5次/秒。对于我们现阶段也是很充足的了。三、微信公众平台配置如果使用腾讯地图，必须提前把腾讯地图Api放到域名白名单里面。微信小程序后台，依次定位到开发管理→开发设置→服务器域名，在request合法域名里面加上百度地图的网址https://apis.map.qq.com/如果微信开发者工具提示域名不合法，记得刷新一下开发者工具的域名信息。刷新后记得重新编译项目。四、获取附近加油站功能改造其实三家地图的API都是类似的，我这里主要用了两个API，逆地址解析geocoder还有地点搜索search4.1、逆地址解析用于将uniapp获取到的经纬度解析成实际位置。let getLocation = function(radius = 1000, successFn) { var latitude = '' //纬度 var longitude = '' //经度 if (uni.getStorageSync('position') == '') { uni.getLocation({ geocode: true, type: 'gcj02', altitude: true, accuracy: 'best', isHighAccuracy: true, success: (res) => { console.log('位置是', res) latitude = res.latitude longitude = res.longitude uni.setStorageSync('latitude', latitude) uni.setStorageSync('longitude', longitude) uni.request({ url: 'https://apis.map.qq.com/ws/geocoder/v1/?key=' + TENCENT_MAP_KEY + '&location=' + latitude + ',' + longitude + '&get_poi=1&output=json', success: function(res) { let result = res.data.result let province = result.address_component.province let city = result.address_component.city // 省份名称 uni.setStorageSync('province', province) // 城市名称 uni.setStorageSync('city', city) // 城市编号 uni.setStorageSync('citycode', result.ad_info.city_code) successFn(result, province, city) }, fail(err) { console.log('获取位置信息失败：' + JSON.stringify(err)) } }) }, fa…

油耗笔记OilNote周边搜索能力由高德地图切换成百度地图
type: item
date: 2022-11-17T21:49:00+08:00
description: 一、交代下背景以前油耗笔记OilNote检索周边加油站功能，都是用的高德地图，起初，也不知道高德Api限制多少访问量，反正一直也没提示过超额，但是今天，莫名的收到了两条超额的短信。一开始收到80%超额的时候也没太当回事，觉得可能这个月改版，调试用的数量比较大，但是紧接着就收到了超额100%的提示，就感觉到不太对劲了，于是登录了高德后台，查询了一下配额。结果真是乖了个乖，搜索服务每日限额100次，这简直就是坑了，100次跟没有没啥区别，高德这是直接劝退个人开发者的节奏。查了下站内信，2022年10月26日发的站内信（没短信），通知2022年10月27日调整限额，连给你调整的时间都不给。坑你没商量呀。二、百度地图为什么选择百度地图呢？介个，国内好像除了高德就是百度了，不用也没办法。因为我就用到了地点检索，百度地图目前还算比较良心的，个人开发者每日限额是5000次。是高德地图的50倍。并发限制都是一样的30QPS。百度地图创建应用的时候，记得选择微信小程序，否则逆地理编码是不能用的。三、微信公众平台配置如果使用百度地图，必须提前把百度地图Api放到域名白名单里面。微信小程序后台，依次定位到开发管理→开发设置→服务器域名，在request合法域名里面加上百度地图的网址https://api.map.baidu.com如果微信开发者工具提示域名不合法，记得刷新一下开发者工具的域名信息。刷新后记得重新编译项目。三、功能改造既然决定了用百度地图，那么剩下的就是如何对程序进行改造了。因为我的小程序是使用uniapp开发的，所以这里就介绍一下uniapp的整个的改造过程。为了不影响在线版本，暂时先不删除高德域名。3.1、manifest.json改造manifest.json用于配置地图的Key，因为我之前使用的是高德的地图，切换到百度后，需要将相关配置改成百度地图的。其实我现在只是用了微信小程序，AK我直接在代码里面写死了，这里配置的appkey我感觉没啥用，对uniapp理解不是很深入，这块可能更多的是给App用的吧。我们Maps需要勾选百度地图并取消高德地图。具体的key，可以在百度开放平台，个人创建的应用中找到。3.2、获取附近加油站功能改造其实改造也比较简单，高德跟百度Api还是比较类似的。3.2.1、原来高德获取周边加油站的代码let getLocation = function(radius = 1000, successFn) { var latitude = '' //纬度 var longitude = '' //经度 if (uni.getStorageSync('position') == '') { uni.getLocation({ geocode: true, type: 'gcj02', altitude: true, accuracy: 'best', isHighAccuracy: true, success: (res) => { console.log('位置是', res) latitude = res.latitude longitude = res.longitude uni.setStorageSync('latitude', latitude) uni.setStorageSync('longitude', longitude) uni.request({ url: 'https://restapi.amap.com/v3/geocode/regeo?key=AK' + '&location=' + longitude + ',' + latitude + '&poitype=010100&radius=' + radius + '&extensions=all&batch=false&roadlevel=0', success: function(res) { var regeocode = res.data.regeocode // 省份名称 uni.setStorageSync('province', regeocode.addressComponent.province) // 城市名称 uni.setStorageSync('city', regeocode.addressComponent.city) // 城市编号 uni.setStorageSync('citycode', regeocode.addressComponent.citycode) successFn(regeocode) }, fail(err) { console.log('获取加油站信息失败：' + JSON.stringify(err)) } }) }, fail: (err) => { console.l…

解决小程序报错the api need to be declared in the requiredPrivateInfos field in app.json
type: item
date: 2022-11-15T21:06:00+08:00
description: 一、报错信息从报错信息可以大概看到，应该是缺少授权导致的。PS:这里真的想吐槽一下微信的接口，真的是三天两头的变，发个版就得调整一次，心累。二、微信官方文档说明打开微信开发文档查看配置信息。为了开发者能够正常使用获取模糊地理位置等接口，以及后续对于代码提审环节的优化，自 **2022 年 7 月 14 日起，开发者在使用地理位置相关接口时（共计 8 个），需要提前在 app.json 中进行配置。在代码中使用的地理位置相关接口（共计 8 个），开发者均需要在 app.json 中 requiredPrivateInfos 配置项中声明，代码格式如下：地理位置接口新增与相关流程调整 | 微信开放社区三、解决方法因为我是用uni-app框架开发的，所以是在manifest.json里面配置，如果是用其他框架开发的话，一般都是在app.json里面配置，不过原理都是一样的。

微信小程序获取用户头像后上传到七牛云
type: item
date: 2022-11-14T22:22:00+08:00
description: 一、事情起因【油耗笔记OilNote】小程序好久没有升级了，最近打算对代码进行一些优化，但是新版本突然发现无法获取到用户微信头像及微信昵称了。查阅官方文档才知道，官方有对getUserProfile接口进行调整了。自 2022 年 10 月 25 日 24 时后（以下统称 “生效期” ），用户头像昵称获取规则将进行如下调整：自生效期起，小程序 wx.getUserProfile 接口将被收回：生效期后发布的小程序新版本，通过 wx.getUserProfile 接口获取用户头像将统一返回默认灰色头像，昵称将统一返回 “微信用户”。生效期前发布的小程序版本不受影响，但如果要进行版本更新则需要进行适配。自生效期起，插件通过 wx.getUserInfo 接口获取用户昵称头像将被收回：生效期后发布的插件新版本，通过 wx.getUserInfo 接口获取用户头像将统一返回默认灰色头像，昵称将统一返回 “微信用户”。生效期前发布的插件版本不受影响，但如果要进行版本更新则需要进行适配。通过 wx.login 与 wx.getUserInfo 接口获取 openId、unionId 能力不受影响。「头像昵称填写能力」支持获取用户头像昵称：如业务需获取用户头像昵称，可以使用「头像昵称填写能力」（基础库 2.21.2 版本开始支持，覆盖iOS与安卓微信 8.0.16 以上版本），具体实践可见下方《最佳实践》。小程序 wx.getUserProfile 与插件 wx.getUserInfo 接口兼容基础库 2.27.1 以下版本的头像昵称获取需求：对于来自低版本的基础库与微信客户端的访问，小程序通过 wx.getUserProfile 接口将正常返回用户头像昵称，插件通过 wx.getUserInfo 接口将正常返回用户头像昵称，开发者可继续使用以上能力做向下兼容。现在只要是发布的新版本，默认都需要调整，不然就显示下面灰色头像，已经发布的版本不受影响。既然官方调整了，那么我们也只有被动接受的份。二、油耗笔记的开发框架油耗笔记OilNote不是直接使用微信开发者工具开发的，而是使用UniApp开发的，后端是SpringBoot。三、改进思路3.1、获取用户头像由于官方指导意见是，使用button组件 open-type 的值设置为 chooseAvatar，当用户选择需要使用的头像之后，可以通过 bindchooseavatar 事件回调获取到头像信息的临时路径。从官方的指导我们可以看到，微信并没有给我们返回一个具体的路径，只是返回了一个临时的路径，因此我们就必须自己获取到这个临时的文件，然后存储起来。3.2、使用七牛云由于使用的腾讯云的低配服务器，带宽、存储都比较捉襟见肘，所以我打算把头像都存储到七牛云上，既能减轻带宽压力也能节省服务器空间。四、具体改进4.1、UniApp页面改进当用户通过微信登录时，此时获取用户头像信息，如果头像存在，登录之后跳转到首页，否则跳转到个人信息界面，让用户维护头像及昵称，此方法适用于新用户，同时也适用于老用户重新登录。//微信授权登录 getUserInfo(e) { let that = this; var p = this.getSetting(); p.then(function(isAuth) { console.log('是否已经授权', isAuth); if (isAuth) { console.log('用户信息，加密数据', e); //eData 包括//微信头像//微信名称 还有加密的数据. // let eData = JSON.parse(e.detail.rawData); uni.getUserProfile({ desc: 'Wexin', // 这个参数是必须的 success: function(infoRes) { //接下来就是访问接口. that.$request( 'wechat/authCode2Session?code=' + that.weChatCode, 'POST' ).then(function(res) { if (res.code == 200) { //将接口返回的数据保存在全局变量中. let userInfo = {} // 用户id userInfo.id = res.data.id userInfo.username = res.data.username userInfo.tel = res.data.tel userInfo.email = res.data.email userInfo.wechatOpenId = res.data.wechatOpenId userInfo.nickName = res.data.nickName u…

微信小程序自定义不一样的tabBar
type: item
date: 2021-04-22T21:41:00+08:00
description: 实现过程其中扫码买单，这个按钮效果，微信自带的tabBar是无法实现的，其后尝试了下custom-tab-bar 也是无法实现。没办法了，既然微信的tabBar无法实现。那就自己弄个真-自定义tabBar来实现好了。各位看官莫慌，下面就把解决方案放上来。首先先来讲下解决方案的思路，然后再把代码送上。思路：微信的tabBar无法实现，那么就放弃微信的tabBar，改用Component 来实现。把微信自带的tabBar隐藏起来，用Component 做成伪tabBar并应该到页面上。把app.json下的 tabBar 给干掉。这样在首页就不会有tabBar显示了。写Component 伪tabBar并应该到页面上。代码app.json删除 tabBar自定义component <view class="tabBar"> <view class="cont"> <block wx:for="{{tabBar}}" wx:for-item="item" wx:key="tabBar"> <view class="item {{item.switchQr?'switchQr':''}}" catchtap="" data-url="{{item.pagePath}}" data-index="{{index}}"> <image class="ico" src="{{idx === index ? item.selectedIconPath : item.iconPath}}"></image> <view class="txt {{idx === index ? 'selectedColor' : ''}}">{{item.text}} </view> </view> </block> </view> </view>// Componet/tabBar/tabBar.js Component({ /** * 组件的属性列表 */ properties: { idx: { type: Number, value: 0 }, }, /** * 组件的初始数据 */ data: { tabBar: [{ "pagePath": "../../pages/index/index", "text": "首页", "switchQr": false, "iconPath": "/image/home.svg", "selectedIconPath": "/image/home_sel.svg", }, { "pagePath": "", "text": "扫码买单", "switchQr": true, "iconPath": "/image/saoma.svg", }, { "pagePath": "../../pages/user/user", "text": "我的", "switchQr": false, "iconPath": "/image/mine.svg", "selectedIconPath": "/image/mine_sel.svg", }, ] }, /** * 组件的方法列表 */ methods: { goToTab: function (e) { //如果点击当前页面则不进行跳转 if (this.data.idx == e.currentTarget.dataset.index) { return false } wx.navigateTo({ url: e.currentTarget.dataset.url }) }, // 扫码 switchQr() { // console.log('扫码') }, } })/* Componet/tabBar/tabBar.wxss */ .tabBar { width: 100%; position: fixed; bottom: 0; font-size: 20rpx; color: #8A8A8A; background: #fff; /* border-top: 2rpx solid #eee; */ box-shadow: 0rpx 1rpx 6rpx rgba(0,0,0,0.3); } .cont { margin-top: 10rpx; padding: 0; z-index: 0; height: calc(100rpx + env(safe-area-inset-bottom) / 2); padding-bottom: calc(env(safe-area-inset-bottom) / 2); display: flex; } .cont .item { font-si…

Spring Boot 封装微信小程序工具类
type: item
date: 2021-03-16T20:24:00+08:00
description: /** * Description: 微信小程序工具类 * * @author : laughing * DateTime: 2021-03-16 11:10 */ @Slf4j public class WeChatUtils { public static String httpRequest(String requestUrl, String requestMethod, String output) { try { URL url = new URL(requestUrl); HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); connection.setDoOutput(true); connection.setDoInput(true); connection.setUseCaches(false); connection.setRequestMethod(requestMethod); if (null != output) { OutputStream outputStream = connection.getOutputStream(); outputStream.write(output.getBytes(StandardCharsets.UTF_8)); outputStream.close(); } // 从输入流读取返回内容 InputStream inputStream = connection.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String str; StringBuilder buffer = new StringBuilder(); while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } bufferedReader.close(); inputStreamReader.close(); inputStream.close(); connection.disconnect(); return buffer.toString(); } catch (Exception e) { e.printStackTrace(); } return ""; } public static String decryptData(String encryptDataB64, String sessionKeyB64, String ivB64) { log.info("encryptDataB64:" + encryptDataB64); log.info("sessionKeyB64:" + sessionKeyB64); log.info("ivB64:" + ivB64); return new String( decryptOfDiyIv( Base64.decode(encryptDataB64), Base64.decode(sessionKeyB64), Base64.decode(ivB64) ) ); } private static final String KEY_ALGORITHM = "AES"; private static final String ALGORITHM_STR = "AES/CBC/PKCS7Padding"; private static Key key; private static Cipher cipher; private static void init(byte[] keyBytes) { // 如果密钥不足16位，那么就补足. 这个if 中的内容很重要 int base = 16; if (keyBytes.length % base != 0) { int groups = keyBytes.length / base + 1; byte[] temp = new byte[groups * base]; Arrays.fill(temp, (byte) 0); System.arraycopy(keyBytes, 0, temp, 0, keyBytes.length); keyBytes = temp; } // 初始化 S…