- 小程序登录之后如何刷新当前tab页面?
比如这个登录规范里面的,登录按钮在每个tab页面,比如ABC各个tab都有登录入口,登录了之后,如何刷新当前页面? 调用onLoad?onShow?onReady?,还是有可以让页面直接刷新重走生命周期的方法? https://developers.weixin.qq.com/community/operate/doc/000640bb8441b82900e89f48351401?blockType=5
2020-12-01 - uni-app vue-cli Jenkins通过CI构建到微信公众平台
uni-app vue-cli CI流程搭建目前可用服务构建发布小程序,例如Jenkins等,可方便开发者手动点上传. 默认目录[图片] 有疑问留言,回复不及时谅解~ci.js const ci = require('miniprogram-ci') const projectConfig = require('./project.config.json'); const updateVersion = require('./updateVersion.js') const { version } = require('./package.json') const project = new ci.Project({ appid: projectConfig.appid, type: projectConfig.type, projectPath: projectConfig.projectPath, privateKeyPath: projectConfig.privateKeyPath, ignores: ['node_modules/**/*'],// 忽略的文件 }) async function upload() { await ci.upload({ project,// 实例化的project version,// 更新的版本号 setting: {// 上传的配置 es7: true, minify: true, autoPrefixWXSS: true, minifyWXML: true, minifyWXSS: true, codeProtect: true, }, onProgressUpdate: console.log,// 上传进度 }) } async function init() { await updateVersion() await upload() } init() project.config.json { "projectPath": "dist/build/mp-weixin/",//上传目录可修改 "description": "项目配置文件", "setting": {//可修改,目前这个是build可以在微信开发者工具中拿到 "urlCheck": false, "es6": true, "enhance": true, "postcss": true, "preloadBackgroundData": false, "minified": false, "newFeature": false, "coverView": true, "nodeModules": false, "autoAudits": false, "showShadowRootInWxmlPanel": true, "scopeDataCheck": false, "uglifyFileName": false, "checkInvalidKey": true, "checkSiteMap": true, "uploadWithSourceMap": true, "compileHotReLoad": false, "lazyloadPlaceholderEnable": false, "useMultiFrameRuntime": true, "useApiHook": true, "useApiHostProcess": true, "babelSetting": { "ignore": [], "disablePlugins": [], "outputPath": "" }, "enableEngineNative": false, "useIsolateContext": true, "userConfirmedBundleSwitch": false, "packNpmManually": false, "packNpmRelationList": [], "minifyWXSS": true, "disableUseStrict": false, "showES6CompileOption": false, "useCompilerPlugins": false }, "type": "miniprogram", "appid": appid, "privateKeyPath": "./ci-private.key", "condition": { "search": { "list": [] }, "conversation": { "list": [] }, "game": { "list": [] }, "miniprogram": { "list": [] } } } [图片]
2022-06-06 - 给大家推荐一款前端工程化脚手架工具 - mini-core-cli
cli create <project> 根据模板在指定目录下(./test)创建新项目 [代码]# -d 可以传入绝对路径或者相对于终端所在当前目录的相对路径,只能传入一个路径 cli create hello-world -d ./test [代码] cli page <pageName> [description] 根据选择模板创建微信小程序页面骨架,支持 mini-core 模板和原生小程序模板 [代码]# -d 可以传入绝对路径或者相对于终端所在当前目录的相对路径,只能传入一个路径 cli page hello_world 测试页面生成 -d ./ [代码] 自定义页面模板 可以通过[代码]cli config set custom_page_template_dir 模板所在文件夹[代码] 指定页面模板所在目录,建议配置为绝对路径。文件名命名格式建议参照index.js.ejs,其中index任意,js为最终生成的文件名类型,ejs可选,为了在编辑模板时可以使用ejs支持的表达式,建议使用ejs格式文件,在创建模板时会传入如下数据,可按需对应取值。 [代码]<!--输入 cli page hello_world 测试 -d ./test--> <!--结果:hello-world--> <%= data.className %> <!--结果:cli config set author 值,如:い 狂奔的蜗牛--> <%= data.author %> <!--结果:HelloWorld--> <%= data.controllerName %> <!--结果:hello_world--> <%= data.fileName %> <!--结果:当前日期,如:2022-07-09 22:48:15--> <%= data.createDate %> <!--结果:测试--> <%= data.description %> [代码] cli component <componentName> [description] 根据选择模板创建组件骨架,支持以下模板: mini-core 模板(微信小程序) 原生小程序页模板(微信小程序) vue.js 2.x模板 vue.js 3.x模板 [代码]# -d 可以传入绝对路径或者相对于终端所在当前目录的相对路径,只能传入一个路径 cli component hello_world 测试组件生成 -d ./ [代码] 自定义组件模板 可以通过[代码]cli config set custom_component_template_dir 模板所在文件夹[代码]指定组件模板所在目录,其他同创建页面自定义模板 cli rm 批量删除指定目录(./test)下的空文件夹或者指定的扩展名文件 [代码]# 删除./test目录下的空文件夹和.*js文件 # -D 可以传入绝对路径或者相对于终端所在当前目录的相对路径,可以传入多个路径 # -e 指定删除文件扩展名,如.js .wxss # -f 静默强制删除,不会进行是否删除确认 cli rm -D ./test -e .js -f [代码] cli rn 批量重命名指定目录(./test)下的文件 [代码]# -d 可以传入绝对路径或者相对于终端所在当前目录的相对路径,只能传入一个路径 # -f 静默重命名,不会进行是否重命名确认 # -p 待替换字符串 值 1.test.js => 1.js cli rn -d ./test -p '.test' '' [代码] cli upload [localPicturePaths…] 上传图片到文件服务器,支持单张图片与多张图片,如果为单张图片,则自动复制到图片网络地址到粘贴板, 图片为多张时,则列出所有图片通过键盘方向键进行选择进行复制图片网络地址。 [代码]# 上传文件需要先通过cli config完成相关信息配置 # cli config expression 'data[0].fileUrl' 配置服务器返回json数据解析表达式 # cli config host www.uicoder.cn 配置文件服务器接口地址 cli upload '/Users/snail/Desktop/learning/图片素材/740c79ce-af29-41b8-b78d-5f49c96e38c4.jpg' '/Users/snail/Desktop/learning/图片素材/00874a5e-0df2-446b-8f69-a30eb7d88ee8.png' [代码] cli compress [localPicturePaths…] 对本地图片进行压缩,支持单张图片与多张图片 [代码]# -d 指定压缩后的图片存放的目录,不指定时会压缩并覆盖当前图片 cli compress '/Users/snail/Desktop/learning/图片素材/740c79ce-af29-41b8-b78d-5f49c96e38c4.jpg' '/Users/snail/Desktop/learning/图片素材/00874a5e-0df2-446b-8f69-a30eb7d88ee8.png' [代码] cli base64 <localPicturePath> 对本地图片进行base64编码,编码成功后可选择将结果复制到粘贴板或者控制台打印,base64操作只支持单张图片 [代码]cli base64 '/Users/snail/Desktop/learning/图片素材/740c79ce-af29-41b8-b78d-5f49c96e38c4.jpg' [代码] cli server 开启一个静态资源服务器,支持文件更改时自动刷新,想了解更多请查看live-server包 [代码]# -H 绑定域名localhost或者127.0.0.1或者本机ip,默认为:127.0.0.1 # -P 绑定端口号,默认为:8080 # -F 指定浏览器默认访问的文件路径,默认为:index.html # -O 启动完成后是否打开默认浏览器,默认为:false # -d 静态资源根目录,默认为:./ cli server -d ./test [代码] cli lessc 编译less为css、wxss [代码]# -e 指定less编译生成的目标文件后缀,可以传入多个,如:css wxss,不传入时默认css # -w 是否监听-d指定目录下的所有less变化,默认为false。会编译目录下的所有less文件,如果设置为true,只会监听文件 ,如果文件没有做修改,不会进行编译 # -d 静态资源根目录,默认为:./ cli lessc cli lessc -d ./ -w -e wxss [代码] cli exec [commands…] 同步执行多条命令,如果命令执行后有后续操作,如选择或者输入,则不适合通过当前方式执行 [代码]# -d 执行命令时所在目录,默认为:./ # -f 静默,不会提示确认 cli exec 'npm install' 'npm run server' -d ./ -f [代码] cli config <option> [configKeyAndValue…] 设置配置项 host 文件服务器接口地址 expression 图片上传到服务器返回 json 获取图片网络地址解析表达式,如:data[0].fileUrl author 模板注释中的作者 tinify_api_key,如果配置了当前值,则自动激活图片压缩功能,不配置时不会激活压缩功能,压缩技术由tinypng提供 compression_times 图片压缩次数,默认为1 [代码]# 设置文件服务器接口地址 cli config set host http://www.uicoder.cn # 查看配置信息 cli config get host # 删除配置信息 cli config delete host # 查看所有配置信息 cli config list [代码] cli hellojson 打开json格式化工具 - HelloJSON [代码]cli hellojson [代码] cli minicore 打开微信小程序框架mini-core帮助文档 [代码]cli minicore [代码] 更多功能可留意如下地址更新 gitthub地址 npm包地址
2022-07-10 - 从0到1开发一个小程序cli脚手架(二) --版本发布/管理篇
接上文 《从0到1开发一个小程序cli脚手架(一)–创建页面/组件模版篇》 github地址:https://github.com/jinxuanzheng01/xdk-cli 觉得有用的朋友帮忙给项目一个star,谢谢 上文大家应该大致学会了怎么搭建一个cli脚手架,包括实现了一个快速生成启动模版的功能,本质上作为脚手架应该可以做更多的事情,本篇文章会实现一些新的功能,例如:自动发布体验版,版本号控制,环境变量控制 痛点 不知道大家有没有一天发多次版本或者一天给多个小程序发版的经历,按照微信正常的发布流程,需要: 修改版本号/版本描述 修改发布环境 点击微信开发者工具上传体验版 提交审核 确认环境/版本 点击发布 其中所有的1,2步为手工修改config文件,第5步是确认手工修改config文件的正确性,毕竟人总会犯错,作者表示就干过线下环境发布到测试环境的事情,而且这是在做了第5步的情况下,很遗憾没有仔细核对 为了不再次发生同样的事情导致引咎辞职,那么有没有更好的方法呢 ?自然是有的,既然人不可靠,那么直接把它流程化自然就可以了 准备工作 最好阅读了上篇文章《从0到1开发一个小程序cli脚手架(一)–创建页面/组件模版篇》,并搭建了一个简单的demo 需要了解微信小程序提供的一些cli能力, 点击这里 Let‘ go 后续的很多流程上的实操代码为了缩短篇幅会以伪代码的形式来进行描述,强烈推荐先阅读上文,如果需要更详细的实操代码请去github仓库查看 实际效果图:[图片] 梳理流程 识别命令行 询问问题,拿到版本号和版本描述 调用微信提供的cli能力,进行体验版上传 是不是发现非常简单,事实也是如此,整个功能做下来也就60行代码 ~ 目录结构 项目结构分为入口文件,配置文件 [代码]- lib - publish-weapp.js - index.js - config.js - node_modules - package.json [代码] config.js用来记录一些基础常量和默认项的配置,例如项目路径,执行路径等 [代码]module.exports = { // 根目录 root: __dirname, // 执行命令目录路径 dir_root: process.cwd(), // 小程序项目路径 entry: './', // 项目编译输出文件夹 output: './', } [代码] 识别命令行 在入口文件(index.js)处利用第三方库 commander 识别命令行参数,同时作为路由进行任务分发, [代码]#!/usr/bin/env node const version = require('./package').version; // 版本号 /* = package import -------------------------------------------------------------- */ const program = require('commander'); // 命令行解析 /* = task events -------------------------------------------------------------- */ const publishWeApp = require('./lib/publish-weapp'); // 发布体验版 program .command('publish') .description('发布微信小程序体验版') .action((cmd, options) => publishWeApp(); /* = main entrance -------------------------------------------------------------- */ program.parse(process.argv) [代码] 创建交互命令 接下来是一个QA环节,这时候需要根据自己的实际需要安排自己需要的命令,可以回想一下要解决的问题: 修改版本号/版本描述 修改发布环境 根据cli自动上传体验版 大概得出这样一个队列: [代码]function getQuestion({version, versionDesc} = {}) { return [ // 确定是否发布正式版 { type: 'confirm', name: 'isRelease', message: '是否为正式发布版本?', default: true }, // 设置版本号 { type: 'list', name: 'version', message: `设置上传的版本号 (当前版本号: ${version}):`, default: 1, choices: getVersionChoices(version), filter(opts) { if (opts === 'no change') { return version; } return opts.split(': ')[1]; }, when(answer) { return !!answer.isRelease } }, // 设置上传描述 { type: 'input', name: 'versionDesc', message: `写一个简单的介绍来描述这个版本的改动过:`, default: versionDesc }, ] } [代码] 最后获得的json对象,是这个样子: [代码]{isRelease: true, version: '1.0.1', versionDesc: '这是一个体验版'} [代码] 确定是否发布正式版 主要是因为体验版并非完全是发行版,公司内部测试的时候也是需要发布测试用体验版的,但是又涉及只有正式版本修改本地版本文件,那么就只能多此一问作为区分了 设置版本号 / 设置版本信息 [图片] 设置上述如图的 体验版上的版本号和描述信息,这里有个case是只有第一个问题选择发布正式版才会让你设置版本号,测试版本使用默认版本号0.0.0,这也是区分体验版的一种方式 [代码] when(answer) { return !!answer.isRelease } [代码] 这里只设置了三个问题:是否发布正式版,设置版本号,设置上传描述,当然你也可以设置自己想做的其他事情 上传体验版 翻了下小程序的文档,大概犹如下几个关键点: 找到cli工具 小程序cli并非全局安装,需要自己去索引路径,命令行工具所在位置:macOS: [代码]<安装路径>/Contents/MacOS/cli[代码] Windows: [代码]<安装路径>/cli.bat[代码] mac的 安装路径 如果是默认安装的话,是/Applications/wechatwebdevtools.app/, 外加cli的位置是: /Applications/wechatwebdevtools.app/Contents/Resources/app.nw/bin/cli windows 的话作者表示没有这个环境,只能大家自己探索了 拼凑上传命令 官方文档给了非常详细的描述: [图片] [代码]# 上传路径 /Users/username/demo 下的项目,指定版本号为 1.0.0,版本备注为 initial release cli -u 1.0.0@/Users/username/demo --upload-desc 'initial release' # 上传并将代码包大小等信息存入 /Users/username/info.json cli -u 1.0.0@/Users/username/demo --upload-desc 'initial release' --upload-info-output /Users/username/info.json [代码] 编写上传逻辑 基本流程: 获取cli -> 获取当前版本配置 -> 问题队列(获取上传信息)-> 执行上传(cli命令)-> 修改本地版本文件 -> 成功提示 [代码]// ./lib/publish-weapp.js 文件 module.exports = async function(userConf) { // cli路径 const cli = `/Applications/wechatwebdevtools.app/Contents/Resources/app.nw/bin/cli`; // 版本配置文件路径 const versionConfPath = Config.dir_root + '/xdk.version.json'; // 获取版本配置 const versionConf = require(versionConfPath); // 开始执行问题队列 anser case: {isRelease: true, version: '1.0.1', versionDesc: '这是一个体验版'} let answer = await inquirer.prompt(getQuestion(versionConf)); versionConf.version = answer.version || '0.0.0'; versionConf.versionDesc = answer.versionDesc; //上传体验版 let res = spawn.sync(cli, ['-u', `${versionConf.version}@${Config.output}`, '--upload-desc', versionConf.versionDesc], { stdio: 'inherit' }); if (res.status !== 0) process.exit(1); // 修改本地版本文件 (当为发行版时) !!answer.isRelease && await rewriteLocalVersionFile(versionConfPath, versionConf); // success tips Log.success(`上传体验版成功, 登录微信公众平台 https://mp.weixin.qq.com 获取体验版二维码`); } // 修改本地版本文件 function rewriteLocalVersionFile(filepath, versionConf) { return new Promise((resolve, reject) => { fs.writeFile(filepath, jsonFormat(versionConf), err => { if(err){ Log.error(err); process.exit(1); }else { resolve(); } }) }) } [代码] 注意这里需要在项目根目录下创建一个xdk.version.json的版本文件,详细配置说明见仓库 大致是这样的结构: [代码]// xdk.version.json { "version": "0.12.2", "versionDesc": "12.2版本" } [代码] 到这里基本就完成了上传的所有步骤,可以当前项目下键入[代码]xdk-cli publish[代码]查看一下程序是否正常运行,注意上传出错记得检查是否处于登录状态 扩展:关于版本号自增 可以看到在版本号环节使用的是list类型,而非input类型,这是因为手写版本号有写错的风险, 还是让我做选择题吧 emmm… 最终效果: [图片] 就不在这里展开了,可以下, 搜索[代码]getVersionChoices[代码]函数: https://github.com/jinxuanzheng01/xdk-cli/blob/master/lib/publish-weapp.js 解决打包工具的问题 你会发现是运行cli命令就直接发布了,那么用gulp,webpack等工具项目因为需要上传dist目录而非src的原因,需要先进行打包再进行发布: gulp -> xdk-cli publish ,将一个动作拆成了两个 遗忘了一个怎么办?纠结了 不知道大家对微信开发者工具的上传钩子还有没有印象,要实现的大概就是这么个东西, 一个上传前预处理的钩子 [图片] 这个实现起来也很简单,首先给用户的配置文件(xdk.config.js)开一个可配置项: [代码]// xdk.config.js { // 发布钩子 publishHook: { async before(answer) { this.spawnSync('gulp'); return Promise.resolve(); }, async after(answer) { this.log('发布后的钩子执行了~'); return Promise.resolve(); } } } [代码] 在publish-weapp.js中去识别钩子即可: [代码] // 前置钩子函数 await userConf.publishHook.before.call(originPrototype, answer); //上传体验版 let res = spawn.sync(cli, ['-u', `${versionConf.version}@${Config.output}`, '--upload-desc', versionConf.versionDesc], { stdio: 'inherit' }); // 后置钩子函数 await userConf.publishHook.after.call(originPrototype, answer); [代码] 当然,你需要判断下钩子是否存在,类型判断,包括需要返回给用户配置里一些基本的方法和属性,例如:日志输出,命令行执行等 我这边以gulp为例,可以看到在publsih前先执行[代码]gulp[代码],后进行发布,最后log出一行提示信息 完全符合预期 解决环境变量切换问题 解决了自动上传的问题,接下来就要解决环境变量切换的问题了,这里还要借用下刚才写的钩子函数: [代码]{ // 发布钩子 publishHook: { async before(answer) { this.spawnSync('gulp', [`--env=${answer.isRelease ? 'online' : 'stage'}`]); return Promise.resolve(); }, async after(answer) { this.log.success('发布后的钩子执行了~'); return Promise.resolve(); } } } [代码] 以gulp为例,根据之前的回答的结果answer.isRelease,判断是否为使用正式环境 如果你没有使用编译工具,也可以提出一个env的config文件,使用fs模块直接重写该环境变量 下面是一串伪代码: [代码]目录结构 - app (小程序项目) - page - app.json ... - env.js - xdk.config.js - xdk.version.js - envConf.js // envConf.js module.exports = { ['online']: { appHost1: 'https://app.xxxxxxxxx.com', appHost2: 'https://app.xxxxxxxxx.com', }, ['stage']: { appHost1: 'https://stage.xxxxxxxxx.com', appHost2: 'https://stage.xxxxxxxxx.com', } }; // xdk.config.js publishHook: { async before(answer) { let config = require('envConf.js')[answer.isRelease ? 'online' : 'stage']; fs.writeFile('./app/env.js', jsonFormat(jsonConf), (err) => { if(err){ this.log.error('写入失败') }else { this.log.success('写入成功); } }); return Promise.resolve(); } [代码] 打包工具的问题还有环境变量的问题,我都没有写在cli里面,是因为不同项目之间对环境变量的写法,格式都不尽相同,具体要怎么做还是要留给开发者自己来确定吧,这样看起来更灵活一些 最后 总之利用脚手架解决了从发布到上线的一连串问题,使得不再担心因为切换环境导致线上bug,也不再担心写版本号写错的问题,确认线上环境这个环境也变成了一个非强需求的事情 整个上线流程也只需要:xdk-cli publish -> 提交审核即可,而且整个代码也并不复杂,publish-weapp.js整个文件算上注释也就60行代码,何乐不为呢? 注:下篇会讲一下如何做自定义指令,帮助小伙伴可以更自由的适配不同的项目~
2019-08-05 - 小程序性能优化指南
开发者可通过开发者工具中的性能扫描工具提前发现代码中的可优化项: 1. 代码包不包含插件大小超过 1.5 M 【建议】小程序代码包单个包大小限制为2M。因此我们建议开发者在开发时,如果遇到单包体积大于1.5M的情况,可以采取分包的方式,把部分代码拆分到分包去,降低单个包的体积,提升小程序的加载速度。具体可以查看文档《使用分包》。 2. 引用插件大小超过 200 K 【知会】小程序插件的大小是会算进小程序代码包2M体积限制中的。因此当我们发现开发者引用的插件体积大于200K时,会对开发者予以提示,避免出现上传阶段提示代码包体积超限,但是不知道为何超限的问题。 3. 图片和音频资源大小超过 200 K 【建议】小程序代码包里可以存放一些必要的静态资源(如tabbar的icon等);但其他非必要的静态资源体积过大会影响小程序代码包加载速度。因此我们建议图片、音频等静态资源体积大小超过200K时,将它们上传到CDN,用URL引入会是个更好的选择。 4. 主包存在仅被其他分包依赖的JS 【建议】当主包里存在一些JS文件只会被分包使用(而主包自己不使用)时,我们建议把这些JS文件从主包中拆分出去,放到对应的分包里,从而优化主包的加载速度。 5. 主包存在仅被其他分包依赖的组件 【建议】当主包里存在一些组件只会被分包使用(而主包自己不使用)时,我们建议把这些组件从主包拆分出去,并且可以使用 分包异步化 这个特性加载这些组件,从而优化主包的加载速度。 6. 存在无使用的插件 【必须】如果有无使用的插件,请将其从 app.json 中去除。不然它会占用代码包体积,也会延迟代码包加载的时间。 7. 存在无使用的组件 【必须】如果在对应页面JSON的 `usingComponents` 里声明的组件但是没有使用,请将其从 `usingComponents` 里去除。 8. 未开启JS压缩 【必须】在工具「详情」-「本地设置」中开启「上传代码时自动压缩脚本文件」的设置 [图片] 9. 未开启WXML压缩 【必须】在工具「详情」-「本地设置」中开启「上传代码时自动压缩wxml文件」的设置 [图片] 10. 未开启WXSS压缩 【必须】在工具「详情」-「本地设置」中开启「上传代码时自动压缩样式文件」的设置 [图片] 11. 存在无依赖文件 【必须】在「代码质量」面板,点击「建议去除」后,可以打开代码依赖分析面板的「无依赖文件」页面,这里可以看到代码包里没有被用到的文件。请在代码包中去除这部分文件,减小体积并优化加载速度。 在本地开发的过程中,会自动过滤无依赖的文件,如果出现误过滤的情况,可以在 project.config.json 的 setting 字段中添加 ignoreDevUnusedFiles 为 false,也可以在 packOptions 的 include 字段中手动将被忽略的文件引入,同时欢迎发帖反馈误报的情况提交代码片段帮助我们完善此功能 注意:页面若为配置在 app.json 中,将被识别为无依赖文件 [图片] 12. 未开启组件懒注入(按需注入) 【必须】在 app.json 中加入 `"lazyCodeLoading": "requiredComponents"` 可以开启小程序组件按需注入特性。 其他优化内容,请点击学习《小程序性能优化实践》课程 [图片]
2025-07-24 - 求助官方 getRealtimeLogManager 5Kb长度的限制?
RealtimeLogManager.info()日志内容,可以有任意多个。每次调用的参数的总大小不超过5Kb 现在看到超过5Kb就会显示 UserLog:fail Log Size xxxx Exceed. 我在客户端尝试截断,有时灵有时不灵,有些日志上看到了截断后的信息,有些依然是 UserLog:fail Log Size xxxx Exceed.; 1,请求官方在接口上自动截断,尽量显示日志信息; 2,望指教下 5Kb 应该怎么计算才能准确无误? 直接把 console.log 封装了 RealtimeLogManager [代码]if[代码] [代码](wx.getRealtimeLogManager) {[代码][代码] [代码][代码]const logger = wx.getRealtimeLogManager()[代码][代码] [代码][代码]let consoleLog=console.log[代码][代码] [代码][代码]function[代码] [代码]str2ab(str) {[代码][代码] [代码][代码]var[代码] [代码]buf = [代码][代码]new[代码] [代码]ArrayBuffer(str.length * 2); [代码][代码]// 每个字符占用2个字节[代码][代码] [代码][代码]var[代码] [代码]bufView = [代码][代码]new[代码] [代码]Uint16Array(buf);[代码][代码] [代码][代码]for[代码] [代码]([代码][代码]var[代码] [代码]i = 0, strLen = str.length; i < strLen; i++) {[代码][代码] [代码][代码]bufView[i] = str.charCodeAt(i);[代码][代码] [代码][代码]}[代码][代码] [代码][代码]return[代码] [代码]buf;[代码][代码] [代码][代码]}[代码][代码] [代码][代码]function[代码] [代码]ab2str(buf) {[代码][代码] [代码][代码]return[代码] [代码]String.fromCharCode.apply([代码][代码]null[代码][代码], [代码][代码]new[代码] [代码]Uint16Array(buf));[代码][代码] [代码][代码]}[代码][代码] [代码][代码]function[代码] [代码]wlog(){[代码][代码] [代码][代码]let argumentsX=[...arguments][代码][代码] [代码][代码]return[代码] [代码]new[代码] [代码]Promise([代码][代码]function[代码] [代码](success,fail) {[代码][代码] [代码][代码]let params=[代码][代码]""[代码][代码] [代码][代码]argumentsX.forEach((val,i)=>{[代码][代码] [代码][代码]let str=JSON.stringify(val);[代码][代码] [代码][代码]if[代码][代码](!str) [代码][代码]return[代码][代码];[代码][代码] [代码][代码]params+=str[代码][代码] [代码][代码]})[代码][代码] [代码][代码]let bs=str2ab(params.substr(0,1024*5))[代码][代码] [代码][代码]logger.info(ab2str(bs.slice(0,1024*5)).toString());[代码][代码] [代码][代码]success()[代码][代码] [代码][代码]})[代码][代码] [代码][代码]}[代码][代码] [代码][代码]console.log=[代码][代码]function[代码] [代码]() {[代码][代码] [代码][代码]consoleLog(...arguments)[代码][代码] [代码][代码]wlog(...arguments);[代码][代码] [代码][代码]}[代码][代码]}[代码]
2019-09-06 - 微信开发者工具下载的 sourcemaps 怎么用。
什么是 Sourcemaps uglifyjs、bable 等工具会对 源代码 进行编译处理生成编译后的代码(下称目标代码),而 sourcemaps 就是保留了目标代码在源代码中的 位置信息 --------- 大神分割线 --------- 如何解读 Sourcemaps Sourcemaps 是一个 json [代码]{ "version": 3, "sources": ["a.js", "b.js"], // 源文件列表,这个表示是由 a.js 和 b.js 合并生成 "names": ["myFn", "test"], // 如果开启了变量名混淆,这里会保留变量名在源文件中名字信息 "sourcesContent: [], // 可选项,保存源码信息,顺序与 sources 字段对应,chrome 的 sources 面板中源码使用了这个字段的内容进行展示 "sourceRoot": "", // 源文件所在的目录信息 "file": "dist.js", // 可选,编译后的文件名 "mappings": "" // 这个是重点,是目标代码和源文件的位置的映射关系 } [代码] mappings 目标文件"行"的信息 mappings 是使用 ; 分隔的,每个部分对应目标代码的行 如: “;AAAA;AAAA,BBBB;;” 本例子目标文件有 4 行 第 0 行和第 3 行没有源文件对应信息,所以这两行是编译过程中加入的代码 目标文件的"列"信息 如: “AAAA,CAEA,CAEA;” ‘,’ 表示行内的位置信息分隔符 本例表示目标文件的这一行有三个有效的位置信息。 位置信息的第一位表示目标文件的列的 偏移 信息 本例中,表示列的信息是 ‘A’、‘C’、‘C’,对应的数字为 0、+1、+1,(vlq 编码,在线编解码工具) 注意,这个是偏移信息; 列数从 0 开始,依次累加偏移值可以算出当前的位置信息对应的真正的列 所以本例中表示的是目标文件的第 n 行中的第 0 列,第 1 列,第 2 列(没错是第 2 列) 源文件的信息 如:‘AAAA;ACAA;ADAA;’ 位置信息的第二位表示源文件的信息,本例子中是 ‘A’、‘C’、‘D’,对应数字是 0、+1、-1 如果 sourcemaps 中的 sources 字段只有一个文件的话,那么位置信息中第二位一直是 A(不需要偏移) 假设 sourcemaps 中 sources: [‘a.js’, ‘b.js’] 本例的意思是 AAAA: 目标文件第 0 行第 0 列 对应 第 0 个文件 a.js ACAA; 目标文件第 1 行第 0 列 对应 第 1 个文件 b.js ADAA; 目标文件第 2 行第 0 列 对了 第 0 个文件 a.js (偏移是 -1 又回到了 a.js) 源文件的行信息 位置信息的第三位表示源文件中的行的信息, 理解了位置偏移的概念,我们很容易理解 如:‘AACA,CACA;AACA;‘ 那么 AACA: 目标文件的第 0 行第 0 列 对应 第 0 个文件的第 1 行 CACA: 目标文件的第 0 行第 0+1 列 对应 第 0 个文件的第 1+1 行 AACA:目标文件的第 1 行第 0 列 对应 第 0 个文件的第 1 行 (注意:’;’ 后的行列偏移信息归 0) 源文件中的列信息 位置信息的第四位表示源文件中的列的信息 如:'AAAA,CAAC;' 那么 AAAA: 目标文件的第 0 行第 0 列 对应 第 0 个文件的第 0 行第 0 列 CAAC: 目标文件的第 0 行第 0+1 列 对应 第 0 个文件的第 0 行第 0+1 列 位置信息的第五位 第五位表示变量的偏移,对应 sourcemaps 中的 names 字段,表示目标文件中的变量名对应域源文件中的变量 如:’AAAA,CAACC;AAAAD;' sourcemaps 中 names 字段是 [‘a’, ‘b’] 那么 AAAA: 目标文件的第 0 行第 0 列 对应 第 0 个文件的第 0 行第 0 列,没有变量的信息 CAACC: 目标文件的第 0 行第 0+1 列 对应 第 0 个文件的第 0 行第 0+1 列,有变量信息,变量在源文件中是 ‘b’ (0+1=1) AAAAD: 目标文件的第 1 行第 0 列 对应 第 0 个文件的第 0 行第 0 列,有变量信息,变量在源文件中是 ‘a’ (1-1=0) --------- 大神分割线 --------- 怎么使用 Sourcemaps Q: 线上小程序报错,我怎么通过 sourcemaps 还原到源代码中? A: 如报错 appservice.js 1:15000, 表示目标文件第一行 第 15000 列位置报错。根据上文介绍的,通过 mappings 字段算。 Q: 不会。 A: 如果你会写代码的话,参考下边 [代码]import fs = require('fs') import {SourceMapConsumer} from 'source-map' async function originalPositionFor(line, column) { const sourceMapFilePath = '如果你不真的替换的成 sourcemaps 在硬盘中的位置,那你还是放弃自己写代码吧。 ' const sourceMapConsumer = await new SourceMapConsumer(JSON.parse(fs.readFileSync(sourceMapFilePath, 'utf8'))) return sourceMapConsumer.originalPositionFor({ line, column, }) } originalPositionFor(出错的行,出错的列) [代码] Q: 不会写代码 A: 下载最新版的开发者工具,菜单-设置-拓展设置-调试器插件 [图片] [图片] Q: 为啥都是 null? A: 每个小程序版本都应该对应一个sourcemap文件。 运营中心那里下载的 sourcemap 是对应线上最新的小程序版本。但运营中心的报错集合了多个小程序版本。拿旧小程序版本的报错信息,和最新版本的 sourcemap,是匹配不出的。开发者工具和ci 上传的时候,会提示下载对应版本的 sourcemap 信息,可以自助保存。 [图片] Q: 怎么确定有没有版本对应上 A: 下载的 sourcemap 中有个 wx 字段,标明了该 sourcemap 文件对应小程序版本号。 [图片] [图片] 前提 1.确保发生错误的小程序版本和下载回来的 sourcemap 版本是一致的。 a. 下载 sourceMap 文件,可在 mp 后台或开发者工具上传成功弹窗下载 2.确保 map 文件和发生错误的 js 文件是对应的。sourcemap 的目录和文件说明 a. APP 是主包,FULL 是整包(仅在不支持分包的低版本微信中使用),其他目录是分包 b. 每个分包下都有对应的 app-service.js.map 文件。 c. 如果是使用了按需注入特性(app.json中配置了lazyCodeLoading),那么每个分包下还会有 appservice.app.js.map(对应分包下非页面的js),和所有页面的 xxx.js.map 以上事情都确保正确之后,还是出现行列号匹配不出来的情况。那就需要进一步排查。 线上运行的小程序 sourcemap 文件是怎么生成的? 处理流程:源码 [ a.js a.js.map b.js b.js.map ] -> 开发者工具(JS转 ES5,压缩)-> 微信后台(合并 js 文件)[ appservice.app.js appservice.app.js.map]。 注意:如果源码在交给工具之前是经过了 webpack 等打包工具的处理,那源码这里需要有 map 文件。否则不需要存在 map 文件。 可以看出,map 文件经过三个步骤的处理,每个步骤都有可能导致出错,因此开发者需要先排查,是否是前两个步骤出错导致的 map 文件失效的。 如何排查前两个步骤产生的 map 文件是否有问题。 1.排查 a.js.map 文件是否有问题。 a. 可以在 a.js 的代码中写一下 throw new Error(‘test sourcemap’)。 b. 使用了 webpack 的情况下,要构建为生产环境的版本。 c. 在开发者工具模拟器中运行对应的页面,看看控制台中的报错,错误行列号是否能正常映射到源文件。 2.排查 开发者工具(JS转 ES5,压缩)步骤是否有问题。 在排查完第一步的基础上,点击预览,用微信上扫码预览,并打开调试 vConsole 功能,检查 vConsole 中是否有报错信息,检查报错信息中的行列号是否能正常映射到源文件。 如何排查 微信后台(合并 js 文件)是否有问题。 a. 一定要先排查完前两个步骤再来排查这一步,一般情况下,这一步是不会出错的。 b. 如果有问题,也只会导致 map 文件中的行号信息出现偏移。比如 Error 信息中显示报错地址是 100: 200,行号是 100。那么你可能直接用 100: 200 在 map 文件中搜索不出信息,但是如果 用 150: 200 就可以搜索出来,说明行号偏移了 50。那其他报错也可以偏移 50 后再进行搜索就找到结果。 c. 怎么排查偏移了多少?可以结合 error.message 的内容,初步判断大概错误的内容是什么。把对应的 map 文件放到这个网站上 source-map-visualization 进行搜索,找出哪些相同列号的地方。再结合 error.message 的内容进行判断。 d. 如果排查到是这一步导致的问题,请在社区上联系我们,我们会在后续版本进行修复。 依旧排查不出原因? 先整理一下按照上述步骤排查的结论,再在社区上联系我们协助
2023-02-10 - wx.canvasToTempFilePath生成图片背景色成黑色了
搜索过多个帖子,还是没解决.. //绘制图片 const ctx = wx.createCanvasContext('myCanvas') ctx.setFillStyle('#FFFFFF') ctx.draw(true, setTimeout(function () { wx.canvasToTempFilePath({ canvasId: 'myCanvas', fileType: 'jpg', success: function (res) { wx.saveImageToPhotosAlbum({}); android 是微信7.0.4版本上导出后图片背景色是黑色了。ios上,微信7.0.3版本就没问题
2019-04-28 - canvas绘制圆角矩形安卓机型不兼容
iOS机型正常: [图片] 安卓机型绘制如下: [图片]
2018-06-02 - 小程序canvas切圆角矩形android存在兼容问题
[图片] [图片] [图片] 图1是ios的效果,图2是android的效果,图3是代码片段,求canvas大神指点
2018-04-27 - 为什么图片链接可正常访问但image组件加载不出来图片?
因为 image 控件的图片拉取本质上是 web 上的 backgroundImage,很多时候是由于图片不规范(content-type / length / 是否302跳转等 )导致拉取不成功,最终表现为加载不出图片。关于这一块我们在持续优化中
2021-12-17 - 教你怎么监听小程序的返回键
更新:2020年7月28日08:51:11 基础库2.12.0起,可以调用wx.enableAlertBeforeUnload监听原生右上角返回、物理返回以及wx.navigateBack时弹框提示 AIP详情请看: https://developers.weixin.qq.com/miniprogram/dev/api/ui/interaction/wx.enableAlertBeforeUnload.html //======================================== 怎么监听小程序的返回键? 应该有很多人想要监听用户的这个动作吧,但是很遗憾,小程序不会给你这个API的,那是不是就没辙了? 幸好我们还可以自定义导航栏,这样一来我们就可以监听用户的这一动作了。 什么?这你已经知道啦? 那好咱们就不说自定义导航栏的返回监听了,说一下物理返回和左滑?右滑?(不管了,反正是滑)返回上一页怎么监听。 监听物理返回 首先说一下这个监听方法的缺点,虽说是监听,但是还是无法真正意义上的监听并拦截来阻止页面跳转,页面还是会返回上一页,而后重新载入刚刚的页面,如果这不是你想要的,那可以不用往下看了 其次说一下用到什么东西: wx.onAppRoute、wx.showModal 最后是一些主要代码: 重写wx.showModal,主要是加个confirmStay参数和使wx.showModal Promise化 [代码]const { showModal } = wx; Object.defineProperty(wx, 'showModal', { configurable: false, // 是否可以配置 enumerable: false, // 是否可迭代 writable: false, // 是否可重写 value(...param) { return new Promise(function (rs, rj) { let { success, fail, complete, confirmStay } = param[0] param[0].success = (res) => { res.navBack = (res.confirm && !confirmStay) || (res.cancel && confirmStay) wx.setStorageSync('showBackModal', !res.navBack) success && success(res) rs(res) } param[0].fail = (res) => { fail && fail(res) rj(res) } param[0].complete = (res) => { complete && complete(res) (res.confirm || res.cancel) ? rs(res) : rj(res) } return showModal.apply(this, param); // 原样移交函数参数和this }.bind(this)) } }); [代码] 使用wx.onAppRoute实现返回原来的页面 [代码]wx.onAppRoute(function (res) { var a = getApp(), ps = getCurrentPages(), t = ps[ps.length - 1], b = a && a.globalData && a.globalData.pageBeforeBacks || {}, c = a && a.globalData && a.globalData.lastPage || {} if (res.openType == 'navigateBack') { var showBackModal = wx.getStorageSync('showBackModal') if (c.route && showBackModal && typeof b[c.route] == 'function') { wx.navigateTo({ url: '/' + c.route + '?useCache=1', }) b[c.route]().then(res => { if (res.navBack){ a.globalData.pageBeforeBacks = {} wx.navigateBack({ delta: 1 }) } }) } } else if (res.openType == 'navigateTo' || res.openType == 'redirectTo') { if (!a.hasOwnProperty('globalData')) a.globalData = {} if (!a.globalData.hasOwnProperty('lastPage')) a.globalData.lastPage = {} if (!a.globalData.hasOwnProperty('pageBeforeBacks')) a.globalData.pageBeforeBacks = {} if (ps.length >= 2 && t.onBeforeBack && typeof t.onBeforeBack == 'function') { let { onUnload } = t wx.setStorageSync('showBackModal', !0) t.onUnload = function () { a.globalData.lastPage = { route: t.route, data: t.data } onUnload() } } t.onBeforeBack && typeof t.onBeforeBack == 'function' && (a.globalData.pageBeforeBacks[t.route] = t.onBeforeBack) } }) [代码] 改造Page [代码]const myPage = Page Page = function(e){ let { onLoad, onShow, onUnload } = e e.onLoad = (() => { return function (res) { this.app = getApp() this.app.globalData = this.app.globalData || {} let reinit = () => { if (this.app.globalData.lastPage && this.app.globalData.lastPage.route == this.route) { this.app.globalData.lastPage.data && this.setData(this.app.globalData.lastPage.data) Object.assign(this, this.app.globalData.lastPage.syncProps || {}) } } this.useCache = res.useCache res.useCache ? reinit() : (onLoad && onLoad.call(this, res)) } })() e.onShow = (() => { return function (res) { !this.useCache && onShow && onShow.call(this, res) } })() e.onUnload = (() => { return function (res) { this.app.globalData = Object.assign(this.app.globalData || {}, { lastPage: this }) onUnload && onUnload.call(this, res) } })() return myPage.call(this, e) } [代码] 在需要监听的页面加个onBeforeBack方法,方法返回Promise化的wx.showModal [代码]onBeforeBack: function () { return wx.showModal({ title: '提示', content: '信息尚未保存,确定要返回吗?', confirmStay: !1 //结合content意思,点击确定按钮,是否留在原来页面,confirmStay默认false }) } [代码] 运行测试,Oj8K 是不是很简单,马上去试试水吧,效果图就不放了,静态图也看不出效果,动态图懒得弄,想看效果的自己运行代码片段吧 代码片段 https://developers.weixin.qq.com/s/hc2tyrmw79hg
2020-07-28 - 关于小程序文件下载并保存到本地的功能?
目前小程序要做保存excel、pdf等文件的功能, 我调用了wx.downFile,然后再调用saveFile,直接是走进了saveFile的success函数,但是保存的文件去哪里打开呢,还是说这个保存并不是保存到手机上? 还是说,现在的小程序并不支持保存文件到手机上(不是临时保存,是可以在退出小程序后再次在手机中找到并且打开) wx.downloadFile({ url: item.url, // filePath: wx.env.USER_DATA_PATH + '/' + item.fullName, success (res) { if (res.statusCode === 200) { wx.hideLoading() // let tempFilePath = res.filePath // 如果设置了filePath参数,则不会有tempFilePath let tempFilePath = res.tempFilePath wx.saveFile({ tempFilePath, success (res) { // 可以进行到这里 console.log(res); const savedFilePath = res.savedFilePath wx.showToast({ title: '下载成功', icon: 'none', mask: true }) }, fail (err) { console.log(err); wx.showToast({ title: '下载失败,请重新尝试', icon: 'none', mask: true }) } }) } } })
2020-04-01 - 【优化】解决swiper渲染很多图片时的卡顿
相信各位在开发的时候应该有遇到这样一个场景,比如商品的图片浏览,有时图片的浏览会很大,多的时候达几百张或上千张,这样就需要swiper里需要很多swiper-item,如此一来渲染的时候就会很消耗性能,渲染时会有一大段的空白时间,有时还会造成卡顿,体验非常差,下面给大家介绍一下我的解决方案。 首先是wxml结构: [图片] js: [图片] [图片] 主要是利用current属性,swiper里面只放3个swiper-item,要显示的图片放在第二,第一和第三放的是加载的动画背景,步骤如下: 1. 将请求到的数据存入一个数组picListAll内,这里不需要setData,只需要在data外面定义一个变量就行了,以减少渲染性能。 2. 把要显示的图片路径赋值给picUrl, 3. 切换的时候根据bindchange获取current属性,当current改变时判断当前图片在picListAll的index,根据index拿到图片再赋值给picUrl 主要实现步骤就是以上3 步,比较简单,要注意的是当切换到第一张和最后一张的时候要判断一下,把loding动画去掉,请求的时候还可以传入index参数以显示不同的图片,方便从前一页点击图片进入到此页面时能定位到该图片,例子里我是自己mock数据的,只是为了展示,如果你有服务器的话可以弄几百张看看效果,对比直接渲染和用以上方式渲染的差异。当然,这只是我的解决方案,如果各位有更好的方案欢迎一起讨论,一起进步. 系甘先,得闲饮茶 完整代码:https://github.com/HaveYuan/swiper
2019-01-25 - 【优化】解决swiper渲染很多图片时的卡顿
最近再用swiper,当有超过20个SwiperItem就会出现卡顿。 查看了各种资料 比如 https://developers.weixin.qq.com/community/develop/doc/000068ff25ccf0bae4e76eab156c04 这个方案不错,但是切换的时候会出现一个突然的跳跃,因为swiper的current会变。 经过我自己很多尝试,发现一个完美解决方案。 核心思想就是把没有显示出来的dom元素尽量简化 比如用空 <SwiperItem/> 代替 const ITEM_LIMIMT = 3; <Swiper current ={currentIndex} onChange={onChangeSwiper} displayMultipleItems={1} duration={300} > {items.map((item: any, index: number) => currentIndex < index + ITEM_LIMIMT && currentIndex > index - ITEM_LIMIMT ? ( <SwiperItem key={index}> <ComplexComponent item={item}/> </SwiperItem> ) : ( /* 没有显示出来的元素用空SwiperItem 代替*/ <SwiperItem key={index} /> ), )} </Swiper>
2020-05-28 - 【优化】解决swiper渲染很多图片时的卡顿
相信各位在开发的时候应该有遇到这样一个场景,比如商品的图片浏览,有时图片的浏览会很大,多的时候达几百张或上千张,这样就需要swiper里需要很多swiper-item,如此一来渲染的时候就会很消耗性能,渲染时会有一大段的空白时间,有时还会造成卡顿,体验非常差,下面给大家介绍一下我的解决方案。 首先是wxml结构: [图片] js: [图片] [图片] 主要是利用current属性,swiper里面只放3个swiper-item,要显示的图片放在第二,第一和第三放的是加载的动画背景,步骤如下: 将请求到的数据存入一个数组picListAll内,这里不需要setData,只需要在data外面定义一个变量就行了,以减少渲染性能; 把要显示的图片路径赋值给picUrl; 切换的时候根据bindchange获取current属性,当current改变时判断当前图片在picListAll的index,根据index拿到图片再赋值给picUrl; 主要实现步骤就是以上3 步,比较简单,要注意的是当切换到第一张和最后一张的时候要判断一下,把loding动画去掉,请求的时候还可以传入index参数以显示不同的图片,方便从前一页点击图片进入到此页面时能定位到该图片,例子里我是自己mock数据的,只是为了展示,如果你有服务器的话可以弄几百张看看效果,对比直接渲染和用以上方式渲染的差异。当然,这只是我的解决方案,如果各位有更好的方案欢迎一起讨论,一起进步。 完整代码:https://github.com/HaveYuan/swiper
2019-02-20 - 小程序版本更新,本地缓存会被清理
https://developers.weixin.qq.com/miniprogram/dev/framework/ability/storage.html 小程序加了首次打开的指引,是使用本地缓存记住用户是否已经过了首次打开。但是,本地缓存貌似每次版本迭代都会被清理掉,导致用户经常弹出首次打开的提示。这个怎么解决
2021-05-06 - wx.getUpdateManager() 上传代码选择 ‘版本升级“才生效吗?
[图片] 问题一:我 wx.getUpdateManager() 在真机上不生效,是因为上传代码没有选择 ”版本升级“吗? 问题二:想要测试 wx.getUpdateManager(),除了提交审核发版在真机上调试(发版调试太慢),真机模拟可以调试吗? [图片]
2021-03-31 - 微信小程序答题页——swiper渲染优化及swiper分页实现
前言 swiper的加载太多问题,网上资料好像没有一个特别明确的,就拿这个答题页,来讲讲我的解决方案 这里实现了如下功能和细节: 保证swiper-item的数量固定,加载大量数据时,大大优化渲染效率记录上次的位置,页面初次加载不一定非得是第一页,可以是任何页答题卡选择某一index回来以后的数据替换,并去掉swiper切换动画,提升交互体验示例动图 [图片] 截图 [图片] [图片] 问题原因 当swiper-item数量很多的时候,会出现性能问题 我实现了一个答题小程序,在一次性加载100个swipe-item的时候,低端手机页面渲染时间达到了2000多ms 也就是说在进入答题页的时候,会卡顿2秒多去加载这100个swiper-item 思考问题 那我们能不能让他先加载一部分,然后滑动以后再去改变item的数据,让swiper一直保持一定量的swiper-item? 注意到官方文档有这么两个属性可以利用,我们可以开启衔接滑动,然后再bindchange方法中去修改data [图片] 1、保证swiper-item的数量固定,加载大量数据时,优化渲染效率 假设我们请求到的数据的为list,实际渲染的数据为swiperList 我们现在给他就固定3个swiper-item,前后滑动的时候去替换数据 正向滑动的时候去替换滑动后的下一页数据,反向滑动的时候去替换滑动后的上一页数据 当我们知道了要替换的条件,我们便可以去替换数据了 但是我们应该考虑到临界值的问题,如果当前页是list第一项和最后一项该怎么办,向左向右滑是不是得禁止啊 这边是判断没数据会让它再弹回去 2、记录上次的位置,页面初次加载不一定非得是第一页,可以是任何页 有很多时候,我们是从某一项直接进来的,比如说上次答题答到了第五题,我这次进来要直接做第六题 那么我们需要去初始化这个swiperList,让它当前页、上一页、下一页都有数据 3、答题卡选择某一index回来以后的数据替换,并去掉swiper切换动画,提升交互体验 从答题卡选择index,那就不仅仅是滑动上下页了,它可以跳转到任何页,所以也采用类似初始化swiperList的方法 swiper切换动画我这边是默认250ms,但是发现有时候从答题卡点击回来,你在答题卡点击的下一项不知道会从左还是从右滑过来 体验真的很差,一开始不知道怎么禁掉动画,其实在跳转到答题卡页的时候把duration设为0就可以了 然后在答题卡页的unload方法中恢复 关键点: 在固定3个swiper-item的同时,要保证我们可以有办法来替代微信自带swiper的current属性和change方法 swiper-limited-load使用方法及说明: 将components中的swiper-limited-load复制到您的项目中在需要的页面引用此组件,并且创建自己的自定义组件item-view在初始化数据时,为你的list的每一项指定index属性具体可以参照项目目录start-swiper-limited-load中的用法说明:其它属性和swiper无异,你们可以自己单独添加你们需要的属性总结 一开始很头疼,为什么微信小程序提供的这个swiper,没去考虑这方面 然后在网上和社区找也没有一个特别好的解决方案。 后来想想,遇到需求就静下来解决吧。 项目地址:https://github.com/pengboboer/swiper-limited-load 如果错误,欢迎指出。 如有新的需求也可以提出来,如果有时间的话,我会帮你们完善。 如果能帮到你们,记得给一个star,谢谢。 ---补充 有很多朋友在评论区提到了分页的需求,抽时间写了一个分页的Demo和大家分享一下。 还是以答题为例,比如我们一共有500条数据,一页20条,可能需要如下功能,乍一看不就加了个分页,挺简单的,其实实现起来挺麻烦的,下面说一下思路和一些需要特别注意的点: 1、从其他页面跳转到答题页时,不光只能默认在第一题,可以是任意一题,比如第80题。 跳转到任意一题,那么需要我们根据index算出该数据在第几页,然后需要请求该页数据,最后显示对应的index。我的思路更注重用户体验,不可能是上滑或者下滑才开始去请求数据,一定是要用户滑动前提前请求好数据。所以起码要保证左右两侧在初始化那一刻都有数据。如果此题和它的上一题下一题都在同一页,那么我们只需要请求一页数据(第15题,那么只需请求第1页数据)。如果此题和它的上一题或者下一题不在同一页,那么我们可能需要请求两页数据。(第20题,那么需要请求第1页和第2页数据) 2、左滑、右滑没数据时,都可以加载新数据。直到滑到第一题或者最后一题。 如果我们初始化时是第24题,那么我们左滑到第21题时,就应该去请求第一页的数据。那么用户在看完21题时,再滑到20题,可能就根本不会感知到通过网络请求了数据。但是如果用户此刻滑动特别快:滑到21题时请求了网络,请求还没成功,就又向左滑了。那么我们需要限制用户的滑动,给用户一个提示:数据正在加载中。 3、从答题卡点击任意一题可以跳转到相应的题目,并且左右滑动显示正常数据 比如我们初始化是跳转到了第80题,不一会点击答题卡又要跳转到200题,一会又跳转到150题。各种无序操作,你也不知道用户要往哪里点。 一开始是想着维护一个主list,点到哪道题往list中添加这道题所在的当页的数据,但是还得判断这一页或者左滑右滑请求新一页的数据得往list的哪个位置添加。这来回来去乱七八糟的判断就很麻烦了,很容易出bug。而且list长度太长了以后insert的性能也不好。 后来就去想,要不答题卡点击任意一题都清空旧的list,然后请求新的数据,左右滑动没数据了再请求新的数据呗。但是这样很浪费资源,并且用户体验也不好,用户已经从第1题答到第200题了,这时用户从答题卡选择了一个25题,还得重新请求网络。而且200道题的数据都没了,那再选个26题,再重新请求网络?网络有延时不说,还浪费资源。 最后转念一想,这时候就需要弄一个缓存了。所以最终的解决方法就出来了:我们维护一个map,在网络请求成功后,在map中保存对应页的数据,同时我们维护一个主list来显示对应的题目。当我们在答题卡选择某一题目,就清空list,然后判断map中有没有该页的数据,如果有就直接拿来,没有就再去网络请求。这个处理方式,写法相对来说简单,不需要乱七八糟的判断,也不浪费资源,用户体验也很不错。 总结 以上就是一些思路和要注意的地方。这个Demo断断续续花了好几天时间写出来的。可能我说的比较啰嗦比较细,只是想让需要用到这个分页Demo的同学能理解我是如何实现的。 如果觉得能帮到你,记得给一个star,谢谢。同时如果这个demo有bug或者你们有新想法,欢迎提出来。
2021-01-07 - 多个input输入完成自动切换到下一个输入框
需求: 一个input输入完回车,切换到下一个input。 思路: JS里声明2个变量: focus是否获得焦点,focusIndex需要焦点的序号。wxml中给input设置id,设置focus属性由这两个变量来控制。在JS的输入完成监听事件里获取下一个input的id序号并修改变量。 js: [代码]Page({ /** * 页面的初始数据 */ data: { ... // 是否获取焦点 focus: false, // 需要获取焦点的序号 focusIndex: 0 }, // 输入完成事件 confirmListener (event) { let currentIndex = event.currentTarget.dataset.categoryIndex if (currentIndex < input的数量 - 1) { this.setData({ focus: true, focusIndex: currentIndex + 1 }) }else { this.setData({ focus: false }) } }, // 输入时事件 inputListener(event) { ... let currentIndex = event.currentTarget.dataset.categoryIndex if (this.focusIndex != currentIndex) { this.setData({ focusIndex: currentIndex }) } }, }) [代码] wxml: [代码]<input id="input{{index}}" data-category-index="{{index}}" bindinput="inputListener" bindconfirm="confirmListener" focus="{{focus && focusIndex == index}}" [代码]
2021-10-28 - 我在点击<input>时会触发<editor>的focus事件,是怎么回事呢?
<input>,<editor> 微信版本 7.0.14,调试版本2.11.0
2020-05-12 - 小程序选择本地文件上传
小程序选择本地文件上传,如何选择相应的文件进行上传?????不是图片!!!
2018-08-20 - scroll-view 使用自定义下拉刷新, 出现抖动闪烁?
<scroll-view refresher-triggered="{{loading}}" refresher-threshold="100" refresher-enabled="{{true}}" style="height: 100%;" scroll-y bindscrolltolower="onReachBottom" bindrefresherrefresh="PullDownRefresh"> <div v-if="schemeList.length === 0" class="scheme_null"> <div class="null"> <img lazy-load :src="nullIcon" alt="" class="nullIcon"/> <div>这里暂时还没有内容</div> <div>去其他地方看看吧</div> </div> </div> <div class="scheme"> <div class="scheme_left" > <div class="demo" v-for="(item,index) in schemeList" :key="index" v-if="index%2 === 0" @click="particulars(item)"> <img lazy-load :src="item.cover_img" mode="widthFix" alt="" class="cover_bulk" v-if="item.plan_type === 1"/> <img lazy-load :src="item.cover_img" mode="widthFix" alt="" class="cover_img" v-if="item.plan_type === 2"/> <img lazy-load :src="videoIcon" alt="" v-if="item.top_img_video_type === 2" class="video_icon"/> <div class="stylist"> <div class="left"> <img lazy-load :src.sync="item.designer.icon" alt="" class="stylist_icon"/> <div class="overFlow">{{item.designer.name}}</div> </div> <div class="right"> <img lazy-load :src.sync="item.is_project_like === 0 ? not_collect : collect" alt="" class="collect_icon" :class="{'collect':item.is_project_like === 1}"/> <div>{{item.project_like_number}}</div> </div> </div> <div class="project_name">{{item.project_name}}</div> <div class="address" v-if="item.plan_type === 1"> <div>{{item.room}}</div>室·<div>{{item.proportion}}</div>m²·<div class="overflow">{{item.province}}{{item.city}}</div> </div> </div> </div> <div class="scheme_right" > <div class="demo" v-for="(item,index) in schemeList" :key="index" v-if="index%2 === 1" @click="particulars(item)"> <img lazy-load :src="item.cover_img" mode="widthFix" alt="" class="cover_bulk" v-if="item.plan_type === 1"/> <img lazy-load :src="item.cover_img" mode="widthFix" alt="" class="cover_img" v-if="item.plan_type === 2"/> <img lazy-load :src="videoIcon" alt="" v-if="item.top_img_video_type === 2" class="video_icon"/> <div class="stylist"> <div class="left"> <img lazy-load :src.sync="item.designer.icon" alt="" class="stylist_icon"/> <div class="overFlow">{{item.designer.name}}</div> </div> <div class="right"> <img lazy-load :src.sync="item.is_project_like === 0 ? not_collect : collect" alt="" class="collect_icon" :class="{'collect':item.is_project_like === 1}"/> <div>{{item.project_like_number}}</div> </div> </div> <div class="project_name">{{item.project_name}}</div> <div class="address" v-if="item.plan_type === 1"> <div>{{item.room}}</div>室·<div>{{item.proportion}}</div>m²·<div class="overflow">{{item.province}}{{item.city}}</div> </div> </div> </div> </div> </scroll-view>
2020-07-13 - 2020-08-16
- Scroll-View 自定义下拉刷新
refresher-triggered属性赋值为false并不能取消动画 ====== 3.13自问自答系列====== 不知为何已经被标记为已回答了,说是怎么这么久没有人理了。 最后自己找到了答案,是因为不同的scroll-view用了同一个变量来控制refresher-triggered,用独立的即可。 细说,代码如下 [图片] 可以看到我渲染了多个 `tab` ,每个tab有一个 `scroll-view` ,但是我用了同一个 `flag`,也就是 `this.state.loding`,问题就来了,出问题了 使用一个数组分别做 `flag`,问题解决。
2020-06-13 - 关于多个input标签切换部分机型会键盘拉起失败的解决方案
前提说明:不是用的原生开发,用的uni-app来开发的,不过都差不多 主要是利用input标签的blur和focus事件来控制focus属性的值来实现的,效果在我自己测试中较为理想 --要吐槽下,这个问题非常久了,而且算是一个很严重的问题,微信官方有点让人失望 下面链接是实现代码 https://blog.csdn.net/u011135729/article/details/119169441
2021-07-28 - 多个input,切换无法聚焦,必现
复现步骤 1. 两个input框输入内容,内容随意 2.然后 随意点击一个input框使其唤起键盘 3. 点击空白的地方让input框失去焦点 4.点击另一个input框,发现不能唤起键盘,不能聚焦 机型: iphone xr 系统版本 14.0.1 微信版本:7.0.20 基础库版本: 2.14.1 代码片段:https://developers.weixin.qq.com/s/67oGK6m57Znb
2021-01-04 - input 获取焦点
是我写法有问题吗?切换焦点时方法多次执行。
2020-05-06 - 如何解决多个input设置cursor-spacing后,获取焦点时会被键盘遮挡的问题?
给一组input都设置相同的cursor-spacing后,填写完第一个input,直接点击第二个input时,第二个input会被键盘遮挡。 直接点第二个input,键盘正常弹出,填写完第二个input,点击第三个input时,第三个会被键盘遮挡。 以此类推,只有第一次点击的input是正常获得了cursor-spacing。 代码片段:https://developers.weixin.qq.com/s/GqJA35mx7qlE iphone xr、WeChat version 7.0.17、iOS 14.0 预览、真机调试都会出现这个问题。 使用的是原生键盘,没有用第三方键盘。救救孩子吧……
2020-10-15 - ios手机,textarea聚焦时,先弹起软键盘后页面才会被顶起?
微信官方提供的textarea代码片段,多复制几个textarea,然后点击最底下的一个textarea,就会复现。
2021-10-18 - for循环渲染的input,不聚焦问题,点击input不聚焦?
for循环渲染的input, 输入完内容输入下一个内容,点击之前的input 弹不出键盘。 ps: 假如是在第三个input失去的焦点,点击第一第二不会弹出,点击第三个会弹出。 代码就是普通的业务代码,用的vant-weapp,所以就不截图了。 [视频]
2020-12-25 - textarea与input同时存在,textarea聚焦事件触发后,input聚焦事触发不了?
textarea与input同时存在时,如果先触发了textarea的聚焦事件,再去触发input的聚焦事件,input的聚焦事件不会触发,触发的是textarea的聚焦事件。
2021-12-28 - 2021-09-13
- 190409版本以后的微信开发者工具,a标签的target是_blank的情况下,点击不起作用?
自从190409版本后的开发者工具一直点击不了链接,后来才发现,原来是因为我a标签加了target=“_blank”。既然微信无法新开窗口,那就直接在当前窗口打开链接不行吗?点击了不起作用是不是有点让人费解?
2020-06-23