- for in循环中,如何根据索引号使用this.setData()?
data中的数据: [代码]data:{[代码] [代码] AccountInfo: {[代码] [代码] [代码][代码]Company: [代码][代码]null[代码][代码],[代码][代码] [代码][代码]UserName: [代码][代码]null[代码][代码],[代码][代码] [代码][代码]PinyinName: [代码][代码]null[代码][代码],[代码][代码] [代码][代码]LoginDate: [代码][代码]null[代码][代码],[代码][代码] [代码][代码]Remarks: [代码][代码]null[代码][代码],[代码] [代码] }[代码] [代码]},[代码] 代码块使用: [代码]for[代码] [代码](let i [代码][代码]in[代码] [代码]this[代码][代码].data.AccountInfo) {[代码][代码] [代码][代码]if[代码] [代码](i === 'Remarks') {[代码][代码] [代码][代码]this[代码][代码].setData({[代码][代码] [代码][代码]AccountInfo[i]: value,[代码][代码] [代码][代码]});[代码][代码] [代码][代码]}[代码][代码]}[代码] 使用想法: 类似于for循环遍历数组,根据数组的下标来修改对于下标的值; 在小程序中,使用for in 循环,遍历AccountInfo对象的属性,根据属性 key ,通过this.setData()修改页面数据 请问,在this.setData()中,key如何写???? 即 [代码]this[代码][代码].setData({[代码][代码] [代码][代码]AccountInfo[i]: value,[代码][代码] [代码][代码]});[代码]这种写法报错,请高手指教。
2019-11-14 - 小程序可以按需引入vant weapp吗?如何引入?
我只想把需要的组件引入,但是一安装就是全部的安装上了,导致小程序的运行速度减慢了~ 目前这些都被引入了,我也想问下,这个会算入上传代码时总体积吗? [图片]
2020-08-13 - 设计一个小程序请求库?
前言 最近想写一个可以适配多平台的请求库,在研究 xhr 和 fetch 发现二者的参数、响应、回调函数等差别很大。想到如果请求库想要适配多平台,需要统一的传参和响应格式,那么势必会在请求库内部做大量的判断,这样不但费时费力,还会屏蔽掉底层请求内核差异。 阅读 axios 和 umi-request 源码时想到,请求库其实基本都包含了拦截器、中间件和快捷请求等几个通用的,与具体请求过程无关的功能。然后通过传参,让用户接触底层请求内核。问题在于,请求库内置多个底层请求内核,内核支持的参数是不一样的,上层库可能做一些处理,抹平一些参数的差异化,但对于底层内核的特有的功能,要么放弃,要么只能在参数列表中加入一些具体内核的特有的参数。比如在 axios 中,它的请求配置参数列表中,罗列了一些 browser only的参数,那对于只需要在 node 环境中运行的 axios 来说,参数多少有些冗余,并且如果 axios 要支持其他请求内核(比如小程序、快应用、华为鸿蒙等),那么参数冗余也将越来越大,扩展性也差。 换个思路来想,既然实现一个适配多平台的统一的请求库有这些问题,那么是否可以从底向上的,针对不同的请求内核,提供一种方式可以很方便的为其赋予拦截器、中间件、快捷请求等几个通用功能,并且保留不同请求内核的差异化? 设计实现 我们的请求库要想与请求内核无关,那么只能采用内核与请求库相分离的模式。使用时,需要将请求内核传入,初始化一个实例,再进行使用。或者基于请求库,传入内核,预置请求参数来进行二次封装。 基本架构 首先实现一个基本的架构 [代码]class PreQuest { constructor(private adapter) request(opt) { return this.adapter(opt) } } const adapter = (opt) => nativeRequestApi(opt) // eg: const adapter = (opt) => fetch(opt).then(res => res.json()) // 创建实例 const prequest = new PreQuest(adapter) // 这里实际调用的是 adapter 函数 prequest.request({ url: 'http://localhost:3000/api' }) [代码] 可以看到,这里饶了个弯,通过实例方法调用了 adapter 函数。 这样的话,为修改请求和响应提供了想象空间。 [代码]class PreQuest { // ...some code async request(opt){ const options = modifyReqOpt(opt) const res = await this.adapter(options) return modifyRes(res) } // ...some code } [代码] 中间件 可以采用 koa 的洋葱模型,对请求进行拦截和修改。 中间件调用示例: [代码]const prequest = new PreQuest(adapter) prequest.use(async (ctx, next) => { ctx.request.path = '/perfix' + ctx.request.path await next() ctx.response.body = JSON.parse(ctx.response.body) }) [代码] 实现中间件基本模型? [代码]const compose = require('koa-compose') class Middleware { // 中间件列表 cbs = [] // 注册中间件 use(cb) { this.cbs.push(cb) return this } // 执行中间件 exec(ctx, next){ // 中间件执行细节不是重点,所以直接使用 koa-compose 库 return compose(this.cbs)(ctx, next) } } [代码] 全局中间件,只需要添加一个 use 和 exec 的静态方法即可。 PreQuest 继承自 Middleware 类,即可在实例上注册中间件。 那么怎么在请求前调用中间件? [代码]class PreQuest extends Middleware { // ...some code async request(opt) { const ctx = { request: opt, response: {} } // 执行中间件 async this.exec(ctx, async (ctx) => { ctx.response = await this.adapter(ctx.request) }) return ctx.response } // ...some code } [代码] 中间件模型中,前一个中间件的返回值是传不到下一个中间件中,所以是通过一个对象在中间件中传递和赋值。 拦截器 拦截器是修改参数和响应的另一种方式。 首先看一下 axios 中拦截器是怎么用的。 [代码]import axios from 'axios' const instance = axios.create() instance.interceptor.request.use( (opt) => modifyOpt(opt), (e) => handleError(e) ) [代码] 根据用法,我们可以实现一个基本结构 [代码]class Interceptor { cbs = [] // 注册拦截器 use(successHandler, errorHandler) { this.cbs.push({ successHandler, errorHandler }) } exec(opt) { return this.cbs.reduce( (t, c, idx) => t.then(c.successHandler, this.handles[idx - 1]?.errorHandler), Promise.resolve(opt) ) .catch(this.handles[this.handles.length - 1].errorHandler) } } [代码] 代码很简单,有点难度的就是拦截器的执行了。这里主要有两个知识点: Array.reduce 和 Promise.then 第二个参数的使用。 注册拦截器时,[代码]successHandler[代码] 与 [代码]errorHandler[代码] 是成对的, successHandler 中抛出的错误,要在对应的 errorHandler 中处理,所以 errorHandler 接收到的错误,是上一个拦截器中抛出的。 拦截器怎么使用呢? [代码]class PreQuest { // ... some code interceptor = { request: new Interceptor() response: new Interceptor() } // ...some code async request(opt){ // 执行拦截器,修改请求参数 const options = await this.interceptor.request.exec(opt) const res = await this.adapter(options) // 执行拦截器,修改响应数据 const response = await this.interceptor.response.exec(res) return response } } [代码] 拦截器中间件 拦截器也可以是一个中间件,可以通过注册中间件来实现。请求拦截器在 [代码]await next()[代码] 前执行,响应拦截器在其后。 [代码]const instance = new Middleware() instance.use(async (ctx, next) => { // Promise 链式调用,更改请求参数 await Promise.resolve().then(reqInterceptor1).then(reqInterceptor2)... // 执行下一个中间件、或执行到 this.adapter 函数 await next() // Promise 链式调用,更改响应数据 await Promise.resolve().then(resInterceptor1).then(resInterceptor2)... }) [代码] 拦截器有请求拦截器和响应拦截器两类。 [代码]class InterceptorMiddleware { request = new Interceptor() response = new Interceptor() // 注册中间件 register: async (ctx, next) { ctx.request = await this.request.exec(ctx.request) await next() ctx.response = await thie.response.exec(ctx.response) } } [代码] 使用 [代码]const instance = new Middleware() const interceptor = new InterceptorMiddleware() // 注册拦截器 interceptor.request.use( (opt) => modifyOpt(opt), (e) => handleError(e) ) // 注册到中间中 instance.use(interceptor.register) [代码] 类型请求 这里我把类似 [代码]instance.get('/api')[代码] 这样的请求叫做类型请求。库中集成类型请求的话,难免会对外部传入的adapter 函数的参数进行污染。因为需要为请求方式 [代码]get[代码] 和路径 [代码]/api[代码] 分配键名,并且将其混入到参数中,通常在中间件中会有修改路径的需求。 实现很简单,只需要遍历 HTTP 请求类型,并将其挂在 this 下即可 [代码]class PreQuest { constructor(private adapter) { this.mount() } // 挂载所有类型的别名请求 mount() { methods.forEach(method => { this[method] = (path, opt) => { // 混入 path 和 method 参数 return this.request({ path, method, ...opt }) } }) } // ...some code request(opt) { // ...some code } } [代码] 简单请求 axios 中,可以直接使用下面这种形式进行调用 [代码]axios('http://localhost:3000/api').then(res => console.log(res)) [代码] 我将这种请求方式称之为简单请求。 我们这里怎么实现这种写法的请求方式呢? 不使用 class ,使用传统函数类写法的话比较好实现,只需要判断函数是否是 new 调用,然后在函数内部执行不同的逻辑即可。 demo 如下 [代码]function PreQuest() { if(!(this instanceof PreQuest)) { console.log('不是new 调用') return // ...some code } console.log('new调用') //... some code } // new 调用 const instance = new PreQuest(adapter) instance.get('/api').then(res => console.log(res)) // 简单调用 PreQuest('/api').then(res => console.log(res)) [代码] class 写法的话,不能进行函数调用。我们可以在 class 实例上做文章。 首先初始化一个实例,看一下用法 [代码]const prequest = new PreQuest(adapter) prequest.get('http://localhost:3000/api') prequest('http://localhost:3000/api') [代码] 通过 new 实例化出来的是一个对象,对象是不能够当做函数来执行,所以不能用 new 的形式来创建对象。 再看一下 axios 中生成实例的方法 [代码]axios.create[代码], 可以从中得到灵感,如果 [代码].create[代码] 方法返回的是一个函数,函数上挂上了所有 new 出来对象上的方法,这样的话,就可以实现我们的需求。 简单设计一下: 方式一: 拷贝原型上的方法 [代码]class PreQuest { static create(adapter) { const instance = new PreQuest(adapter) function inner(opt) { return instance.request(opt) } for(let key in instance) { inner[key] = instance[key] } return inner } } [代码] 注意: 在某些版本的 es 中,[代码]for in[代码] 循环遍历不出 class 生成实例原型上的方法。 方式二: 还可以使用 Proxy 代理一个空函数,来劫持访问。 [代码]class PreQuest { // ...some code static create(adapter) { const instance = new PreQuest(adapter) return new Proxy(function (){}, { get(_, name) { return Reflect.get(instance, name) }, apply(_, __, args) { return Reflect.apply(instance.request, instance, args) }, }) } } [代码] 上面两种方法的缺点在于,通过 [代码]create[代码] 方法返回的将不再是 [代码]PreQuest[代码] 的实例,即 [代码]const prequest = PreQuest.create(adapter) prequest instanceof PreQuest // false [代码] 个人目前还没有想到,判断 [代码]prequest[代码] 是不是 [代码]PreQuest[代码] 实例有什么用,并且也没有想到好的解决办法。有解决方案的请在评论里告诉我。 使用 [代码].create[代码] 创建 ‘实例’ 的方式可能不符合直觉,我们还可以通过 Proxy 劫持 new 操作。 Demo如下: [代码]class InnerPreQuest { create() { // ...some code } } const PreQuest = new Proxy(InnerPreQuest, { construct(_, args) { return () => InnerPreQuest.create(...args) } }) [代码] 请求锁 如何实现在请求接口前,先拿到 token 再去请求? 下面的例子中,页面同时发起多个请求 [代码]const prequest = PreQuest.create(adapter) prequest('/api/1').catch(e => e) // auth fail prequest('/api/2').catch(e => e) // auth fail prequest('/api/3').catch(e => e) // auth fail [代码] 首先很容易想到,我们可以使用中间件为其添加 token [代码]prequest.use(async (ctx, next) => { ctx.request.headers['token'] = token await next() }) [代码] 但 token 值从何而来?token 需要请求接口得来,并且需要重新创建请求实例,以避免重新走添加 token 的中间件的逻辑。 简单实现一下 [代码]const tokenRequest = PreQuest.create(adapter) let token = null prequest.use(async (ctx, next) => { if(!token) { token = await tokenRequest('/token') } ctx.request.headers['token'] = token await next() }) [代码] 这里使用了 token 变量,来避免每次请求接口,都去调接口拿 token。 代码乍一看没有问题,但仔细一想,当同时请求多个接口,tokenRequest 请求还没有得到响应时,后面的请求又都走到这个中间件,此时 token 值为空,会造成多次调用 tokenRequest。那么如何解决这个问题? 很容易想到,可以加个锁机制来实现 [代码]let token = null let pending = false prequest.use(async (ctx, next) => { if(!token) { if(pending) return pending = true token = await tokenRequest('/token') pending = flase } ctx.request.headers['token'] = token await next() }) [代码] 这里我们加了 pending 来判断 tokenRequest 的执行,成功解决了 tokenRequest 执行多次的问题,但又引入了新的问题,在执行 tokenRequest 时,后面到来的请求应当怎么处理?上面的代码,直接 return 掉了,请求将被丢弃。实际上,我们希望,请求可以在这里暂停,当拿到 token 时,再请求后面的中间件。 暂停,我们也可以很容想到使用 async、await 或者 promise 来实现。但在这里如何用呢? 我从 axios 的 cancelToken 实现中得到了灵感。axios 中,利用 promise 简单实现了一个状态机,将 Promise 中的 resolve 赋值到外部局部变量,实现对 promise 流程的控制。 简单实现一下 [代码]let token = null let pending = false let resolvePromise let promise = new Promise((resolve) => resolvePromise = resolve) prequest.use(async (ctx, next) => { if(!token) { if(pending) { // promise 控制流程 token = await promise } else { pending = true token = await tokenRequest('/token') // 调用 resolve,使得 promise 可以执行剩余的流程 resolvePromise(token) pending = flase } } ctx.request.headers['Authorization'] = `bearer ${token}` await next() }) [代码] 当执行 tokenRequest 时,其余请求的接口,都会进入到一个 promise 控制的流程中,当 token 得到后,通过外部 resolve, 控制 promise 继续执行,以此设置请求头,和执行剩余中间件。 这种方式虽然实现了需求,但代码丑陋不美观。 我们可以将状态都封装到一个函数中。以实现类似下面这种调用。这样的调用符合直觉且美观。 [代码]prequest.use(async (ctx, next) => { const token = await wrapper(tokenRequest) ctx.request.headers['Authorization'] = `bearer ${token}` await next() }) [代码] 怎么实现这样一个 wrapper 函数? 首先,状态不能封装到 wrapper 函数中,否则每次都会生成新的状态,wrapper 将形同虚设。可以使用闭包函数将状态保存。 [代码]function createWrapper() { let token = null let pending = false let resolvePromise let promise = new Promise((resolve) => resolvePromise = resolve) return function (fn) { if(pending) return promise if(token) return token pending = true token = await fn() pending = false resolvePromise(token) return token } } [代码] 使用时,只需要利用 [代码]createWrapper[代码] 生成一个 [代码]wrapper[代码] 即可 [代码]const wrapper = createWrapper() prequest.use(async (ctx, next) => { const token = await wrapper(tokenRequest) ctx.request.headers['Authorization'] = `bearer ${token}` await next() }) [代码] 这样的话,就可以实现我们的目的。 但,这里的代码还有问题,状态封装在 createWrapper 内部,当 token 失效后,我们将无从处理。 比较好的做法是,将状态从 [代码]createWrapper[代码] 参数中传入。 实战 以微信小程序为例。小程序中自带的 [代码]wx.request[代码] 并不好用。使用上面我们封装的代码,可以很容易的打造出一个小程序请求库。 封装小程序原生请求 将原生小程序请求 Promise 化,设计传参 opt 对象 [代码]function adapter(opt) { const { path, method, baseURL, ...options } = opt const url = baseURL + path return new Promise((resolve, reject) => { wx.request({ ...options, url, method, success: resolve, fail: reject, }) }) } [代码] 调用 [代码]const instance = PreQuest.create(adapter) // 中间件模式 instance.use(async (ctx, next) => { // 修改请求参数 ctx.request.path = '/prefix' + ctx.request.path await next() // 修改响应 ctx.response.body = JSON.parse(ctx.response.body) }) // 拦截器模式 instance.interecptor.request.use( (opt) => { opt.path = '/prefix' + opt.path return opt } ) instance.request({ path: '/api', baseURL: 'http://localhost:3000' }) instance.get('http://localhost:3000/api') instance.post('/api', { baseURL: 'http://loclahost:3000' }) [代码] 获取原生请求实例 首先看一下在小程序中怎样中断请求 [代码]const request = wx.request({ // ...some code }) request.abort() [代码] 使用我们封装的这一层,将拿不到原生请求实例。 那么怎么办呢?我们可以从传参中入手 [代码]function adapter(opt) { const { getNativeRequestInstance } = opt let resolvePromise: any getNativeRequestInstance(new Promise(resolve => (resolvePromise = resolve))) return new Promise(() => { const nativeInstance = wx.request( // some code ) resolvePromise(nativeInstance) }) } [代码] 用法如下: [代码]const instance = PreQuest.create(adapter) instance.post('http://localhost:3000/api', { getNativeRequestInstance(promise) { promise.then(instance => { instance.abort() }) } }) [代码] 兼容多平台小程序 查看了几个小程序平台和快应用,发现请求方式都是小程序的那一套,那其实我们完全可以将 [代码]wx.request[代码] 拿出来,创建实例的时候再传进去。 结语 上面的内容中,我们基本实现了一个与请求内核无关的请求库,并且设计了两种拦截请求和响应的方式,我们可以根据自己的需求和喜好自由选择。 这种内核装卸的方式非常容易扩展。当面对一个 axios 不支持的平台时,也不用费劲的去找开源好用的请求库了。我相信很多人在开发小程序的时候,基本都有去找 axios-miniprogram 的解决方案。通过我们的 PreQuest 项目,可以体验到类似 axios 的能力。 本文涉及到的代码,请参阅这里 参考 axios: https://github.com/axios/axios umi-request:https://github.com/umijs/umi-request
2021-05-31 - 小程序textarea组件confirm-type在华为mate 30 pro上没有生效
textarea组件confirm-type这个属性设置了不生效,我手机一直在开发版、真机调试都没有效果
2020-12-29 - 关于小程序调取地图api获取位置
问下小程序调取地图api获取位置能否精确到小区,比如我在灵秀山庄小区内获取定位是不是会定位为 北京市大兴区旧宫镇灵秀山庄小区 这样
2019-06-21 - 小程序如何定位所在城市,如何发起周边搜索
app上常见的自动切换本地城市,还有见到有些小程序中个人账号可以获取位置服务,整理了一些封装方法 先封装request 为小程序get/post的promise封装 rq.js [代码]/* * url {String} 请求地址接口 * data {Object} 请求参数 * param {Object} request参数 * method {String} 指定使用post或者是get方法 */ export function request(url, data={}, param={}, method='POST') { return new Promise((resolve, reject)=>{ let postParam = { url, method, data, // timeout // dataType // responseType header: { 'content-type': 'application/json' // 默认值 }, success(res) { resolve(res) }, error: function (e) { reject(e) } } postParam = Object.assign(postParam, param) postParam.fail = postParam.error if (postParam.url) wx.request(postParam) }) } module.exports = { get(url, data, param){ return request(url, data={}, param={}, method='GET') }, post(){ return request.apply(null, arguments) } } [代码] 位置服务方法 需要开通小程序的位置服务功能,在小程序控制台 [图片] 简单的封装了三个位置服务的方法 所在地城市 地区搜索 范围搜索 [代码]// request的promise封装 const iKit = request('./rq.js') // key为开通位置服务所获取的查询值 // 下面这个key是随便写的 let key = 'JKDBZ-XZVLW-IAQR8-OROZ1-XR3G9-UYBD5' /* * 搜索地区... * 搜索地区的商圈, 如搜索 kfc 广州市 * key {String} 搜索内容 * region {String} 搜索区域 */ export function searchRegion(kw, region) { let opts = { keyword: encodeURI(kw), boundary: region ? `region(${encodeURI(region)}, 0)` : '', // 0 为限定范围搜搜索,1为开放范围搜素偶 page_size: 10, // 搜索结果分页最大条数 page_index: 1, // 指定分页 key } return new Promise((resolve, rej)=>{ iKit.get('https://apis.map.qq.com/ws/place/v1/search', opts).then(res=>{ resolve(res.data.data) }) }) } /* * 搜索附近的... * 以当前位置的经纬度搜索附近商圈,如附近的酒店,快餐等等 * key {String} 搜索内容 * params {Object} 搜索参数 包含三个参数 lat纬度,lng经度,distance范围(单位米) */ export function searchCircle(kw, params={}) { let {lat, lng, distance} = params if (!lat && !lng) return if (!distance) distance = 1000 // 搜索范围默认1000米 let opts = { keyword: encodeURI(kw), boundary: `nearby(${lat},${lng},${distance})`, orderby: '_distance', // 范围搜索支持排序,由近及远 page_size: 20, // 搜索结果分页最大条数 page_index: 1, // 指定分页 key } return new Promise((resolve, rej)=>{ iKit.get('https://apis.map.qq.com/ws/place/v1/search', opts).then(res=>{ resolve(res.data.data) }) }) } // 所在地的城市,省份等区域信息 /* * 所在地的城市,省份等区域信息 * 如当前地址所在省份、城市 * lat {Number} 纬度 * lng {Number} 经度 */ export function myCity(lat, lng) { return new Promise((resolve, rej)=>{ let opts = { location: `${lat},${lng}`, key } iKit.get(`https://apis.map.qq.com/ws/geocoder/v1/`, opts).then(res => { resolve(res.data.result) }) }) } [代码] 调用 [代码]wx.getLocation({ type: 'wgs84', success(location) { locationPosition = location // 所在地信息 myCity(location.latitude, location.longitude).then(res => { let myCityInfo = res.ad_info let {city, nation, province, city_code, adcode} = myCityInfo console.log({title: `国家: ${nation},省份: ${province},城市: ${city}`}) }) // 附近搜索 searchCircle('快餐', { lat: location.latitude, lng: location.longitude, distance: 500 }).then(res=>{ console.log(res) }) // 地区搜索 searchRegion('酒店', '广州').then(res=>{ console.log(res) }) } }) [代码] 关注小程序 [图片]
2020-02-10 - 小程序1rpx边框不完美解决方案
在小程序开发中,1rpx边框随处可见, 像上图UI给的设计稿,如果只是简单使用[代码]border: 1rpx solid red;[代码]的话,在不同的机型上会有不同的表现 [图片] 表现IOS 机型上[图片] Android机型上[图片] 由图片可以看出, IOS机型上会有边框缺失(然而经常出现缺不能稳定复现), 而Android机型上边框比较粗 原因上面这两种表现形式很难联系到一起 首先先看IOS边框缺失的问题,借鉴网络上前辈们的经验 当父元素的高度为奇数,容易出现上下边框缺失,同理宽度为奇数,容易出现左右边框缺失解决办法是在边框内部添加一个1rpx的元素或者伪元素, 撑开内部使父元素的宽高是偶数。 然而我们发现这种方案在Iphone 6等2倍屏可以生效, 但放在如Iphone X等3倍屏下面就很飘了, 还是经常会出现边框缺失的情况, 这种情况下再去把父元素改为2和3共同的倍数就非常不现实了。 再回过头看导致边框缺失的具体原因是啥。 在这之前需要了解下高分屏的物理像素和虚拟像素的概念 简单来说物理像素是设备的实际像素 虚拟像素是设备的坐标点, 可以简单理解为css像素 而rpx类似rem,渲染后实际转换成px之后可能存在小数,在不同的设备上多多少少会存在渲染的问题。而1rpx的问题就更加明显,因为不足1个物理像素的话,在IOS会进行四舍五入,而安卓好像统一向上取整,这也是上面两种设备表现不同的原因。 解决方法我们采用的方法是采用translate:scale(0.5)的方法对边框进行缩放 具体的代码如下 .border1rpx, .border1rpx_before{ position: relative; border-width: 0rpx !important; padding: 0.5rpx; z-index: 0; } .border1rpx::after, .border1rpx_before::before{ content: ""; border-style: inherit; border-color: inherit; border-radius: inherit; box-sizing: border-box !important; position: absolute; border-width: 2rpx !important; left: 0; top: 0; width: 200% !important; height: 200% !important; transform-origin: 0 0; transform: scale(0.5) !important; z-index: -1; } .border1rpx-full { margin: -1rpx; } 给.border1rpx的元素设置边框宽度为0给::after伪元素宽高为两倍,边框设置2rpx,边框其他样式继承元素的设置然后再缩放0.5来达到边框为1rpx的效果 用法基础用法给相应的元素添加border1rpx的class即可, (.borde1rpx说:我们不生产边框,只是边框的搬运工,要显示边框样式的话还需要在元素上自行设置) 圆角边框圆角边框需要自行设置相应伪元素::before 或 ::after的border-raduis值为预期的2倍, 如原本想要设置10rpx的圆角,需要设置[代码].xxx::after{border-raduis: 20rpx;}[代码] 边框内部填充由于设计原因,目标元素会留1rpx的padding用于显示伪元素的边框,如果内部元素是填充的,正常会看到填充元素和目标元素有小部分间隙,此时需要给填充元素添加.border1rpx_full来解决 注意点此方案默认使用::after伪元素实现边框,如果目标元素的after被占用(如iconfont),请使用[代码].border1rpx_before[代码]如单独设置边框(如上边框), [代码]border: 1rpx solid red;border-width: 1rpx 0 0 0;[代码]不能被正确继承,请使用简写[代码]border-top: 1rpx solid red;[代码]由于设计原因,目标元素请最少设置1rpx的padding用于显示边框,(上面的样式已经有了默认的padding,不写也可以, 只是不要用padding:0覆盖)请自行测试点击功能是否正常,防止层级关系导致元素区域被伪元素覆盖
2020-07-23 - 小程序分享朋友圈功能,getUpdateManager使用不了吗
[图片][图片]小程序分享朋友圈功能,通过分享的页面点进去getUpdateManager报错,数据无法加载。 。 。 。 。 。 。 。
2020-07-10 - 手把手教你接入小程序转发朋友圈
本文背景小程序转发朋友圈出来有几天了,一直没有接入,今天花了5分钟看了下文档,跟着操作了一遍,现已审核通过、发布上线 本文内容小程序转发朋友圈接入,需要完成以下两步 (1)设置小程序基础库版本为:2.11.3,因为小程序转发朋友圈从基础库 2.11.3 开始支持 (2)新增如下代码,如以下截图所示 代码片段一(这个一定要加上才能生效 否则就是置灰的) [图片] 代码片段二 f [图片] f 小程序基础库版本变更如下所示 [图片] f 代码变更如下所示 [图片] 其他不需要操作,以下小程序分享是通过华为荣耀手机操作 [图片] 参考文档 (1) H5跳转小程序以及小程序分享朋友圈官方文档? - 微信开放社区 https://developers.weixin.qq.com/community/develop/article/doc/000aac18fdcd809af49a9a6bf5ec13 (2)官方文档 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share-timeline.html (3) 实现小程序分享朋友圈功能? - 微信开放社区 https://developers.weixin.qq.com/community/develop/article/doc/000246d81bc35025e19a814735a413 (4) onShareTimeline安卓真机无效? - 微信开放社区 https://developers.weixin.qq.com/community/develop/doc/00002e5375cdb091d39ac5e7451000 遇到问题部分安卓手机,不能分享,以下为三星手机A80 [图片] 本文总结未总结 注意事项目前小程序转发朋友圈还在灰度,仅支持安卓手机。
2020-07-16 - 完美解决小程序session问题
小程序不像web浏览器有cookie机制,在默认使用cookie存sessionid的机制下,后台将无法正常使用session功能,如果正确使用session呢,提供两个方案。 [代码]1、将sessionid通过url进行传递 用户每次登录成功后将生成的sessionid值使用参数回传到客户端, 客户端接到sessionid后保存到本地, 在发起网络请求的底层接口中默认自动带上sessionid=本地存储的sessionid值。 需要配合服务器一起更改,服务器后端默认使用cookie机制 2、无缝对接cookie, 将服务器的set-cookie值保存到本地,再请求的时候模拟浏览器头部信息并带上保存的cookie信息 1)保存cookie值: _XHR('login',{'code':res.code}).then(function( ret ){ ret.header["Set-Cookie"] != undefined && wx.setStorageSync("cookie", ret.header["Set-Cookie"]); }); 2)请求的时候自动带上cookie信息 var header={}; header = { 'content-type': 'application/x-www-form-urlencoded' }; var cookie = wx.getStorageSync("cookie"); if( url != 'login' && !isNull( cookie ) ){ header['cookie'] = cookie; } 将header 赋值到 request的header内 wx.request({ url: qryDomian + url + '.html', data: _data, method: 'POST', header: header, dataType:'json' ...... 第二种方案服务器无需做任务操作。[代码]
2019-05-20 - 小程序工具怎么模拟弱网环境
- 需求的场景描述(希望解决的问题) 需要模拟网络状态比较差的情况下的请求 - 希望提供的能力 [图片] 这块儿好像无法控制网络请求速度
2019-07-09 - 开发小程序,开发版/体验版/线上版会混淆是什么问题?
开发版、体验版、线上版是共用缓存的,删除所有版本的小程序才可以清除缓存。需要在微信列表、【我的小程序】、【最近使用】中同时删除才可以清缓存。
2019-12-31 - 如何实现快速生成朋友圈海报分享图
由于我们无法将小程序直接分享到朋友圈,但分享到朋友圈的需求又很多,业界目前的做法是利用小程序的 Canvas 功能生成一张带有小程序码的图片,然后引导用户下载图片到本地后再分享到朋友圈。相信大家在绘制分享图中应该踩到 Canvas 的各种(坑)彩dan了吧~ 这里首先推荐一个开源的组件:painter(通过该组件目前我们已经成功在支付宝小程序上也应用上了分享图功能) 咱们不多说,直接上手就是干。 [图片] 首先我们新增一个自定义组件,在该组件的json中引入painter [代码]{ "component": true, "usingComponents": { "painter": "/painter/painter" } } [代码] 然后组件的WXML (代码片段在最后) [代码]// 将该组件定位在屏幕之外,用户查看不到。 <painter style="position: absolute; top: -9999rpx;" palette="{{imgDraw}}" bind:imgOK="onImgOK" /> [代码] 重点来了 JS (代码片段在最后) [代码]Component({ properties: { // 是否开始绘图 isCanDraw: { type: Boolean, value: false, observer(newVal) { newVal && this.handleStartDrawImg() } }, // 用户头像昵称信息 userInfo: { type: Object, value: { avatarUrl: '', nickName: '' } } }, data: { imgDraw: {}, // 绘制图片的大对象 sharePath: '' // 生成的分享图 }, methods: { handleStartDrawImg() { wx.showLoading({ title: '生成中' }) this.setData({ imgDraw: { width: '750rpx', height: '1334rpx', background: 'https://qiniu-image.qtshe.com/20190506share-bg.png', views: [ { type: 'image', url: 'https://qiniu-image.qtshe.com/1560248372315_467.jpg', css: { top: '32rpx', left: '30rpx', right: '32rpx', width: '688rpx', height: '420rpx', borderRadius: '16rpx' }, }, { type: 'image', url: this.data.userInfo.avatarUrl || 'https://qiniu-image.qtshe.com/default-avatar20170707.png', css: { top: '404rpx', left: '328rpx', width: '96rpx', height: '96rpx', borderWidth: '6rpx', borderColor: '#FFF', borderRadius: '96rpx' } }, { type: 'text', text: this.data.userInfo.nickName || '青团子', css: { top: '532rpx', fontSize: '28rpx', left: '375rpx', align: 'center', color: '#3c3c3c' } }, { type: 'text', text: `邀请您参与助力活动`, css: { top: '576rpx', left: '375rpx', align: 'center', fontSize: '28rpx', color: '#3c3c3c' } }, { type: 'text', text: `宇宙最萌蓝牙耳机测评员`, css: { top: '644rpx', left: '375rpx', maxLines: 1, align: 'center', fontWeight: 'bold', fontSize: '44rpx', color: '#3c3c3c' } }, { type: 'image', url: 'https://qiniu-image.qtshe.com/20190605index.jpg', css: { top: '834rpx', left: '470rpx', width: '200rpx', height: '200rpx' } } ] } }) }, onImgErr(e) { wx.hideLoading() wx.showToast({ title: '生成分享图失败,请刷新页面重试' }) //通知外部绘制完成,重置isCanDraw为false this.triggerEvent('initData') }, onImgOK(e) { wx.hideLoading() // 展示分享图 wx.showShareImageMenu({ path: e.detail.path, fail: err => { console.log(err) } }) //通知外部绘制完成,重置isCanDraw为false this.triggerEvent('initData') } } }) [代码] 那么我们该如何引用呢? 首先json里引用我们封装好的组件share-box [代码]{ "usingComponents": { "share-box": "/components/shareBox/index" } } [代码] 以下示例为获取用户头像昵称后再生成图。 [代码]<button class="intro" bindtap="getUserInfo">点我生成分享图</button> <share-box isCanDraw="{{isCanDraw}}" userInfo="{{userInfo}}" bind:initData="handleClose" /> [代码] 调用的地方: [代码]const app = getApp() Page({ data: { isCanDraw: false }, // 组件内部关掉或者绘制完成需重置状态 handleClose() { this.setData({ isCanDraw: !this.data.isCanDraw }) }, getUserInfo(e) { wx.getUserProfile({ desc: "获取您的头像昵称信息", success: res => { const { userInfo = {} } = res this.setData({ userInfo, isCanDraw: true // 开始绘制海报图 }) }, fail: err => { console.log(err) } }) } }) [代码] 最后绘制分享图的自定义组件就完成啦~效果图如下: [图片] tips: 文字居中实现可以看下代码片段 文字换行实现(maxLines)只需要设置宽度,maxLines如果设置为1,那么超出一行将会展示为省略号 代码片段:https://developers.weixin.qq.com/s/J38pKsmK7Qw5 附上painter可视化编辑代码工具:点我直达,因为涉及网络图片,代码片段设置不了downloadFile合法域名,建议真机开启调试模式,开发者工具 详情里开启不校验合法域名进行代码片段的运行查看。 最后看下面大家评论问的较多的问题:downLoadFile合法域名在小程序后台 开发>开发设置里配置,域名为你图片的域名前缀 比如我文章里的图https://qiniu-image.qtshe.com/20190605index.jpg。配置域名时填写https://qiniu-image.qtshe.com即可。如果你图片cdn地址为https://aaa.com/xxx.png, 那你就配置https://aaa.com即可。
2022-01-20 - 为什么使用wx.saveImageToPhotosAlbum保存多张图片时候,其中几张保存失败?
我在真机调试的时候,其中几张图片的回调函数返回err,具体是这样:errMsg: "saveImageToPhotosAlbum:fail save fail:Error Domain…入, NSLocalizedFailureReason=写入此资产时出现问题,因为写入资源正忙。}"[图片] 我的代码: [图片]
2020-02-28 - 在ios版本下,FileSystemManager.saveFile 无法保存临时文件
https://developers.weixin.qq.com/miniprogram/dev/api/file/FileSystemManager.saveFile.html 复现片段(取消es6转es5)https://developers.weixin.qq.com/s/vZjPxBmB7Tfk ipad 4 系统版本13.3.1 微信版本7.0.5 let { statusCode, tempFilePath } = await wx.cloud.downloadFile({ fileID }) let tmp = tempFilePath.indexOf('?') if (tmp != -1) tempFilePath = tempFilePath.slice(0, tmp) //在ios下,下载的临时文件带有skip参数无法保存,会抛出无效参数,因此要做剪裁去掉参数,但是剪裁掉后还是无法保存。再看下面。 if (statusCode == 200) wx.getFileSystemManager().saveFile({ tempFilePath, success: res => console.log('success',res), fail: res => console.log('fail', res) //在这里会抛出一个saveFile:fail move to savedFilePath fail。直接调用wx.saveFile接口也一样。 //按照设计是移动临时文件保存,但在这里会移动失败。这错误码在文档里面也没有特别说明。 }) 以上两个问题都只存在于ios版本,具体是ipad,android下,两个问题都不存在。 代码可以直接运行复现。只需给一个fileID。
2020-03-04 - fileSystemManager.saveFile保存的文件在哪里能找到?
场景是:在小程序中下载docx,并且客户需要拿到这个文件,后续去转发或者打印。 现在找不到这个文件。 请问需要怎么做才能找到这个文件。
2019-09-11 - 订阅消息授权弹窗不点击任何按钮,返回再进来,无法拉起授权弹窗?
订阅消息授权弹窗,既不选择允许,也不选择取消,点击左上角返回键返回,再次进入页面,点击按钮,授权调不起来; 打印结果是进入点击按钮事件后,就没有响应了... 是什么原因呢? appid: wx14200f22d56f6f9e 复现:去参团->授权后->一键参团->出现订阅消息授权弹窗后->左上角按钮返回->再次点击去参团(任意一个)->不出现订阅消息授权弹窗
2019-12-16 - 一个页面上有两个获取缓存,怎么合并在一起啊?
[图片]
2019-12-12 - 如何解决在装修建材类目下发布装修建材视频的问题?
审核类型:代码审核 提审时间:2019-12-05 17:39:00 1、我们的小程序中包含了装修/建材类的视频以及图片介绍,但是我们经营范围并不是音乐娱乐方面,所以如何发布装修类的视频? 2、我们的小程序中包含了工地直播,只不过跟同类的装修小程序名称一样,并没有进行对应的直播功能。
2019-12-06 - 缓存使用报错,并且无法清除缓存
微信小程序的缓存清理机制有问题,并没有根据小程序的生命周期而改变 [图片] 文档说的是与代码包一致的清理时机,但是实际并没清理,甚至删除小程序都清理不了,唯一可清理的操作是将小程序添加到“我的小程序”,然后同时从“我的小程序”、“最近运行”删除,才能清理缓存。 因为清理机制有问题,导致我们中长期留存用户使用小程序时报错,因为调用缓存时会卡死整个小程序(因为超过了缓存大小?),这是个非常严重的bug,严重影响我们的日活!!并且这个报错没办法调试,我们真的很困难啊~~是联系了用户才得到了报错信息,困扰了我们很久,同时社区里面也有其他的开发者反馈了这个问题,希望官方重视,给到解决方案! 用户的报错提示为如下图: [图片] [图片]
2019-08-01 - 内嵌的h5跳转另一个小程序如何实现?
请解答
2019-11-04 - 用<slot>标签实现建造者模式
背景 在平常的开发过程中,我们经常会遇到一种情况:有一个组件(或页面)被用于多个场景,不同的场景下面,组件的部分内容发生变化,其他则不变。 某一天,前端开发小雷正在码代码,淳朴的产品经理(以下称为淳朴哥)跑过来说:“我们要做一个商品组件,根据后台返回的数据显示出来。这个简单吧!” [图片] 小雷一想,这个简单啊,框框10分钟把代码敲了出来。 开发一个商品组件 [代码]<!--sku-panel.wxml--> <view style="border: 1rpx solid red; padding: 10rpx;"> <image src="{{goods.url}}"></image> <view>名称:"{{goods.name}}"</view> <view>规格:"{{goods.sku}}"</view> <view>价格:"{{goods.price}}"元</view> </view> [代码] [代码]// sku-panel.js Component({ /** * 组件的属性列表 */ properties: { goods: Object }, /** * 组件的初始数据 */ data: { }, /** * 组件的方法列表 */ methods: { } }) [代码] [图片] 过了两天,淳朴哥又跑过来说:“小雷啊,我们现在要搞国庆活动,在活动页面的商品显示不太一样,增加在括号里面显示活动价。很简单的!”。 小雷一想,这个确实也简单嘛:给商品组件加个类型,如果是普通页面,就按照以前的方式显示,如果是活动界面,就按照新的条件显示。 添加各种活动 改造js文件,添加type入参 [代码]Component({ /** * 组件的属性列表 */ properties: { goods: Object, type: String } }) [代码] 改造wxml文件 [代码]<view style="border: 1rpx solid red; padding: 10rpx;"> <image src="{{goods.url}}"></image> <view>名称:"{{goods.name}}"</view> <view>规格:"{{goods.sku}}"</view> <view wx:if="{{type === 'activity'}}">价格:{{goods.price}}元(活动价8折:{{goods.price * 0.8}}元)</view> <view wx:else>价格:{{goods.price}}元</view> </view> [代码] [图片] 过了几天,淳朴哥又跑过来说:“小雷啊!活动商品搞得很好,反响很不错,老板很高兴,我们决定趁热打铁,如果是会员,直接打七折。这个也简单吧!” 小雷一想:“还好我机智,这次增加个‘会员’类型嘛”。于是修改xml文件实现了需求。 改造wxml文件 [代码]<view style="border: 1rpx solid red; padding: 10rpx;"> <image src="{{goods.url}}"></image> <view>名称:"{{goods.name}}"</view> <view>规格:"{{goods.sku}}"</view> <view wx:if="{{type === 'member'}}">价格:{{goods.price}}元(会员价7折:{{goods.price * 0.7}})元</view> <view wx:elif="{{type === 'activity'}}">价格:{{goods.price}}元(活动价8折:{{goods.price * 0.8}}元)</view> <view wx:else>价格:{{goods.price}}元</view> </view> [代码] [图片] 从表面上看,这样的处理解决了需求,但是实际上却存在问题。那么存在哪些问题呢?我们不妨发散开来想想. 随着活动的种类不断增加,假设增加到a,b,c,d,e,f,g…等活动,那么wxml代码里面是不是会充斥十几个if条件呢?如果后续给商品组件添加一个onClick()事件,点击的后续动作通过这个onClick()函数处理,根据不同的种类,进行不同的处理,js中是不是也要增加十几条if else 呢? [代码]Component({ /** * 组件的属性列表 */ properties: { goods: Object, type: String }, onClick() { if(type === a) { log(a) } if(type === b) { log(b) } if(type === c) { log(c) } .... if(type === z) { log(z) } } }) [代码] 这样的代码违反了两个设计原则 单一责任原则 指的是一个类或者一个方法只做一件事。如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化就可能抑制或者削弱这个类完成其他职责的能力。 开放封闭原则 对扩展开放,对修改关闭。意为一个类独立之后就不应该去修改它,而是以扩展的方式适应新需求。 比如说过了几天,淳朴哥又又跑过来说:“小雷啊!我们的会员价现在不打7折了,改成打6折。这个很简单哈!”。 [图片] 小雷开始感觉不对了,但是又不知道出了什么问题,(我怀疑你有问题,但是我没有证据)心里开始嘀咕了:“这需求怎么老是改来改去?这么一大堆代码,我都要翻几屏呢。”于是小雷打开编辑器,找到type===member的代码块,修改完成,然后交给测试妹妹(以下简称可爱妹)。 可爱妹:“小雷,这次修改了哪些地方啊?” 小雷:“所有使用到商品组件的地方都改动了,麻烦你把所有相关界面过一遍。”(因为所有页面都是用的商品组件,改动一个地方会影响所有使用该组件的页面) 可爱妹:“什么?我尼玛。。。你想累死我吗?” 小雷:“跟我也没有关系啊,我想改嘛?还不是淳朴哥老在改需求!” 淳朴哥:“我尼玛,这老板要求改的,我找谁说理去啊?” [图片] 于是一场撕逼大戏展开。。。。 存在的问题: 1)代码量爆炸,阅读起来困难 2)每次需求变更都要改动原代码,不利于维护和测试 相关的改动在产品的开发过程中不可避免,解决这类问题小雷需要将代码抽象出来,而不是简单的if else。 过程分析 淳朴哥在修改需求的过程中,有一些特点,他所改变的是商品的价格显示。因此,存在着变化(价格的显示,点击事件的响应)和不变(商品组件的基本布局:图片/名称/sku/价格)。 根据业务发展的特点,从设计模式中寻找对应方案 建造者(生成器)模式: 建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。 介绍: 将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。 意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。 主要解决:主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。 何时使用:一些基本部件不会变,而其组合经常变化的时候。 如何解决:将变与不变分离开。 优点: 1、建造者独立,易扩展。 2、便于控制细节风险。 缺点: 1、产品必须有共同点,范围有限制。 2、如内部变化复杂,会有很多的建造类。 [图片] 解决过程 指导思想:封装变化,灵活替换。 实现思路:将变化的部分独立封装出来,使用的时候进行灵活替换。 过程: 1)将原有的商品组件重新封装,变化的部分用slot代替 [代码]<!--sku-panel.wxml--> <view style="border: 1rpx solid red; padding: 10rpx;"> <image src="{{goods.url}}"></image> <view>名称:{{goods.name}}</view> <view>规格:{{goods.sku}}</view> <view>价格:{{goods.price}}元<slot /></view> </view> [代码] 2)将变化的部分封装出来 [代码]活动商品组件 <!--components/activity-builder/index.wxml--> <sku-panel goods="{{goods}}"> <text>(活动价8折:{{goods.price * 0.8}}元)</text> </sku-panel> [代码] [代码]会员商品组件 <!--components/member-builder/index.wxml--> <sku-panel goods="{{goods}}"> <text>(会员价7折:{{goods.price * 0.7}}元)</text> </sku-panel> [代码] 3)在活动页面调用活动组件,在会员页面调用会员组件 [代码]活动页面 <!--pages/activty/index.wxml--> <view class="container"> <view>活动</view> <sku-panel-activity goods='{{goods}}' />//活动组件 </view> [代码] [代码]会员页面 <!--pages/member/index.wxml--> <view class="container"> <view>会员</view> <sku-panel-member goods='{{goods}}' />//会员组件 </view> [代码] 改造之后的结果 1)代码量减少,阅读轻松。不同活动之间的判断逻辑被分散到各个业务组件中了 2)结构清晰,职责分明。每个业务组件中只有与自己相关业务的代码 3)拓展方便。易于测试。某个活动的有所改变,只需要修改对应的业务组件,影响范围被控制起来,测试妹妹也只需要测试某个组件就行。 [图片]
2019-10-28 - swiper 如何自适应高度
swiper 组件无法自适应高 内容是图片 图片的mode 设置了 widthFix 属性 [代码]<swiper indicator-dots=[代码][代码]"{{indicatorDots}}"[代码][代码] [代码][代码]autoplay=[代码][代码]"{{autoplay}}"[代码] [代码]interval=[代码][代码]"{{interval}}"[代码] [代码]duration=[代码][代码]"{{duration}}"[代码][代码]>[代码][代码] [代码][代码]<block wx:[代码][代码]for[代码][代码]=[代码][代码]"{{imgUrls}}"[代码] [代码]wx:key=[代码][代码]"{{index}}"[代码][代码]>[代码][代码] [代码][代码]<swiper-item>[代码][代码] [代码][代码]<image src=[代码][代码]"{{item}}"[代码] [代码]class[代码][代码]=[代码][代码]"slide-image"[代码] [代码]style=[代码][代码]"width:100%"[代码] [代码]mode=[代码][代码]"widthFix"[代码][代码]/>[代码][代码] [代码][代码]</swiper-item>[代码][代码] [代码][代码]</block>[代码][代码]</swiper>[代码][代码]<button bindtap=[代码][代码]"changeIndicatorDots"[代码][代码]> indicator-dots </button>[代码][代码]<button bindtap=[代码][代码]"changeAutoplay"[代码][代码]> autoplay </button>[代码][代码]<slider bindchange=[代码][代码]"intervalChange"[代码] [代码]show-value min=[代码][代码]"500"[代码] [代码]max=[代码][代码]"2000"[代码][代码]/> interval[代码][代码]<slider bindchange=[代码][代码]"durationChange"[代码] [代码]show-value min=[代码][代码]"1000"[代码] [代码]max=[代码][代码]"10000"[代码][代码]/> duration[代码]
2017-08-23 - 微信小程序swiper高度自适应,swiper的子元素高度不固定
微信小程序swiper高度自适应,swiper的子元素高度不固定小程序 swiper 组件默认高度150px,并且如果子元素过高,swiper不会自适应高度 解决方案一: (总体来说不够完美,适合满屏滑动)如果不是满屏的状态,用scroll-view IOS滑动兼容性不好,在IOS会有无法滑动的情况 <swiper class="content" style="height:{{height}}px" bindchange="change" current-item-id="{{docid}}" duration="100" > <swiper-item data-key="{{item.id}}" wx:for="{{title}}" wx:key="index" item-id="{{item.id}}" > <scroll-view data-id="{{item.id}}" style='height:100%;' scroll-y bindscrolltolower="scrolltolower" data-left="51" scroll-top="{{top}}" bindscroll="scroll" > <!--单条新闻start --> <navigator url="/pages/detail/detail?id={{item.docid}}" class="item" wx:for="{{item.id==docid?news:''}}" wx:key="index"> </navigator> <!--单条新闻end --> <view class='loading'>加载中...</view> </scroll-view> </swiper-item> </swiper>适应场景: [图片] 适合这种满屏滑动的,嵌套 scroll-view 注意: <scroll-view> 需要有条件,写固定的高,纵向滑动scroll-y 横向滑动 scroll-x white-space:nowrap; 解决方案二: (适应子元素高度也不一致) 效果图是这样的:然后在上滑过程中,导航栏还需要吸顶,然后滑动下方tab栏的内容 [图片][图片] 其实如果不是基于小程序,我们可以很直接用swiper插件,操作起来简直方便,小程序由于 swiper 高的限制,真是走了不少弯路,如果下面的列表高度一样,我们便可以算出一个的高度,然后乘以个数即可,但是这样只能求出每一个个数 index .wxml <swiper current="{{current}}" bindchange="change" duration="300" style="height:{{swiper_height + 80}}px;min-height:50%vh;"> <swiper-item class="swiper-item" wx:for="{{channel_list}}" wx:key="{{item.id}}"> <!-- navigator 的类名很重要,虽然一个循环用统一样式,但是我们基于不同的tab 取了不用的类名 ,因为小程序无法操作DOM元素,虽然封装的API 可以获取,但是只能获取第一个和所有,我们每个tab的内容个数不一样,所以需要基于每个tab栏求,item.channelId 是图2标注吸顶效果的channelId, --> <navigator class="column-list column-list{{item.channelId}}" url="" wx:for="{{item.viewLessonList}}" wx:for-item="lesson" wx:key="{{index}}" wx:key-item="lesson-item"> 这里面便是一个一个不同高度的列表 </navigator> <!-- 这下面就是weui的一个加载样式,基于分页加载做的不同样式 --> <view class="weui-loadmore" hidden="{{is_loadmore}}"> <view class="weui-loading"></view> <view class="weui-loadmore__tips">正在加载</view> </view> <view class="weui-loadmore weui-loadmore_line" hidden="{{!is_loadmore}}"> <text class="weui-loadmore__tips">左右滑动,查看更多</text> </view> </swiper-item> </swiper> [图片] index.js // 获取wxml的节点信息 function get_wxml(className, callback) { wx.createSelectorQuery().selectAll(className).boundingClientRect(callback).exec() } onReady: function() { let column_all = that.data.column_list[that.data.current].viewLessonList, // 这个是基于java端返回的tab栏的接口,大致样式如上图,也就是每个列表, channel_id = that.data.column_list[that.data.current].channelId // 我们这个就是求出目前的channelId,好区分不同的类名 that.setData({ swiper_length: column_all.length // 算出当前tab栏有多少个列表 }) get_wxml(`.column-list${channel_id}`, (rects) => { let sum_heigth = 0 for (let i = 0; i < that.data.swiper_length; i++) { sum_heigth += rects[i].height } that.setData({ swiper_height: sum_heigth }) // 就是循环相加每个列表的高度,然后赋值给swiper_height,便可以求出当前tab栏的高度,赋值给swiper 便可以swiper高度自适应 }) }
2018-08-08 - 小程序配置自己的域名,生成普通二维码,链接后面带参数。直接用微信扫码打开小程序获取不到参数?
1,生成二维码链接:https://gzzhgz.com/data/wx/pages/home/index?boothid=4866d72017ba443c8aa2372d70a8ca1c 2,微信扫描二维码能打开小程序,但是获取不到boothid参数。 3,获取参数代码如下。(以下有两种获取boothid参数的方法,为了代码成功率。每次测的话还要等微信审核通过上线版才能测。建议微信增加未上线就能测) onLoad: function(options) { var p = decodeURIComponent(options.boothid); this.setData({ wxSacenShopId: p, }) if (p == null || p == undefined || p == "" || p == 'undefined'){ this.setData({ wxSacenShopId: options.boothid }) }
2019-09-27 - 微信平台小程序的审核逻辑是什么?
微信平台小程序的审核逻辑是什么? 审核结果: [图片] 我们平台已经根据登录规范要求开放了用户体验功能及体验页面,新用户可以不授权给我们平台小程序进行完整的购物流程,为什么审核还要被驳回? 望官方给指条明路!!! 下面提供的图片中有7个步骤,说明了一个未登录的用户使用我们平台购物的整套流程。 注:整个流程都是在我们平台未获取用户授权的情况下进行! 1、用户在好货页面浏览商品,选中商品后进行购买。 2、使用“京东好物街”插件实际购物流程,因为当前选中商品涉及到了优惠券领取操作,所以需要登录京东账号(插件要求)。 3、领取优惠券。 4、商详。 5、订单确认。 6、选择支付方式。 7、支付。 [图片] 补充说明:图一下方的tabbar中"我的小店"、"社群助手"、“个人中心”需要登录才能使用。只有“精选好货”是开放给用户的,但已经支持用户未登录走完全部流程。
2019-09-26 - 越过山丘-微盟小程序开发问题的解决之道
本文整理了微盟小程序开发过程中遇到的一些问题。 问题一: iOS时间转换问题怎么解决?pc端调试、安卓机都能正常执行,iOS上time的结果是null var resData = ‘2017-3-14 10:03:45’ console.log(“返回时间:” + resData) var time = Date.parse(new Date(resData)) ; console.log(time); 解决方案: 将 ‘-’ 替换成 ‘/’ resData = resData.replace(/-/g, ‘/’); 问题二: 直播过程中可能会开始、暂停、继续、结束,客户端需要相应的做暂停和播放、结束,我们是希望通过消息的发送来通知到客户端,但是腾讯云IM的API调接口发群消息,限制 100次/秒,满足不了我们的需求 解决方案: 两点。 1、客户端轮询我们的接口,查询播放状态,舍弃。因为客户端数量过大时,服务端扛不住。 2、操作管理后台界面做轮询,一旦暂停,发送消息。客户端接口通过特定code跟普通消息做区分。 问题三: storage不可以共用,小程序和插件之间数据共享有问题怎么解决呢? 解决方案: 插件使用小程序的共用方法(该方法由插件初始化时从构造函数传入),可以通过此方法从小程序发起请求、存储数据等。 问题四: 小程序的10层跳转限制怎么破? 解决方案: 判断一下当前页面长度是否达到10级,达到10级用redirectTo,未达到用navigateTo var jumpUrl = function (data) { [代码] let pages = getCurrentPages(); if (pages.length >= 10) { wx.redirectTo({ url: data }) } else { wx.navigateTo({ url: data }) } [代码] } 问题五: 浮点运算的精度问题一般怎么解决? 解决方案: 先转成整数,再进行运算,最后进行除法运算。 列举一个减法运算: function accSub(arg1, arg2) { [代码] var r1, r2, m, n; try { r1 = arg1.toString().split(".")[1].length; } catch (e) { r1 = 0; } try { r2 = arg2.toString().split(".")[1].length; } catch (e) { r2 = 0; } m = Math.pow(10, Math.max(r1, r2)); //动态控制精度长度 n = (r1 >= r2) ? r1 : r2; return ((arg1 * m - arg2 * m) / m).toFixed(n); [代码] } 问题六: 小程序二维码scene的长度限制最多32位可见字符,这个有什么妙招吗 解决方案: 1、中间页 + 短参数 [代码] 新建一个中间空白跳转页面,每次生成的二维码都是这个页面, 访问这个页面时,将参数中的scene的值,去指定接口获取完整的 带参数的链接, 然后跳转过去,适用于一个解决方案中有很多个页面需要生成二维码来跳转; [代码] 2、短参数 [代码] 二维码指向到特定页面,scene值为短参数,进入页面时请求接口获取完整的参数(json格式); [代码] 问题七: 还有一个图片预加载问题,有些活动图片太多太大,且不确定使用哪张图片,接口返回数据时即刻显示图片可能出现短暂空白的问题 解决方案: 分三步,你需要建立一个图片预加载器 1、js /** [代码]* 图片预加载组件 */ [代码] class ImgLoader { [代码] /** * 初始化方法,在页面的 onLoad 方法中调用,传入 Page 对象及图片加载完成的默认回调 */ constructor(pageContext, defaultCallback) { this.page = pageContext this.defaultCallback = defaultCallback || function () { } this.callbacks = {} this.imgInfo = {} this.page.data.imgLoadList = [] //下载队列 this.page._imgOnLoad = this._imgOnLoad.bind(this) [代码] this.page._imgOnLoadError= this._imgOnLoadError.bind(this) [代码] } /** * 加载图片 * * @param {String} src 图片地址 * @param {Function} callback 加载完成后的回调(可选),第一个参数个错误信息,第二个为图片信息 */ load(src, callback) { if (!src) return; let list = this.page.data.imgLoadList, imgInfo = this.imgInfo[src] if (callback) this.callbacks[src] = callback //已经加载成功过的,直接回调 if (imgInfo) { this._runCallback(null, { src: src, width: imgInfo.width, height: imgInfo.height }) //新的未在下载队列中的 } else if (list.indexOf(src) == -1) { list.push(src) this.page.setData({ 'imgLoadList': list }) } } _imgOnLoad(ev) { let src = ev.currentTarget.dataset.src, width = ev.detail.width, height = ev.detail.height //记录已下载图片的尺寸信息 this.imgInfo[src] = { width, height } this._removeFromLoadList(src) this._runCallback(null, { src, width, height }) } _imgOnLoadError(ev) { let src = ev.currentTarget.dataset.src this._removeFromLoadList(src) this._runCallback('Loading failed', { src }) } //将图片从下载队列中移除 _removeFromLoadList(src) { let list = this.page.data.imgLoadList list.splice(list.indexOf(src), 1) this.page.setData({ 'imgLoadList': list }) } //执行回调 _runCallback(err, data) { [代码] let callback = this.callbacks[data.src] || this.defaultCallback [代码] callback(err, data) delete this.callbacks[data.src] } [代码] } module.exports = ImgLoader 2、wxml <template name=“img-loader”> [代码] <image mode="aspectFill" wx:for="{{ imgLoadList }}" wx:key="*this" src="{{ item }}" data-src="{{ item }}" bindload="_imgOnLoad" binderror="_imgOnLoadError" style="width:0;height:0;opacity:0" /> [代码] </template> 3、使用 let images = [ [代码] 'http://cdn.weimob.com/saas/activity/bargain/images/arms/shoulie.png', 'http://cdn.weimob.com/saas/activity/bargain/images/arms/shandian.png', 'http://cdn.weimob.com/saas/activity/bargain/images/arms/fengbao.png', 'http://cdn.weimob.com/saas/activity/bargain/images/arms/yingren.png', 'http://cdn.weimob.com/saas/activity/bargain/images/arms/leiming.png', 'http://cdn.weimob.com/saas/activity/bargain/images/arms/caidao.png', 'http://cdn.weimob.com/saas/activity/bargain/images/arms/liehen.png' [代码] ] //初始化图片预加载组件,并指定统一的加载完成回调 this.imgLoader = new ImgLoader(this, null); images.forEach(item => { [代码] this.imgLoader.load(item) [代码] }) 问题八: 服务端时间和客户端时间问题,实际项目中有很多倒计时存在,服务端和客户端时间不对应会引起错乱。 解决方案: 将服务器时间通过接口下发给客户端,计算出二者的差值,计算倒计时的时候,可以将客户端时间加上这个差值来,再跟开始时间或结束时间计算倒计时。 如果你还有什么问题,欢迎在下方留言,我们会尽力为你解答。
2019-03-04 - js经验分享之for循环区别
昨天在做删除列表功能时候碰到的坑 虽然很早以前知道 for(var i in arr)这种方法有坑 但是这么多年简写习惯了 就经常忽略了这个坑 昨天又碰到 特意分享给各位码友 下面我用几段代码举例 [代码]const arr = [2, 5];[代码][代码] [代码][代码]const arr2 = [2, 5];[代码][代码] [代码][代码]let list = [[代码][代码]'张三'[代码][代码], [代码][代码]'李四'[代码][代码], [代码][代码]'王五'[代码][代码], [代码][代码]'张飞'[代码][代码], [代码][代码]'关羽'[代码][代码], [代码][代码]'刘备'[代码][代码], [代码][代码]'曹操'[代码][代码]];[代码][代码] [代码][代码]let list2 = [[代码][代码]'张三'[代码][代码], [代码][代码]'李四'[代码][代码], [代码][代码]'王五'[代码][代码], [代码][代码]'张飞'[代码][代码], [代码][代码]'关羽'[代码][代码], [代码][代码]'刘备'[代码][代码], [代码][代码]'曹操'[代码][代码]];[代码][代码] [代码][代码]//若要从数组中删除下标为 arr 的值 即 王五 和 刘备[代码][代码] [代码][代码]// 使用for循环遍历 arr 在进行删除 利用常见的for循环删除[代码] [代码] [代码][代码]/**[代码][代码] [代码][代码]* 方法1 得出结果 list为[代码][代码] [代码][代码]* 0 : 张三[代码][代码] [代码][代码]* 1 : 李四[代码][代码] [代码][代码]* 2 : 张飞[代码][代码] [代码][代码]* 3 : 关羽[代码][代码] [代码][代码]* 4 : 刘备[代码][代码] [代码][代码]* 并没有删除我们想要的值 [代码][代码] [代码][代码]* */[代码][代码] [代码][代码]for[代码] [代码]([代码][代码]var[代码] [代码]i [代码][代码]in[代码] [代码]arr) {[代码][代码] [代码][代码]console.log([代码][代码]typeof[代码][代码](i)); [代码][代码]//输出为string类型[代码][代码] [代码][代码]list.splice([arr[i]], 1);[代码][代码] [代码][代码]console.log(arr) [代码][代码]// 输出得 [2, 5]和 [2, 5, 01: NaN][代码][代码] [代码][代码]arr.length > 1 && i != arr.length - 1 ? arr[i + 1] -= 1 : [代码][代码]''[代码][代码];[代码][代码] [代码][代码]arr.length - 1 == i && [代码][代码]this[代码][代码].setData({[代码][代码] [代码][代码]list,[代码][代码] [代码][代码]})[代码][代码] [代码][代码]}[代码][代码] [代码][代码]/**[代码][代码] [代码][代码]* 方法2 得出结果 list2为[代码][代码] [代码][代码]* 0 : 张三[代码][代码] [代码][代码]* 1 : 李四[代码][代码] [代码][代码]* 2 : 张飞[代码][代码] [代码][代码]* 3 : 关羽[代码][代码] [代码][代码]* 4 : 曹操[代码][代码] [代码][代码]* 和我们期望相符合[代码][代码] [代码][代码]*/[代码] [代码] [代码][代码]for[代码] [代码]([代码][代码]var[代码] [代码]j = 0; j < arr2.length; j++) {[代码][代码] [代码][代码]console.log([代码][代码]typeof[代码][代码](j)); [代码][代码]//输出为number[代码][代码] [代码][代码]list2.splice([arr2[j]], 1);[代码][代码] [代码][代码]console.log(arr2); [代码][代码]//输出得 [2, 5] 和 [2, 4][代码][代码] [代码][代码]arr2.length > 1 && j != arr2.length - 1 ? arr2[j + 1] -= 1 : [代码][代码]''[代码][代码];[代码][代码] [代码][代码]arr2.length - 1 == j && [代码][代码]this[代码][代码].setData({[代码][代码] [代码][代码]list2,[代码][代码] [代码][代码]})[代码][代码] [代码][代码]}[代码]由此可得 在碰到需要下标进行运算时候就不要简写了 感谢你的耐心观看 希望对你有所帮助
2019-04-12 - 微信手机解密有一定几率出错。
<button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber"></button> 解密有一定的几率出现-41003错误。这是什么原因导致的,是否和参数从前端传至后端有关系?
2018-10-24