- 【实战记录】小程序登录态维护的分享
背景: 最近公司有新项目要启动,之前项目在登录的流程中需要用户授权手机号,不符合小程序运营规范,故决定此次进行梳理整改一下登录的相关流程。 分析: 根据官方的示例(https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html),针对C端业务token的管理,输出该流程图,如下所示: [图片] 代码实现: app.js // 登录 login() { return new Promise((resolve, reject) => { const userInfo = wx.getStorageSync('userinfo') if (userInfo) { resolve(userInfo) } else { wx.login({ success: (res) =>{ console.log(res) // 获取业务token,及个人信息接口 setTimeout(() => { const res = { token: "iahsoiyiuaiu798=23492ib2hbjds823sdkajhgkas=", userInfo: { mobile: '18755131234', nickName: 'xxx', imgUrl: 'https:/xxxx' } } wx.setStorageSync('userinfo', res) resolve(res) }, 0) } }) } }) xxx页面.js: const app = getApp() Page({ data: { isBandMobile: false // 是否绑定手机号 }, onLoad() { // 登录 app.login().then(res => { console.log(res) const { userInfo: { mobile } } = res this.setData({ isBandMobile: Boolean(mobile) }) }) }, // 绑定手机号 getPhoneNumber (e) { console.log(e.detail.code) }, // 业务 todo() { // todo }, }) request.js: let isRefreshing = true; let subscribers = []; function onAccessTokenFetched() { subscribers.forEach((callback) => { callback(); }) subscribers = []; } function addSubscriber(callback) { subscribers.push(callback) } export class Http { constructor() {} request({ url, data = {}, method, header, callback = ''} = {}) { let _this = this; return new Promise((resolve, reject) => { wx.request({ url, data, method, header: { Authorization: 'Bearer ' + wx.getStorageSync('userinfo').token }, callback, fail(res) { reject(res) }, complete: res => { if (callback) return callback(res.data); let statusCode = res.statusCode; if (statusCode == 404) { console.log('接口不存在') } else if (statusCode == 401) { // 将需要重新执行的接口缓存到一个队列中 addSubscriber(() => { _this.request({ url, data, method, header, callback: resolve }) }) if (isRefreshing) { refreshToken(url, data).then(() => { // 依次去执行缓存的接口 onAccessTokenFetched(); isRefreshing = true; }) } isRefreshing = false; } else if (statusCode == 200) { resolve(res.data) } else if (statusCode.startsWith('5')) { wx.showModal({ content: '服务器报错,请重试!', showCancel: false }); } } }) }) } } // 获取token const refreshToken = () => { return new Promise((resolve, reject) => { wx.login({ success: (res) =>{ console.log(res) // 获取业务token,及个人信息接口 setTimeout(() => { const res = { token: "iahsoiyiuaiu798=23492ib2hbjds823sdkajhgkas=", userInfo: { mobile: '18755131234', nickName: 'xxx', imgUrl: 'https:/xxxx' } } wx.setStorageSync('userinfo', res) resolve(res) }, 0) } }) }) } 代码片段:https://developers.weixin.qq.com/s/d2uFwHm17mBg 总结: 此流程中主要将获取用户手机号,从登录流程中剥离出去,使得登录流程更加解耦,以用户openid作为唯一标识,将授权手机号后置,当具体某业务中需要用户手机号时,再去获取。根据自己的业务需要,在“个人中心”页面也可以提供修改绑定手机号的入口即可。关于头像和昵称的话,也在个人中心去获取即可。
2022-08-22 - 小程序搜索关键字突出显示
先看效果: [图片] 实现思路: 循环搜索出来的数据,将每条数据再绑定在组件上,在组件中通过observer监听数据,将每条数据的所有关键词都替换成特殊字符包裹的关键词 如:str.replace(new RegExp(`${key}`, 'g'), `**${key}**`) 再通过该字符进行分割转化成数组,遍历数组,判断是否为关键字给出突出样式。 observer 用于监听和响应任何属性和数据字段的变化。可以同时监听多个。一次 setData 最多触发每个监听器一次。同时也可以监听子数据字段 代码片段:https://developers.weixin.qq.com/s/GEHhn7mP77m4
2020-11-25 - banner效果美化
先上图 [图片] wxss: .bannerSwiper { height:300rpx; width: 100vw; position: absolute; left: 0rpx; top: 10rpx; } .imageBanner { width: 100%; height: 100%; border-radius: 20rpx; position: relative; padding: 0; line-height: 100%; background: transparent; text-align: left; } .imageBanner_small { transform: scale(0.9); transition: 0.2s, ease-in 0s; border-radius: 10rpx; width: 100%; height: 100%; position: absolute; bottom: 0; z-index: 100; padding: 0; line-height: 100%; background: transparent; text-align: left; opacity: 0.5; } wxml: <view class="container"> <swiper class='bannerSwiper' previous-margin="80rpx" next-margin='80rpx' indicator-dots="true" indicator-color='#999' indicator-active-color='#fff' indicator-dots="{{indicatorDots}}" autoplay="{{autoplay}}" interval="{{interval}}" duration="{{duration}}" bindchange='onChange' circular='true'> <block wx:for="{{banner}}"> <swiper-item> <image class="{{index==selindex?'imageBanner':'imageBanner_small'}}" style="background:{{item}}"></image> </swiper-item> </block> </swiper> </view> github地址:https://github.com/jieqwer/-Banner
2020-11-27 - 实现最简单的公告滚动效果
[图片] wxml: <view style="--width:{{width}}px;--timer:{{timer}}s;"> <view class="marquee_text" style="font-size:{{size}}px">{{text}}</view> </view> wxss: @keyframes move { from { margin-left: 100%; } to { margin-left: var(--width); /* var接受传入的变量 */ } } .marquee_text{ display: inline-block; margin-left: 100%; white-space: nowrap; animation: move var(--timer) infinite linear; font-weight: bold;background: url('https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2025728819,3580500436&fm=26&gp=0.jpg');background-size: contain; -webkit-background-clip: text;color: transparent; } js: Page({ data: { text: '成都近日新增确诊新冠患者3例,请大家做好防护,外出请戴好口罩!', size: 14,//宽度即文字大小 moveTimes: 8,//刚好文字宽度等于屏幕宽度所需的时间 width: 0,//文字宽度 timer: 0 //滚动时间 }, onLoad: function () { var screenW = wx.getSystemInfoSync().windowWidth;//获取屏幕宽度 var contentW = this.data.text.length * this.data.size;//获取文本大概宽度 var timer = (contentW / screenW) * this.data.moveTimes;//动态计算文字滚动时间 timer = timer < 8 ? 8 : timer; //判断时间是否小于8s this.setData({ width: -contentW, timer: timer }); } }) 小结:wxss里面使用变量,在js中给定数据,wxml中通过内联样式动态绑定自定义属性--width(--width--这样也行,获取就是var(--width--))的值,wxss中通过var(--width)的方式接收变量的值,剩下的看注释,都备注上了。
2020-12-08 - 简单实现签到日历效果
wxml: <view class="box"> <view class="section"> <picker mode="date" value="{{date}}" fields="month" start="2010-01-01" end="{{cy+'-'+cm}}" bindchange="bindDateChange"> <view class="picker">{{cur_year || "--"}} 年 {{cur_month || "--"}} 月</view> </picker> </view> <view> <!-- 显示星期 --> <view class="week color9b"> <view wx:for="{{weeks_ch}}" wx:key="unique">{{item}}</view> </view> <view class='days'> <!-- 行 --> <view class="rows" wx:for="{{days.length/7}}" wx:for-index="i" wx:key="unique"> <!-- 列 --> <view class="columns" wx:for="{{7}}" wx:for-index="k" wx:key="unique"> <!-- 每个月份的空的单元格 --> <view class='cell' wx:if="{{days[7*i+k].date == null}}"> <text decode="{{true}}"> </text> </view> <!-- 每个月份的有数字的单元格 --> <view class='cell' wx:else> <!-- 当前日期已签到 --> <view wx:if="{{days[7*i+k].isSign == true}}" class='qianbg'> <text class="colorff">{{days[7*i+k].date}}</text> <text class="sourse">+{{days[7*i+k].Score}}</text> </view> <!-- 当前日期未签到 --> <view wx:else> <text>{{days[7*i+k].date}}</text> </view> </view> </view> </view> </view> </view> </view> 简单提下思路,首先默认确定当前年月,cy cm, 初始化:获取days遍历日历的格子,通过获取当前月第一天是星期几来判断前面有几个空格,放入days,再当月天数放入days,然后进行渲染,再通接口去拿签到信息,签到成功的突出显示。这里签到初始化时我默认给了标识isSign,将已签到列表和当前年月日进行比较,符合条件则更新签到状态。切换选择日期,这里我用的是选择器,当然可以写成点击左侧按钮上一月,右侧按钮下一月那种,重新选择日期后,initdata(e) 传入年月,就是当前选择年月的数据。将星期日作为第一日的我也备注上去了。样式根据自己的喜好改就行了,最后看看我写的两个项目效果: [图片][图片] 写了个demo:https://developers.weixin.qq.com/s/SSlwjGmb7Wm9
08-14 - 如何使用scroll-view制作左右滚动导航条效果
最新:2020/06/13。修改为scroll-view与swiper联动效果,新增下拉刷新以及上拉加载效果。。具体效果查看代码片段,以下文章内容和就不改了 刚刚在社区里看到 有老哥在问如何做滚动的导航栏。这里简单给他写了个代码片段,需要的大哥拿去随便改改,先看效果图: [图片] 代码如下: wxml [代码]<scroll-view class="scroll-wrapper" scroll-x scroll-with-animation="true" scroll-into-view="item{{currentTab < 4 ? 0 : currentTab - 3}}" > <view class="navigate-item" id="item{{index}}" wx:for="{{taskList}}" wx:key="{{index}}" data-index="{{index}}" bindtap="handleClick"> <view class="names {{currentTab === index ? 'active' : ''}}">{{item.name}}</view> <view class="currtline {{currentTab === index ? 'active' : ''}}"></view> </view> </scroll-view> [代码] wxss [代码].scroll-wrapper { white-space: nowrap; -webkit-overflow-scrolling: touch; background: #FFF; height: 90rpx; padding: 0 32rpx; box-sizing: border-box; } ::-webkit-scrollbar { width: 0; height: 0; color: transparent; } .navigate-item { display: inline-block; text-align: center; height: 90rpx; line-height: 90rpx; margin: 0 16rpx; } .names { font-size: 28rpx; color: #3c3c3c; } .names.active { color: #00cc88; font-weight: bold; font-size: 34rpx; } .currtline { margin: -8rpx auto 0 auto; width: 100rpx; height: 8rpx; border-radius: 4rpx; } .currtline.active { background: #47CD88; transition: all .3s; } [代码] JS [代码]const app = getApp() Page({ data: { currentTab: 0, taskList: [{ name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, { name: '有趣好玩' }, ] }, onLoad() { }, handleClick(e) { let currentTab = e.currentTarget.dataset.index this.setData({ currentTab }) }, }) [代码] 最后奉上代码片段: https://developers.weixin.qq.com/s/nkyp64mN7fim
2020-06-13 - 如何实现一个简单的swiper效果
简单的siwper效果,又是逛社区发现老哥提的想要该效果,那么有需要,咱们就得上啊: 效果图: [图片] 上代码: wxml [代码]<view class="container"> <swiper duration="200" previous-margin="140rpx" next-margin="140rpx" bindchange="currentHandle" circular="{{true}}" class="swiper-out"> <block wx:for="{{punchList}}" wx:key="*this"> <swiper-item class="swp-item {{current === index ?'active-item': ''}}"> <view class="slide-image" style=" background: url({{item.bannerUrl}}) no-repeat center center;background-size: 100% 100%;" id="{{index}}"></view> </swiper-item> </block> </swiper> <view class="swp-dot"> <view class="square-12 m-r-8 {{current === index ?'active': ''}}" wx:for="{{punchList}}" wx:key="{{index}}"></view> </view> </view> [代码] JS [代码]const app = getApp() Page({ data: { punchList: [{ "bannerUrl": "https://qiniu-image.qtshe.com/1536067857379_122.png" }, { "bannerUrl": "https://qiniu-image.qtshe.com/1536068379879_115.png", }, { "bannerUrl": "https://qiniu-image.qtshe.com/1536068319939_230.png", }, { "bannerUrl": "https://qiniu-image.qtshe.com/1536068074140_695.png", }, { "bannerUrl": "https://qiniu-image.qtshe.com/1536068213758_796.png", }], current: 0 }, currentHandle(e) { let { current } = e.detail this.setData({ current }) } }) [代码] wxss [代码].container { display: flex; flex-direction: column; align-items: center; justify-content: space-between; height: 100vh; } .slide-image { height: 600rpx; width: 400rpx; margin-top: 20rpx; overflow: hidden; display: flex; flex-direction: column; align-items: center; justify-content: center; border-radius: 10rpx; } .swiper-out { width: 750rpx; height: 660rpx; margin-top: 60rpx; } .active-item .slide-image { box-shadow: 0 5rpx 20rpx 3rpx rgba(0, 0, 0, 0.15); } .swp-item { width: 400rpx; display: flex; flex-direction: column; align-items: center; padding-top: 4rpx; opacity: 0.6; } .active-item { opacity: 1; } .swp-dot { display: flex; justify-content: center; flex: 1; margin-top: 18rpx; } .m-r-8 { margin-right: 8rpx; } .m-l-8 { margin-right: 8rpx; } .square-12 { width: 12rpx; height: 12rpx; background-color: #d8d8d8; border-radius: 6rpx; transition: width 0.2s linear; } .active { background-color: #3c3c3c; width: 36rpx; transition: width 0.2s linear; } [代码] 代码放完了,看下效果吧。代码片段如下: https://developers.weixin.qq.com/s/DCK6HJmw7kaZ
2019-12-19 - 如何实现一个自定义数据版省市区二级、三级联动
社区可能有其他的方案了,但是再分享下吧,给有需要的童鞋。 效果图: [图片] 额,这个视频转GIF因为社区上传不了大图,所以剪了一部分,具体的效果还是直接工具打开代码片段预览吧~ 第一步:你的页面JSON引入该组件: [代码]{ "usingComponents": { "city-picker": "/components/cityPicker/index" } } [代码] 第二步:你的页面WXML引入该组件 [代码]<city-picker visible="{{visible}}" column="2" bind:close="handleClick" bind:confirm="handleConfirm" /> [代码] 第三步:你的页面JS调用 [代码]// 显示/隐藏picker选择器 handleClick() { this.setData( visible: !this.data.visible }) }, // 用户选择城市后 点击确定的返回值 handleConfirm(e) { const { detail: { provinceName = '', provinceId = '', cityName, cityId='', areaName = '', areaId = '' } = {} } = e this.setData({ cityId, cityName, areaId, areaName, provinceId, provinceName }) } [代码] 组件属性 属性 默认值 描述 visible false 是否显示picker选择器 column 3 显示几列,可选值:1,2,3 values [0, 0, 0] 必填,默认回填的省市区下标,可选择具体省市区后查看AppData的regionValue字段 close function 点击关闭picker弹窗 confirm function 点击选择器的确定返回值 confirm: 属性 默认值 描述 provinceName 北京市 省份名称 provinceId 110000 省份ID cityName 市辖区 城市名称 cityId 110100 城市ID areaName 东城区 区域名称 areaId 110000 区域Id 至于怎么获取你想默认城市的下标,可以滑动操作下选中省市区后,点击确定后查看appData里的regionValue的值。 以上就是一个自定义数据版本的省市区二级、三级联动啦,老规矩,结尾放代码片段。 https://developers.weixin.qq.com/s/F9k9cTmT7LAz
2022-07-20 - 如何实现一个简单的http请求的封装
好久没发文章了,最近浏览社区看到比较多的请求封装,以及还有在使用原始请求的童鞋。为了减少代码,提升观赏性,我也水一篇吧,希望对大家有所帮助。 默认请求方式,大家每次都这样一些写相同的代码,会不会觉得烦,反正我是觉得头大 😂 [代码]wx.request({ url: 'test.php', //仅为示例,并非真实的接口地址 data: { x: '', y: '' }, header: { 'content-type': 'application/json' // 默认值 }, success (res) { console.log(res.data) } }) [代码] 来,进入正题吧,把这块代码封装下。 首先新建个request文件夹,内含request.js 代码如下: [代码]/** * 网络请求封装 */ import config from '../config/config.js' import util from '../util/util.js' // 获取接口地址 const _getPath = path => (config.DOMAIN + path) // 封装接口公共参数 const _getParams = (data = {}) => { const timestamp = Date.now() //时间戳 const deviceId = Math.random() //随机数 const version = data.version || config.version //当前版本号,自定或者取小程序的都行 const appKey = data.appKey || config.appKey //某个小程序或者客户端的字段区分 //加密下,防止其他人随意刷接口,加密目前采用的md5,后端进行校验,这段里面的参数你们自定,别让其他人知道就行,我这里就是举个例子 const sign = data.sign || util.md5(config.appKey + timestamp + deviceId) return Object.assign({}, { timestamp, sign, deviceId, version, appKey }, data) } // 修改接口默认content-type请求头 const _getHeader = (headers = {}) => { return Object.assign({ 'content-type': `application/x-www-form-urlencoded` }, headers) } // 存储登录态失效的跳转 const _handleCode = (res) => { const {statusCode} = res const {msg, code} = res.data // code为 4004 时一般表示storage里存储的token失效或者未登录 if (statusCode === 200 && (code === 4004)) { wx.navigateTo({ url: '/pages/login/login' }) } return true } /** * get 请求, post 请求 * @param {String} path 请求url,必须 * @param {Object} params 请求参数,可选 * @param {String} method 请求方式 默认为 POST * @param {Object} option 可选配置,如设置请求头 { headers:{} } * * option = { * headers: {} // 请求头 * } * */ export const postAjax = (path, params) => { const url = _getPath(path) const data = _getParams(params) //如果某个参数值为undefined,则删掉该字段,不传给后端 for (let e in data) { if (data[e] === 'undefined') { delete data[e] } } // 处理请求头,加上最近比较流行的jwtToken(具体的自己百度去) const header = util.extend( true, { "content-type": "application/x-www-form-urlencoded", 'Authorization': wx.getStorageSync('jwtToken') ? `Bearer ${wx.getStorageSync('jwtToken')}` : '', }, header ); const method = 'POST' return new Promise((resolve, reject) => { wx.request({ url, method, data, header, success: (res) => { const result = _handleCode(res) result && resolve(res.data) }, fail: function (res) { reject(res.data) } }); }) } [代码] 那么如何调用呢? [代码]//把request的 postAjax注册到getApp()下,调用时: const app = getApp() let postData = { //这里填写请求参数,基础参数里的appKey等参数可在这里覆盖传入。 } app.postAjax(url, postData).then((res) => { if (res.success) { //这里处理请求成功逻辑。 } else { //wx.showToast大家觉得麻烦也可以写到util.js里,调用时:util.toast(msg) 即可。 wx.showToast({ title: res.msg || '服务器错误,请稍后重试', icon: "none" }) } }).catch(err => { //这里根据自己场景看是否封装到request.js里 console.log(err) }) [代码] config.js 主要是处理正式环境、预发环境、测试环境、开发环境的配置 [代码]//发版须修改version, env const env = { dev: { DOMAIN: 'https://dev-api.weixin.com' }, test: { DOMAIN: 'https://test-api.weixin.com', }, pro: { DOMAIN: 'https://api.qtshe.com' } } module.exports = { ...env.pro } [代码] 以上就是简单的一个request的封装,包含登录态失效统一跳转、包含公共参数的统一封装。 老规矩,最后放代码片段,util里内置了md5方法以及深拷贝方法,具体的我也不啰嗦,大家自行查看即可~ https://developers.weixin.qq.com/s/gbPSLOmd7Aft
2020-04-03 - 如何实现一个带图标的actionSheet效果
刚才有童鞋询问如何给actionSheet前加图标,然鹅官方没提供该效果,那么我们自己做一个定制化的actionSheet吧。 还是那句话,有需求,咱们有空就去实现下,也算是能锻炼自己的动手能力,提升点知识面。。 先看看实现后的效果图: [图片] 首先自定义actionSheet组件。 wxml 简单的页面布局。 [代码]<view class="qts-as-mask qts-class-mask {{ visible ? 'qts-as-mask-show' : '' }}" bindtap="handleClickMask" catchtouchmove="preventTouchmove" ></view> <view class="qts-class qts-as {{ visible ? 'qts-as-show' : '' }}" catchtouchmove="preventTouchmove"> <view class="qts-as-header qts-class-header"> <slot name="header"></slot> </view> <view class="qts-as-actions"> <view class="qts-as-action-item" wx:for="{{actions}}" wx:key="name"> <button hover-class="none" bindtap="handleClickItem" data-index="{{index}}" open-type="{{item.openType}}" class="qts-as-btn-qts ptp_exposure" > <image wx:if="{{item.icon}}" src="{{item.icon}}" class="qts-btn-image" /> <view class="qts-as-btn-text">{{item.name}}</view> </button> </view> </view> <view class="qts-as-cancel" wx:if="{{showCancel}}"> <button hover-class="none" class="qts-as-cancel-btn" bindtap="handleClickCancel">取消</button> </view> </view> [代码] js处理: 定制了三种功能: 是否需要隐藏取消按钮 是否需要点击蒙层关闭actionSheet 内容外部组件可配置 [代码]Component({ externalClasses: ['qts-class', 'qts-class-mask', 'qts-class-header'], options: { multipleSlots: true }, properties: { showCancel: { type: Boolean, value: true }, visible: { type: Boolean, value: false }, actions: { type: Array, value: [] }, maskClosable: { type: Boolean, value: true } }, methods: { handleClickMask() { if (!this.data.maskClosable) return; this.handleClickCancel(); }, preventTouchmove() {}, handleClickItem(e) { const dataset = e.currentTarget.dataset || '' this.triggerEvent('click', dataset) this.triggerEvent('cancel'); }, handleClickCancel() { this.triggerEvent('cancel'); } } }) [代码] 应用的页面引用该组件: [代码]<view class="container" bindtap="handleClick">点我弹出actionSheet</view> <!-- visible 弹窗显隐藏 show-cancel 是否显示取消按钮 bind:cancel 取消按钮点击回调 bind:click 单个item点击回调 mask-closable 点击蒙层是否关闭弹窗 --> <action-sheet visible="{{visible}}" actions="{{actions}}" show-cancel bind:cancel="handleClick" bind:click="handleClickItem" mask-closable="{{true}}" /> [代码] [代码]const app = getApp() Page({ data: { //可配置items选项,支持传入button的openType属性 actions: [{ name: '生成图片', icon: 'https://qiniu-image.qtshe.com/201809104.png' }, { name: '转发给好友或群聊天', icon: 'https://qiniu-image.qtshe.com/201809105.png', openType: 'share' } ], visible: false }, //弹窗显隐藏 handleClick() { this.setData({ visible: !this.data.visible }) }, //单个item点击 //actionsheet内的点击方法 handleClickItem({detail}) { if (detail.index === 0) { console.log(111) //第一个选项被点击 } }, }) [代码] 以上就是一个自定义actionSheet的实现方式以及引用方式。。 老规矩,附上代码片段。 https://developers.weixin.qq.com/s/Bhc0Sjmw7AgW
2020-05-26