个人案例
- 青蛙家电维修
一缕风沙渡(关于天气的小程序案例)
一缕风沙渡(关于天气的小程序案例)扫码体验
- 小程序免开发接入企业微信客服如何配置?
我看企业微信后台有小程序免开发接入微信客服的方式,小程序进入客服页面,就会回复一个客服名片,但是没看到客服名片的配置地方?[图片]
2023-05-23 - 手机号授权?
手机号授权一直提示这个 是什么问题,手机也是一样的,是不是需要什么授权 [图片]
2023-05-22 - scroll-view内,首列和首行固定的一个元素,ios下滚动会出现明显抖动
如题:编译后预览,在iphone-x的15.2版本,微信的8.0.37版本下,上述元素抖动很厉害,体验较差 为了方便调试,我把代码片段贴出来了 https://developers.weixin.qq.com/s/zv7pHpmI7eI2
2023-05-22 - 怎么能做到在小程序的任意页面都能转发?
设置小程序转发,只能是单页面模式吗?怎么能做到在任意页面都能转发?只能是到每个页面的js里添加onShareAppMessage() 吗?好像在app.js里也不能设置onShareAppMessage()
2023-05-19 - 我想做一个这样的小程序,有程序员人员可以开发做?
[图片] 我想做一个这样一摸一样的小程序,有程序员人员可以开发做?
2023-05-07 - 请问个如何注册视频号商店?
请问个如何注册视频号商店?
2023-05-06 - PC端微信聊天记录丢失
微信升级过后聊天记录丢失,无法看到联系人以及聊天记录,但是系统文件下面可以查看到相关聊天记录文件; 通过以下方式修改文件名后,聊天记录页面联系人列表恢复,但是无相关聊天记录;请问如何解决? [图片] 目前界面如下图,无法显示具体的聊天记录; [图片]
2023-05-05 - redirect_uri 参数错误
https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html redirect_uri 参数错误和配置的一样也不行
2023-04-04 - 一个小程序可以有多个收款账户吗
在小程序里根据不同的商品将支付的金额支付到不同的收款账户里
2023-03-24 - 微信没有人工客服吗?为什么没人解决实际问题?
小程序APPID: wx6d36909d58c7e938 手机号: 15623964010 提示小程序不支持chatGPT服务并下架了我的小程序。但是目前同类型小程序有几百上千个都在正常运行,请问我的小程序具体是哪一点违规了,我这边整改 [图片][图片]
2023-03-24 - 小程序使用threejs,没有getElementById怎么解决?
小程序要引入threejs,没有getElementById,用uni.createSelectorQuery().select('#container')的时候,报错container.appendChild is not a function。也就是没有appendChild功能,有什么办法解决?用web-view加H5的话,下拉回卡顿,回调也麻烦,有没有好的解决办法。
2023-03-15 - 活禽要怎么卖,需要什么资质?
活禽要怎么卖,需要什么资质
2023-03-14 - 小程序怎么实时获取麦克风声音频率?
做个古琴调音器,需要实时获取声音频率,请教一下有现成的API没呀,有什么办法可以做到呢?类似的小程序有个子博调音器 [图片]
2023-03-13 - 智能图像识别AI小程序 开源
sea-ai 另求star ~ 模板使用云开发实现,接入百度AI平台API图像识别系统,无需另外搭建服务器,只需修改文件内配置项 项目介绍 一款方便快捷识别AI,可根据您拍摄或相册中照片识别出您所需要知道的物种(植物,动物,图文,菜品类型),相关知识,帮助您了解该物种,打开新世界! [图片] 学习本项目 整套前端使用 Wepy 开发,提倡前端组件化工程化,高效的完成前端项目。 使用说明 申请百度AI 获取Appid,secret 找到文件内的appid,secret , 修改为你微信小程序的appid以及secret 安装使用 安装依赖 [代码]cd sea-ai npm install [代码] 开发实时编译 [代码]npm run [代码] 开发者工具导入项目 使用[代码]微信开发者工具[代码]新建项目,本地开发选择项目根目录,会自动导入项目配置。 上传安装云函数 开发者工具中找到云函数目录上传并部署:云端安装依赖(不上传node_modeles) setBaiduToken 需上传触发器(定时器) 每十五天更新一次token
2019-04-19 - 车载特斯拉微信小程序如何开发?
特斯拉正式上线了微信小程序功能,并新增了新浪微博、微信听书等小程序。 目前希望开发的小程序可以发布到特斯拉微信小程序中。 请问是否开放了可以对接特斯拉微信小程序的入口?
2023-01-31 - webView页面可以通过weixin://dl/business/?t 协议跳转到另外的小程序?
webView页面,是否可以 通过获取 URL Scheme 链接跳转 小程序小程序页面 https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/url-scheme.html
2023-01-29 - wx.uploadFile上传文件到云函数,如何解释body?
https://docs.cloudbase.net/service/access-cloud-function 参考文档如上。 1、小程序端,用wx.uploadFile上传文件,访问云函数的http接入服务; 2、云函数端event中获取到event.body的文件内容,看上去象是base64的字符串; 3、现在想获取并解析该文件内容,并保存文件到云存储。 有谁做过这样的事吗?event.body的格式转换试了很久没成功,文件保存到云存储后,都是坏文件。
2023-01-03 - 这个小程序卡片进度的功能很新,有大佬知道是什么能力吗?
[图片]
2023-01-03 - JavaScript小技巧【建议收藏】
在JavaScript世界里,有些操作会让你无法理解,但是却无比优雅! 有时候读取变量属性时,他可能不是Ojbect。这个这个你就要判断这个变量是否为对象,如果是在如引用 [代码]var obj; if(obj instanceof Object){ console.log(obj.a); }else{ console.log('对象不存在'); } [代码] ES6中有简便写法,可以帮我们简短代码,从而更加明确 [代码]var obj; console.log(obj?.a || '对象不存在'); [代码] 1,2,3,4,5,6…10,11,12 小于10的前面补0 其实我们用slice函数可以巧妙解决这个问题 slice(start,end); start:必需。规定从何处开始选取。如果是负数,那么它规定从数组尾部开始算起的位置。也就是说,-1 指最后一个元素,-2 指倒数第二个元素,以此类推。 end :可选。规定从何处结束选取。该参数是数组片断结束处的数组下标。如果没有指定该参数,那么切分的数组包含从 start 到数组结束的所有元素。如果这个参数是负数,那么它规定的是从数组尾部开始算起的元素。 [代码]var list=[1,2,3,4,5,6,7,8,9,10,11,12,13]; list=list.map(ele=>('0' + ele).slice(-2)); console.log(list); [代码] [图片] 打印可视化console.table() [代码]var obj = { name: 'Jack' }; console.table(obj); obj.name= 'Rose'; console.table(obj); [代码] [图片] 获取数组中的最后的元素 数组方法slice()可以接受负整数,如果提供它,它将从数组的末尾开始截取数值,而不是开头。 [代码]let array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; console.log(array.slice(-1)); // Result: [9] console.log(array.slice(-2)); // Result: [8, 9] console.log(array.slice(-3)); // Result: [7, 8, 9] [代码] es6模板字符串 [代码]let name = "Charlse" let place = "India"; let isPrime = bit =>{ return (bit === 'P'? 'Prime' : 'Nom-Prime') } let messageConcat = `Mr.name' is form ${place} .He is a ${isPrime('P')} member`; [代码] H5语音合成 在网页端实现将指定的文字进行语音合成并进行播报。 使用HTML5的Speech Synthesis API就能实现简单的语音合成效果。 [代码]<input id="btn1" type="button" value="点我" onclick="test();" /> <script> function test() { const sos = `阿尤!不错哦`; const synth = window.speechSynthesis; let msg = new SpeechSynthesisUtterance(sos); synth.speak(msg) } </script> [代码] 然后点击按钮,就会触发test方法的执行实现语音合成 这里推荐使用Chrome浏览器,因为HTML5的支持度不同 数字取整 [代码]let floatNum = 100.5; let intNum = ~~floatNum; console.log(intNum); // 100 [代码] [图片] 字符串转数字 [代码]let str="10000"; let num = +str; console.log(num); // 10000 [代码] 交换对象键值 [代码]let obj = { key1: "value1", key2: "value2" }; let revert = {}; Object.entries(obj).forEach(([key, value]) => revert[value] = key); console.log(revert); [代码] [图片] 数组去重 [代码]let arrNum = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 ]; let arrString = [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" ]; let arrMixed = [ 1, "1", "2", true, false, false, 1, 2, "2" ]; arrNum = Array.from(new Set(arrNum)); arrString = [...new Set(arrString)]; arrMixed = [...new Set(arrMixed)]; console.log(arrNum); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] console.log(arrString); // ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] console.log(arrMixed); // [1, "1", "2", true, false, 2] [代码] 数组转化为对象 [代码]const arr = [1,2,3] const obj = {...arr} console.log(obj) [代码] 合并对象 [代码]const obj1 = { a: 1 } const obj2 = { b: 2 } const combinObj = { ...obj1, ...obj2 } console.log(combinObj) [代码] 也就是通过展开操作符(spread operator)来实现。 获取数组中的最后一项 [代码]let arr = [0, 1, 2, 3, 4, 5]; const last = arr.slice(-1)[0]; console.log(last); [代码] 一次性函数,适用于初始化的一些操作 [代码]var sca = function() { console.log('msg')//永远只会执行一次 sca = function() { console.log('foo') } } sca() // msg sca() // foo sca() [代码] 提高工作效率,减少代码量,降低键盘磨损程度 我希望你喜欢它并学到了一些新东西 感谢你的阅读,编程快乐!
2022-10-25 - H5网页跳转小程序
现在小程序越来越普遍了,从H5网页(要在微信浏览器下打开的)跳转到相应小程序的场景也越来越多。至此微信提供了相应的微信开放标签让网页开发者可安全便捷地使用微信或系统的能力,为微信用户提供更优质的网页体验。 需要注意的是,微信开放标签有最低的微信版本和最低的系统版本要求。 微信版本要求为:7.0.12及以上系统版本要求为:iOS 10.3及以上、Android 5.0及以上 对于符合微信或系统最低版本要求但仍无法使用微信开放标签的场景,将会在下方使用步骤中的[代码]wx.config[代码]权限验证成功后触发[代码]WeixinOpenTagsError[代码]事件告知开发者。仅无法使用微信开发标签,JS-SDK其他功能不受影响。可通过如下方法监听并进行回退兼容: document.addEventListener('WeixinOpenTagsError', function (e) { console.error(e.detail.errMsg); // 无法使用开放标签的错误原因,需回退兼容。仅无法使用开放标签,JS-SDK其他功能不受影响 }); 根据目前已知的错误场景,回退兼容建议如下: iOS15底层 WebKit 接口发生变更,微信版本8.0.8以下(不包括8.0.8)无法使用开放标签,可引导用户升级最新版本微信;开放标签依赖Web Components方案,极少部分 Android 系统可能由于版本太低而不支持,可引导用户升级系统固件。H5网页跳转小程序有如下步骤: 1.在微信公众号(已认证的服务号)绑定“JS接口安全域名” 如果是公众号身份的网页,需要绑定安全域名。登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。 如果是使用小程序云开发静态网站托管的小程序网页,则不需绑定安全域名即可直接使用(即跳过该"步骤一:绑定JS接口安全域名")。 2.引入JS文件 在需要调用 JS 接口的页面引入如下 JS 文件:http://res.wx.qq.com/open/js/jweixin-1.6.0.js (支持https) 如需进一步提升服务稳定性,当上述资源不可访问时,可改访问:http://res2.wx.qq.com/open/js/jweixin-1.6.0.js (支持https) 备注:支持使用 AMD/CMD 标准模块加载方法加载。 注意:js文件必须使用1.6.0版本以上 3.通过config接口注入权限验证配置并申请所需开放标签 与使用 JS-SDK 配置方式相同,所有需要使用开放标签的页面必须先注入配置信息,并通过[代码]openTagList[代码]字段申请所需要的开放标签,否则将无法使用(同一个 url 仅需调用一次,对于变化url的SPA的web app可在每次url变化时进行调用。目前Android微信客户端不支持pushState的H5新特性,所以使用pushState来实现web app的页面会导致签名失败,此问题会在Android6.2中修复)。开放标签的申请和 JS 接口的申请相互独立,因此是可以同时申请的。 wx.config({ debug: true, // 开启调试模式,调用的所有 api 的返回值会在客户端 alert 出来,若要查看传入的参数,可以在 pc 端打开,参数信息会通过 log 打出,仅在 pc 端时才会打印 appId: '', // 必填,公众号的唯一标识 timestamp: , // 必填,生成签名的时间戳 nonceStr: '', // 必填,生成签名的随机串 signature: '',// 必填,签名 jsApiList: [], // 必填,需要使用的 JS 接口列表 openTagList: [] // 可选,需要使用的开放标签列表,例如['wx-open-launch-app'] }); 注意:如果使用的是小程序云开发静态网站托管的域名的网页,可以免鉴权直接跳任意合法合规小程序,调用 wx.config 时 appId 需填入非个人主体的已认证小程序,不需计算签名,timestamp、nonceStr、signature 填入非空任意值即可。 4.通过ready接口处理成功验证 wx.ready(function () { // config信息验证后会执行 ready 方法,所有接口调用都必须在 config 接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在 ready 函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在 ready 函数中 }); 5.通过error接口处理失败验证 wx.error(function (res) { // config信息验证失败会执行 error 函数,如签名过期导致验证失败,具体错误信息可以打开 config 的debug模式查看,也可以在返回的 res 参数中查看,对于 SPA 可以在这里更新签名 }); 接口调用说明所有接口通过wx对象(也可使用jWeixin对象)来调用,参数是一个对象,除了每个接口本身需要传的参数之外,还有以下通用参数: success:接口调用成功时执行的回调函数。fail:接口调用失败时执行的回调函数。complete:接口调用完成时执行的回调函数,无论成功或失败都会执行。cancel:用户点击取消时的回调函数,仅部分有用户取消操作的api才会用到。trigger: 监听Menu中的按钮点击时触发的方法,该方法仅支持Menu中的相关接口。备注:不要尝试在trigger中使用ajax异步请求修改本次分享的内容,因为客户端分享操作是一个同步操作,这时候使用ajax的回包会还没有返回。 以上几个函数都带有一个参数,类型为对象,其中除了每个接口本身返回的数据之外,还有一个通用属性errMsg,其值格式如下: 调用成功时:"xxx:ok" ,其中xxx为调用的接口名 用户取消时:"xxx:cancel",其中xxx为调用的接口名 调用失败时:其值为具体错误信息 使用说明 所使用的标签允许提供插槽,由于插槽中模版的样式是和页面隔离的,因此需要注意在插槽中定义模版的样式。插槽模版及样式均需要通过[代码]<script type="text/wxtag-template"></script>或<template></template>[代码]进行包裹。另外,对于具名插槽还需要通过[代码]slot[代码]属性声明插槽名称,下文标签插槽中的 default 插槽为默认插槽,可不声明插槽名称。 对于标签事件,均可通过[代码]event.detail[代码]获得详细信息。如果无特殊说明,下文标签事件说明中的返回值均指代[代码]event.detail[代码]中的内容。 另外,需要注意以下几点: 页面中与布局和定位相关的样式,如[代码]position: fixed; top -100;[代码]等,尽量不要写在插槽模版的节点中,请声明在标签或其父节点上;对于有 CSP 要求的页面,需要添加白名单[代码]frame-src https://*.qq.com webcompt:[代码],才能在页面中正常使用开放标签。(CSP相关内容可查看以下几篇文章:https://zhuanlan.zhihu.com/p/142987601、https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy)开放对象已认证的服务号,服务号绑定“JS接口安全域名”下的网页可使用此标签跳转任意合法合规的小程序。已认证的非个人主体的小程序,使用小程序云开发的静态网站托管绑定的域名下的网页,可以使用此标签跳转任意合法合规的小程序。代码 参考:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/staticstorage/jump-miniprogram.html https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_Open_Tag.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>H5跳转小程序</title> <!-- weui 样式 --> <link rel="stylesheet" href="https://res.wx.qq.com/open/libs/weui/2.4.1/weui.min.css"> <!-- 页面样式 start --> <style> /* --------START reset.css------- */ * { margin: 0; padding: 0; } html, body { background-color: #fff; } a { text-decoration: none; } a, button, input, span, div { -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } li { list-style-type: none; } /* --------END reset.css------- */ .hidden { display: none; } .full { position: absolute; top: 0; bottom: 0; left: 0; right: 0; } .public-web-container, .wechat-web-container, .wechat-web-container wx-open-launch-weapp, .desktop-web-container { display: flex; flex-direction: column; align-items: center; } .public-web-container p, .wechat-web-container p, .desktop-web-container p { position: absolute; top: 40%; } .public-web-container a { position: absolute; bottom: 40%; } .wechat-web-container wx-open-launch-weapp { position: absolute; bottom: 40%; left: 0; right: 0; } .wechat-web-container .open-btn { display: block; margin: 0 auto; padding: 8px 24px; width: 200px; height: 45px; border: none; border-radius: 4px; background-color: #07c160; color: #fff; font-size: 18px; text-align: center; } </style> <!-- 页面样式 end --> </head> <body> <!-- 页面容器 start --> <div id="h5OpenMiniprogram"> <!-- <template> --> <!-- 页面内容 start --> <div class="page full"> <!-- 移动端微信外部浏览器 --> <div id="public-web-container" class="hidden"> <p>正在打开“小程序名字”</p> <a href="javascript:" id="public-web-jump-button" class="weui-btn weui-btn_primary weui-btn_loading" onclick="openWeapp()"> <span id="public-web-jump-button-loading" class="weui-primary-loading weui-primary-loading_transparent"> <i class="weui-primary-loading__dot"></i> </span> 打开小程序 </a> </div> <!-- 微信内部浏览器 --> <div id="wechat-web-container" class="hidden"> <p>点击以下按钮打开“小程序名字”</p> <!-- username:必填,所需跳转的小程序原始id,即小程序对应的以gh_开头的id; path:非必填,所需跳转的小程序内页面路径及参数(默认小程序的初始页面【即首页】) --> <wx-open-launch-weapp id="launch-btn" username="gh_XXX" path="/pages/XXX"> <!-- 第一种: 不适用于Vue.js开发的项目,template标签会冲突 --> <template> <style> .open-btn { display: block; margin: 0 auto; padding: 8px 24px; width: 200px; height: 45px; border: none; border-radius: 4px; background-color: #07c160; color: #fff; font-size: 18px; text-align: center; } </style> <button class="open-btn">打开小程序</button> </template> <!-- 第二种:几乎适用于所有前端框架开发的项目 --> <!-- <script type="text/wxtag-template"> <style> .open-btn { display: block; margin: 0 auto; padding: 8px 24px; width: 200px; height: 45px; border: none; border-radius: 4px; background-color: #07c160; color: #fff; font-size: 18px; text-align: center; } </style> <button class="open-btn">打开小程序</button> </script> --> </wx-open-launch-weapp> </div> <!-- 桌面端 --> <div id="desktop-web-container" class="hidden"> <p>请在手机打开网页链接</p> </div> </div> <!-- 页面内容 end --> <!-- </template> --> </div> <!-- 页面容器 end --> <!-- 引入jQuery --> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script> <!-- 调试用的移动端 console --> <script src="https://cdn.jsdelivr.net/npm/eruda"></script> <script> eruda.init(); </script> <!-- 公众号 JSSDK --> <script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script> <!-- 云开发 Web SDK --> <script src="https://res.wx.qq.com/open/js/cloudbase/1.1.0/cloud.js"></script> <script> function docReady(fn) { document.addEventListener('WeixinOpenTagsError', function (e) { console.error(e.detail.errMsg); // 无法使用开放标签的错误原因,需回退兼容。仅无法使用开放标签,JS-SDK其他功能不受影响 }); if (document.readyState === "complete" || document.readyState === "interactive") { fn(); } else { document.addEventListener("DOMContentLoaded", fn); } } docReady(async function () { var ua = navigator.userAgent.toLowerCase(); var isWXWork = ua.match(/wxwork/i) == "wxwork"; var isWeixin = !isWXWork && ua.match(/micromessenger/i) == "micromessenger"; console.log("isWeixin", isWeixin, isWXWork); var isMobile = false; var isDesktop = false; if (navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|IEMobile)/i)) { isMobile = true; } else { isDesktop = true; } if (isWeixin) { var containerEl = document.getElementById("wechat-web-container"); containerEl.classList.remove("hidden"); containerEl.classList.add("full", "wechat-web-container"); // 公众号网页需要绑定安全域名 // 获取签名,timestamp、nonceStr、signature $.ajax({ url: "请求地址", dataType: "json", success: function (res) { console.log("WeChatConfig", res); if (res.id === 1) { var data = res.items; // 根据实际情况返还的数据进行赋值 wx.config({ // debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印 appId: data.appId, // 必填,公众号的唯一标识 timestamp: data.timestamp, // 必填,生成签名的时间戳 nonceStr: data.nonceStr, // 必填,生成签名的随机串 signature: data.signature, // 必填,签名 jsApiList: ["chooseImage"], // 必填,需要使用的JS接口列表(此处随意一个接口即可) openTagList: ["wx-open-launch-weapp"], // 可选,需要使用的开放标签列表,例如['wx-open-launch-app'] }); /** * config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后。 * config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。 * 对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中 * */ wx.ready(function (res2) { console.log("ready", res2); var launchBtn = document.getElementById("launch-btn"); launchBtn.addEventListener("ready", function (e) { console.log("开放标签 ready"); }); launchBtn.addEventListener("launch", function (e) { console.log("开放标签 success"); }); launchBtn.addEventListener("error", function (e) { console.log("开放标签 fail", e.detail); }); }); // config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名 wx.error(function (err) { console.log("error", err); }); } } }) // 小程序云开发静态网站托管的网页 // var launchBtn = document.getElementById("launch-btn"); // launchBtn.addEventListener("ready", function (e) { // console.log("开放标签 ready"); // }); // launchBtn.addEventListener("launch", function (e) { // console.log("开放标签 success"); // }); // launchBtn.addEventListener("error", function (e) { // console.log("开放标签 fail", e.detail); // }); // wx.config({ // // debug: true, // 调试时可开启 // appId: "", // 非个人主体的已认证的小程序APPID // timestamp: 0, // 必填,填任意数字即可 // nonceStr: "nonceStr", // 必填,填任意非空字符串即可 // signature: "signature", // 必填,填任意非空字符串即可 // jsApiList: ["chooseImage"], // 必填,随意一个接口即可 // openTagList: ["wx-open-launch-weapp"], // 填入打开小程序的开放标签名 // }); } else if (isDesktop) { // 在 pc 上则给提示引导到手机端打开 var containerEl = document.getElementById("desktop-web-container"); containerEl.classList.remove("hidden"); containerEl.classList.add("full", "desktop-web-container"); } else { var containerEl = document.getElementById("public-web-container"); containerEl.classList.remove("hidden"); containerEl.classList.add("full", "public-web-container"); // 云函数 // 因未开通云开发环境,此处不做处理 // var c = new cloud.Cloud({ // identityless: true, // 必填,表示是未登录模式 // resourceAppid: "小程序 AppID", // 资源方 AppID // resourceEnv: '云开发环境 ID', // 资源方环境 ID // }); // await c.init(); // window.c = c; // var buttonEl = document.getElementById("public-web-jump-button"); // var buttonLoadingEl = document.getElementById("public-web-jump-button-loading"); // try { // await openWeapp(() => { // buttonEl.classList.remove("weui-btn_loadin"); // buttonLoadingEl.classList.add("hidden"); // }) // } catch (error) { // buttonEl.classList.remove("weui-btn_loadin"); // buttonLoadingEl.classList.add("hidden"); // throw error; // } } }); async function openWeapp(onBeforeJump) { console.log("未开通云开发环境", onBeforeJump); // 因未开通云开发环境,此处不做处理 // var c = window.c; // const res = await c.callFunction({ // name: "public", // data: { // action: "getUrlScheme", // }, // }); // console.warn(res); // if (onBeforeJump) { // onBeforeJump(); // } // location.href = res.result.openlink; } </script> </body> </html> 错误提示 (1)没有在“JS接口安全域名”设置 [图片] 成功提示 (1)微信开发者工具 [图片] (2)真机:会有要打开小程序的名字 [图片]
03-06 - css 多行文字两端对齐
[图片] 需要的地方补充这个样式,文字就可以实现两端对齐了. .text-justify{ text-align: justify; text-justify:distribute-all-lines; /*这行必加,兼容ie浏览器*/ text-align-last: left!important; } [图片]
2022-12-20 - 微信小程序如何处理发版本之前的本地缓存
微信小程序如何处理发版本之前的本地缓存,实际开发中,回遇到比如用户搜索的关键字存本地的操作,但是如果接口调整了,返回的字段发生了变更.那么需要在发布新版本后,处理一下用户之前的本地缓存. 解决方案一: 在版本更新的时候,处理清除缓存操作. (实操失败,也算一种思路,可以试试) onLaunch: function() { // #ifdef MP this.mpUpdate() // #endif } methods: { mpUpdate() { const updateManager = uni.getUpdateManager() // 小程序版本更新管理器 updateManager.onCheckForUpdate(res => { // 检测新版本后的回调 if(res.hasUpdate) { // 如果有新版本提醒并进行强制升级 uni.showModal({ content: '更新到最新版本', showCancel: false, confirmText: '确定', success: res => { if (res.confirm) { updateManager.onUpdateReady(res => { // 新版本下载完成的回调 uni.removeStorageSync('search-history')// 处理清除缓存操作; updateManager.applyUpdate() // 强制当前小程序应用上新版本并重启 }) updateManager.onUpdateFailed(res => { // 新版本下载失败的回调 // 新版本下载失败,提示用户删除后通过冷启动重新打开 uni.showModal({ content: '下载失败,请删除当前小程序后重新打开', showCancel: false, confirmText: '知道了' }) }) } } }) } }) } } 解决方案二: 本地存储版本号, 然后更新后得到最新的版本号,两个版本号不等,然后处理逻辑 文档地址: https://developers.weixin.qq.com/miniprogram/dev/api/open-api/account-info/wx.getAccountInfoSync.html [图片] onShow(){ let versionNum = uni.getStorageSync('version') || ''; if(!versionNum){ uni.removeStorageSync('search-history') } let accountInfo = wx.getAccountInfoSync(); let version = accountInfo.miniProgram.version; uni.setStorageSync("version", version); this.initHistory(); // 初始化搜索历史记录 }, 小程序内的缓存,首页调用wx.clearStorage 可以清掉。 旧的版本可以用UpdateManager对象来管理更新 https://developers.weixin.qq.com/minigame/dev/api/base/update/UpdateManager.html [图片] [图片]
2022-12-05 - 社区每周 | 用户头像昵称获取规则的兼容性调整通知及上周问题反馈(10.31-11.04)
各位微信开发者: 以下是用户头像昵称获取规则的兼容性调整通知及上周我们在社区收到的问题反馈的处理进度,希望与大家一同打造更好的小程序生态! 用户头像昵称获取规则的兼容性调整通知 由于 头像昵称填写能力 在 PC / macOS 平台存在兼容性问题,因此对于来自低于 2.27.1 基础库版本的访问,小程序通过 wx.getUserProfile 接口将正常返回用户头像昵称,插件通过 wx.getUserInfo 接口将正常返回用户头像昵称。 更多关于本次调整的 Q&A,可查看原公告《小程序用户头像昵称获取规则调整公告》。 上周问题反馈和处理进度(10.31-11.04) 已修复的问题安卓用户无法应用 Canvas 能力的问题 查看详情 修复中的问题video 组件在其他小程序返回后不能播放视频的问题 查看详情 已达到小程序流量主的要求但无法开通的问题 查看详情 微信团队 2022年11月11日
2022-11-11 - 我这里想要测试短信唤醒小程序,小程序必须先提交上线吗?
我这里在生成URL Scheme的时候,提醒我,如下错误,开发者文档提示是小程序未发布,那如果我要测试发短信唤醒小程序的功能,应该怎么测试? [图片]
2022-11-08 - 禁止遮罩层下的页面滚动 终极解决办法
[图片] 方案一: methods: { moveHandle() {} 方案二: (推荐) 如果要显示遮罩层,那么用css控制底部的不允许滚动。 [图片] data() { return { mask: false, } } // 如果要显示遮罩层,那么用css控制底部的不允许滚动。 .tl-show{ overflow: hidden; position:fixed; height: 100%; width: 100%; }
2022-11-04 - chooseAvatar 还是不能从相册上传大于1M的图片?
小程序使用chooseAvatar设置头像昵称,目前测试下来使用相册里大于1M的图片还是有报错。之前看官方有回复说图片压缩和裁剪功能在开发中,但是公告上要求11.8前完成切换,现在组件还没更新吗? [图片]
2022-11-03 - 怎么把云服务器上图片变成http图片呢?
cloud://pqtz2-9gzchr9694281166.7071-pqtz2-9gzchr9694281166-1309635073/sxfriend_list_photo/oskUN5hwrBJ7EqZSIwUEI6EkWNlA881664419082570.jpg
2022-10-26 - 获取当前系统时间戳
获取当前的时间:然后处理一系列逻辑 getHours(){ let time = (new Date()).getTime(); // 获取当前时间戳 console.log('time=='+time); let timeDate = this.orderInfo.pay_time; //let Time = new Date(timeDate); let Time = new Date(timeDate.replace(/-/g, '/')) let timestemp = Time.getTime(); //得到时间戳 console.log('timestemp====',timestemp) let timeDiff = time - timestemp console.log('timeDiff==',time - timestemp); const hours = parseInt(timeDiff/3600000 ) // 当前时间和服务器时间的差值,计算出小时数 console.log('hours==',hours); this.hours = hours },
2022-07-20 - 小程序返回上一页终极处理办法
注意:注意针对自定义顶部导航,或者是使用了uni.navigateBack这个方法的界面。 //如果当前界面存在上一个界面,就返回上一个界面 //如果当前界面是分享进入的,没有上一个界面,这样的情况直接返回到指定的界面上去。 back() { /*uni.navigateBack({ delta: 1 })*/ //@zxyuns 处理兼容,如果没有上一级界面则返回首页 const pages = getCurrentPages(); if (pages.length === 2) { uni.navigateBack({ delta: 1 }); } else if (pages.length === 1) { //判定没有上一页得情况下,才进行跳转到指定界面 uni.switchTab({ url: '/pages/home/home', }) } else { //这里应该是不会用到得 uni.navigateBack({ delta: 1 }); } },
2022-08-05 - 全网最全小程序自定义tabbar实现方案
业务需求,底部导航栏(tabbar)支持自定义,目前有个局限性,必须要在app.json里面声明list数组,而且数量是五个,导致扩展受限制。目前还没有解决办法,基本可以满足五个和小于五个自定义的已存在的tabbar的动态配置。 感谢网友的热心分享,以下链接地址都非常有参考价值:不过在当前时间点2020年6月5号,和对应的小程序版本号来说或多或少会遇到一些问题。 2022-08-30 补充: 首先很抱歉,就是图片看不到了.确实影响阅读体验. 然后这个方案是不成熟的, 大家可以巧用: wx.setTabBarItem 这个api,来做一些文章. 其实可以用wx.setTabBarItem https://developers.weixin.qq.com/miniprogram/dev/api/ui/navigation-bar/wx.setNavigationBarTitle.html 参考地址:见文章结尾 自定义 tabBar 官方api地址:https://developers.weixin.qq.com/miniprogram/dev/framework/ability/custom-tabbar.html?search-key=%E8%87%AA%E5%AE%9A%E4%B9%89tabbar 效果图先上: [图片] 小程序自定义tabbar实现.gif 下面开始进入正文: 第一步、先下载官方的demo,然后进行合并。说明:这里存放的目录地址要是项目下和pages同目录,不然无法识别自定义的tabbar组件[图片] image.png 说明2,这个用的是全局设置的,需要加一下,不然不能开启自定义组件模式 "usingComponents": {}, "window": { 第二步、在 app.json 中的 tabBar 项指定 custom 字段,设置为true,同时其余 tabBar 相关配置也补充完整。说明:如果遇到报错,说什么组件不存在,可以把 custom 字段去掉,本项目没有去掉能正常使用。[图片] image.png 第三步、修改custom-tab-bar/index.js的文件说明,由于这里我们是后端动态返回的,这里我不展开说具体的业务,这里需要注意的是list中的pagePath一定要写绝对路径/pages开头第四步、把官方给的使用方法放到tabbar跳转页的onShow方法里,selected根据list下标位置进行设置selected: 这里的参数是对应的底部tabbar的顺序的,含义是进入当前界面,并选中这个当前的tabbar if (typeof this.getTabBar === 'function' && this.getTabBar()) { this.getTabBar().setData({ selected: 4 }) } 第五步,关于点击后会闪一下这个问题的说明,目前官方也没有解决的方案,所以,除非必要,建议还是用自带的tabbar写业务。[图片] image.png 最后补充一个兼容跳到tabbar的语法糖: wx.navigateTo({ url: '/pages/cart_new/index/index', success: function(res) {}, fail: function(res) { wx.switchTab({ url: '/pages/cart_new/index/index', //注意switchTab只能跳转到带有tab的页面,不能跳转到不带tab的页面 }) }, complete: function(res) {}, }) 参考地址: 微信小程序开发---自定义tabBar:https://segmentfault.com/a/1190000016283268 微信小程序自定义tabbar:https://www.jianshu.com/p/8b918e21cc6b 小程序自定义tabbar报Component is not found in path "custom-tab-bar/index":https://blog.csdn.net/qq_34672907/article/details/93624433 微信小程序自定义导航栏天大的坑,报错提示:component is not found in path "custom-tab-bar/index"...https://blog.csdn.net/dqzd12345/article/details/102756681 鉴于文中图片丢失:可以移步到我的博客看文中图片 https://www.jianshu.com/p/c48281e61907?u_atoken=b039edc7-97e0-4fa9-9d2d-4aca9a1faa87&u_asession=01VEYqQobFRt91aDKSjf6RMv2PFMk-NGAnJp1a80FUHt5puOXqVMQ49qq2C1XR8xKKX0KNBwm7Lovlpxjd_P_q4JsKWYrT3W_NKPr8w6oU7K_wjOK6EbT2ki0dkwQctnAQnHmbkqVcEgdObpAroqY1_GBkFo3NEHBv0PZUm6pbxQU&u_asig=05ESJ0rAXmqHXPIepLzgwTZn_I1UfOjo4mnyB5w-1YuP98oKPx90Z-OI_FpFckab0IKcUl39blHNAEIyPVHalH7evI9N43IRLtrs4TIbEtbub6fJW-7KGDLiz20hzMsz99AoqUieIp1TcUCwzplBT-dwzjA8FeyDmEzHEoYBRMWeH9JS7q8ZD7Xtz2Ly-b0kmuyAKRFSVJkkdwVUnyHAIJzYGzTYSVKNX8WdtkFZgDm7lSpGaDCR7AutzhN5tKbTbm6xbSxAaWh9ph0bRUFW-6vO3h9VXwMyh6PgyDIVSG1W-u2m4aSV3j7RjNp-oJ3rx3fX5y-uYeLDQishV-vt1GW8_QLnW4q9Rmwoz8SvMcA9UV9IzhK00je8y5MzvIwdeImWspDxyAEEo4kbsryBKb9Q&u_aref=uxwZVbN3vBalaKU8R2iVPkmS7C4%3D
2022-08-30 - 强烈建议可以允许项目人员自己退出某一个小程序项目?!
加了一些项目,已经满50上限了,现在找管理员找不到了,没法退出旧项目、加入新项目。强烈建议可以允许项目人员自己退出某一个小程序项目!
2022-10-14 - wx.getlocation到底怎么才能申请通过?
申请了四次了,也不说明原因,就说不通过。 1、附近商家功能,根据用户的位置和店铺的位置,匹配距离较近的商家给客户服务。 [图片] [图片]
2022-10-09 - 解析二维码失败,非小程序码,手机可以扫,用开发者工具的二维码编译就不行?
[图片] 先描述应用场景: 一种有效得推广手段,就是配置跳转小程序链接二维码,从而引流到微信小程序. 操作流程如下图: [图片] [图片] 然后用 这个测试地址生成一个二维码,举个栗子比如下面这个生成一个二维码,手机切换到体验版,扫码进入首页就可以了 https://api.XXXXX.com/share?uid=324 这里补充一个知识点: 这个链接地址 小程序里面得到的是一个q的参数标识. 参考这篇文章: https://www.jianshu.com/p/6c62a161e609 贴一下核心的业务代码: onLoad: function (options) { console.log("index 生命周期 onload" + JSON.stringify(options)) //在此函数中获取扫描普通链接二维码参数 if(options.q){ let q = decodeURIComponent(options.q); console.log("index 生命周期 onload url=" + q) console.log("index 生命周期 onload 参数 is_water=" + utils.getQueryString(q, 'is_water')) var is_water = utils.getQueryString(q, 'is_water'); console.log(is_water); console.log("index 生命周期 onload 参数 access_token=" + utils.getQueryString(q, 'access_token')) var access_token = utils.getQueryString(q, 'access_token'); console.log(access_token); } } utils.js截取方法如下: let getQueryString = function (url, name) { var reg = new RegExp('(^|&|/?)' + name + '=([^&|/?]*)(&|/?|$)', 'i') var r = url.substr(1).match(reg) if (r != null) { return r[2] } return null; } module.exports = { getQueryString: getQueryString } 通过上面方法即可获取普通二维码所携带的参数进行判断了 当然还有一种极端操作,如果我想在onLaunch生命周期里面获取,应该怎么操作呢? 当然还有一种极端操作,如果我想在onLaunch生命周期里面获取,应该怎么操作呢? 获取的参数会发生变动 :options?.query?.q onLaunch: function(options) { if(options?.query?.q){ let q = decodeURIComponent(options.query.q); let uid = getQueryString(q, 'uid'); if(uid){ this.inviteUserId = uid; uni.setStorageSync('inviteUserId',uid) } console.log('uid======onLaunch=======',uid) } [图片] [图片]
2022-09-28 - 小程序app.onLaunch与page.onLoad异步问题的最佳实践
场景: 在小程序中大家应该都有这样的场景,在onLaunch里用wx.login静默登录拿到code,再用code去发送请求获取token、用户信息等,整个过程都是异步的,然后我们在业务页面里onLoad去用的时候异步请求还没回来,导致没拿到想要的数据,以往要么监听是否拿到,要么自己封装一套回调,总之都挺麻烦,每个页面都要写一堆无关当前页面的逻辑。 直接上终极解决方案,公司内部已接入两年很稳定: 1.可完美解决异步问题 2.不污染原生生命周期,与onLoad等钩子共存 3.使用方便 4.可灵活定制异步钩子 5.采用监听模式实现,接入无需修改以前相关逻辑 6.支持各种小程序和vue架构 。。。 //为了简洁明了的展示使用场景,以下有部分是伪代码,请勿直接粘贴使用,具体使用代码看Github文档 //app.js //globalData提出来声明 let globalData = { // 是否已拿到token token: '', // 用户信息 userInfo: { userId: '', head: '' } } //注册自定义钩子 import CustomHook from 'spa-custom-hooks'; CustomHook.install({ 'Login':{ name:'Login', watchKey: 'token', onUpdate(token){ //有token则触发此钩子 return !!token; } }, 'User':{ name:'User', watchKey: 'userInfo', onUpdate(user){ //获取到userinfo里的userId则触发此钩子 return !!user.userId; } } }, globalData) // 正常走初始化逻辑 App({ globalData, onLaunch() { //发起异步登录拿token login((token)=>{ this.globalData.token = token //使用token拿用户信息 getUser((user)=>{ this.globalData.user = user }) }) } }) //关键点来了 //Page.js,业务页面使用 Page({ onLoadLogin() { //拿到token啦,可以使用token发起请求了 const token = getApp().globalData.token }, onLoadUser() { //拿到用户信息啦 const userInfo = getApp().globalData.userInfo }, onReadyUser() { //页面初次渲染完毕 && 拿到用户信息,可以把头像渲染在canvas上面啦 const userInfo = getApp().globalData.userInfo // 获取canvas上下文 const ctx = getCanvasContext2d() ctx.drawImage(userInfo.head,0,0,100,100) }, onShowUser() { //页面每次显示 && 拿到用户信息,我要在页面每次显示的时候根据userInfo走不同的逻辑 const userInfo = getApp().globalData.userInfo switch(userInfo.sex){ case 0: // 走女生逻辑 break case 1: // 走男生逻辑 break } } }) 具体文档和Demo见↓ Github:https://github.com/1977474741/spa-custom-hooks 祝大家用的愉快,记得star哦
2023-04-23 - 请问怎样注销?
注销微信一直卡到这个步骤,请问怎么注销???[图片]
2022-09-23 - 微信小程序全局使用websocket
这里说点题外话: 服务器必须要wss,不然推送不了. [图片] 我们通过[代码]vuex[代码]来实现全局状态管理,[代码]uniapp[代码]中内置了[代码]vuex[代码],可以直接引入使用。 在根目录下新建[代码]store[代码]文件夹,在底下建一个[代码]index.js[代码],代码如下: import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) const store = new Vuex.Store({ state: { socketTask: null }, mutations: { WEBSOCKET_INIT (state, url) { state.socketTask = uni.connectSocket({ url, // url是websocket连接ip success () { console.log('websocket连接成功') } }) state.socketTask.onOpen((res) => { console.log('WebSocket连接正常打开中...!') // 注:只有连接正常打开中 ,才能正常收到消息 state.socketTask.onMessage(res => { console.log('收到服务器内容:' + res.data) }); }) }, WEBSOCKET_SEND (state, data) { state.socketTask.send({ data, async success() { console.log('消息发送成功') }, }) }, CLOSE_SOCKET (state) { if (!state.socketTask) return state.socketTask.close({ success (res) { console.log('关闭成功', res) }, fail (err) { console.log('关闭失败', err) } }) } }, actions: { WEBSOCKET_INIT({ commit }, url) { commit('WEBSOCKET_INIT', url) }, WEBSOCKET_SEND({ commit }, data) { commit('WEBSOCKET_SEND', data) }, CLOSE_SOCKET({ commit }) { commit('CLOSE_SOCKET') } } }) export default store 更多内容参考这两篇文章: https://zhuanlan.zhihu.com/p/554350423 https://zhuanlan.zhihu.com/p/554353769 2022-09-19 补充 [图片]
2022-09-19 - 轻松实现小程序直接上传图片至腾讯云对象存储
概念介绍 对象存储(Cloud Object Storage,COS)是腾讯云提供的一种存储海量文件的分布式存储服务,用户可通过网络随时存储和查看数据。腾讯云 COS 使所有用户都能使用具备高扩展性、低成本、可靠和安全的数据存储服务。 前期准备 登录腾讯云对象存储控制台创建存储桶,获取 Bucket(存储桶名称) 和 Region(地域名称)。 通过管理控制台的 密钥管理 获取您的项目 SecretId 和 SecretKey 下载对象存储SDK,至小程序对象存储gitHub项目目录下载https://github.com/tencentyun/cos-wx-sdk-v5/tree/master/demo/lib下的cos-auth.js文件,添加至项目目录 项目实践 一、引用 [代码]var CosAuth = require('cos-auth');//引入对象存储SDK var config = require('../utils/api.js');//定义了项目的一些配置内容,例如存储桶名称、地区、请求域名等 var stsCache; //存储临时秘钥及秘钥过期时间内容 //定义上传接口 var uploadFile = function(filePath, cb) { // 请求用到的参数 var prefix = 'https://' + config.Bucket + '.cos.' + config.Region + '.myqcloud.com/'; // 对更多字符编码的 url encode 格式 var camSafeUrlEncode = function(str) { return encodeURIComponent(str) .replace(/!/g, '%21') .replace(/'/g, '%27') .replace(/\(/g, '%28') .replace(/\)/g, '%29') .replace(/\*/g, '%2A'); }; // 获取临时密钥 // 全局变量stsCache 存储临时秘钥及过期时间内容 var getCredentials = function(callback) { //判断临时秘钥未过期 if (stsCache && Date.now() / 1000 + 30 < stsCache.expiredTime) { callback(stsCache && stsCache.credentials); return; } //过期,服务器重新请求获取临时秘钥 wx.request({ method: 'GET', url: 'https://baidu.com/Api/Cos/getCosTempKeys', // 服务端签名,参考 server 目录下的两个签名例子 dataType: 'json', success: function(result) { var data = result.data.result; var credentials = data.credentials; if (credentials) { stsCache = data } else { wx.showModal({ title: '临时密钥获取失败', content: JSON.stringify(data), showCancel: false }); } callback(stsCache && stsCache.credentials); }, error: function(err) { wx.showModal({ title: '临时密钥获取失败', content: JSON.stringify(err), showCancel: false }); } }); }; // 计算签名 var getAuthorization = function(options, callback) { getCredentials(function(credentials) { callback({ XCosSecurityToken: credentials.sessionToken, Authorization: CosAuth({ SecretId: credentials.tmpSecretId, SecretKey: credentials.tmpSecretKey, Method: options.Method, Pathname: options.Pathname, }) }); }); }; // 上传文件 var uploadFile = function(filePath, cb) { var Key = filePath.substr(filePath.lastIndexOf('/') + 1); // 这里指定上传的文件名 getAuthorization({ Method: 'POST', Pathname: '/' }, function(AuthData) { var requestTask = wx.uploadFile({ url: prefix, name: 'file', filePath: filePath, formData: { 'key': Key, 'success_action_status': 200, 'Signature': AuthData.Authorization, 'x-cos-security-token': AuthData.XCosSecurityToken, 'Content-Type': '', }, success: function(res) { var url = prefix + camSafeUrlEncode(Key).replace(/%2F/g, '/'); if (res.statusCode === 200) { if (cb) { cb(url); } } else { wx.showModal({ title: '上传失败', content: JSON.stringify(res), showCancel: false }); } }, fail: function(res) { wx.showModal({ title: '上传失败', content: JSON.stringify(res), showCancel: false }); } }); requestTask.onProgressUpdate(function(res) { }); }); }; // 触发上传文件方法,按步骤调用执行 uploadFile(filePath, cb); }; module.exports = { uploadFile }; [代码] 2、调用以上SDK实现上传 1、 在需调用的文件,首先引入以上文件 var demoNoSdk = require(‘sdk文件路径’);//引入上述的对象存储SDK文件 2、 获取到上传文件的临时路径(备注:可以是直接调用wx.chooseImag方法获取的临时文件,也可以是调用wx.canvasToTempFilePath将画布导出的临时图片路径等等其他方式获取的到的临时图片文件路径) 这里调用wx. chooseImage为例 [代码]wx.chooseImage({ count: 1, sizeType: ['original', 'compressed'], sourceType: ['album', 'camera'], success: function(res) { //临时文件路径 var tempFilePath= res.tempFilePaths[0]; //定义上传图片成功后的回调函数 var callback = function(url) { //url为上传至对象存储成功后返回的云存储文件路径 //do other something …… }; //调用对象存储SDK,实现文件上传 //传递参数临时文件路径及上传成功后的回调函数,若不需要回调可不传此参数 demoNoSdk.uploadFile(tempFilePath, callback); }, }) [代码] 实现上传的过程如下 获取图片临时文件路径 ->调用SDK的uploadFile方法 ->服务器请求获取请求对象存储功能所需要的临时秘钥(并将秘钥结果及过期时间存储至全局变量,方便后面直接调用已缓存的未过期的临时秘钥即可,无需重复请求服务器获取) ->调用wx.uploadFile接口上传图片 ->上传成功后调用我们自定义的回调函数实现我们自己的业务。 至此,小程序直接上传图片至对象存储完成!
2019-04-27 - type=nickname 如何获取值的问题总结
之前写了如何最佳处理获取的头像参考地址: https://developers.weixin.qq.com/community/develop/article/doc/000666b2094e38f60c7ea4e4156813 这里看到很多朋友在问如何获取弹出来的昵称,之前用过,特来总结一下,方便大家及时解决这个小问题. 一:unipp写法如下: 参考这个: changeName(e){ this.nick_name = e.detail.value; console.log('this.nick_name.length',this.nick_name.length); let str = this.nick_name.trim(); if(str.length==0){ this.$toast('请输入合法的昵称') return } if((/[^/a-zA-Z0-9\u4E00-\u9FA5]/g).test(str)){ this.$toast('请输入中英文和数字') return } this.updateUserInfo() }, 二:微信原生参考: getname(e) { console.log(e) this.setData({ nickname: e.detail.value }) }, 运行代码片段后,用真机调试: https://developers.weixin.qq.com/s/XuSKVomH7fAK 地址来源这位朋友. [图片]
2022-09-26 - 【重要提议】button组件的chooseAvatar能力的几个问题?
经过测试我们发现chooseAvatar能力有如下问题,望微信团队予以评估,尽早优化: 1、无法通过配置实现“使用微信头像”、“从相册选择”、“拍照”三个选择的显隐; 2、无法通过配置控制“从相册选择”、“拍照”所取得图片是否压缩(默认都是可选择“原图”的,无法屏蔽); 3、最严重的问题:在用户选择非微信头像后,不知基于什么考虑,组件会自动向“http://finder-assistant.mp.video.tencent-cloud.com/snsuploadsimple”这个地址上传用户选择的图片(如下图所示),若用户选择的图片较大,这个过程会非常长,甚至有时还会超时崩溃。更麻烦的是:只有在这个上传过程完成后,组件才会触发bindchooseavatar事件,在此之前,组件没有暴露任何有帮助的事件供开发者进行体验优化。 [图片]。[图片] 个人认为这个chooseAvatar能力上线略显草率,在比较注重体验的小程序中,尚不可用。我们理解微信保护用户隐私的考量,但考虑到10月份即将收回getUserProfile能力,辛苦予以及时优化。
2022-06-19 - button组件chooseAvatar获取微信头像时,从相册选取图片后报错?
button组件open-type="chooseAvatar",微信版本8.0.27,基础库版本:2.26.0 底部下拉框中国呢选择从相册选取照片,照片过大,会直接报错,chooseavatar有失败回调吗?如何捕捉到这个错误? [图片]
2022-09-16 - 关于商品详情使用rich-text组件的总结
rich-text组件还是非常强大的。 文档地址:https://developers.weixin.qq.com/miniprogram/dev/component/rich-text.html 目前用这个组件容易遇到的问题如下: 1.连续的一张商品详情图片分割后,拼接在一起会出现空白的问题。之所以不用一张整图,是考虑到用户在非wifi模式下加载资源会很卡,图片大了都这样。所以会把大图裁剪成小于120kb左右的小图 2.会出现图片大小不自适应的问题,比如图片太大,只展示了一半。因为这两个情况都是经常遇到的,我这里就不找效果图展示了。 3.关于加载富文本里面的图片或者a标签是否能够触发事件,并执行对应的函数,答案是支持的,不过需要用三方的插件,这个参考我的另外一篇文章。 文章地址同步如下: https://zhuanlan.zhihu.com/p/554786156 下面直接上我珍藏版的代码: js,这里我把整个函数都放出来,但是关注点其实就两点: 1.p标签添加:font-size: 0 2.img标签,添加 style="max-width:100%;height:auto;display:block;" getData(){ uni.showLoading(); let self = this; this.$http.get("getBagDetail", {id: this.pId}) .then(res => { uni.hideLoading() if (res.data.code == 1) { //this.productList = res.data.data || []; let obj = res.data.data; this.swiperImgs = [obj.img_url] this.productObj = {...obj}; if(obj?.max_bag_order){ let bag_type = obj?.max_bag_order?.bag_type; // 1 基础套餐 2 全额套餐 this.isFlag = bag_type == 1 ? true : false this.isBuy = false; this.bag_type = bag_type; }else { this.isBuy = true; } let richObj1 = obj["detail_info"].replace( /\ 以上是目前能够想到的解决方案,如果大家有其他比较好的方案,欢迎留言补充。互勉! [代码]tip[代码]: nodes 不推荐使用 String 类型,性能会有所下降。[代码]tip[代码]: [代码]rich-text[代码] 组件内屏蔽所有节点的事件。[代码]tip[代码]: attrs 属性不支持 id ,支持 class 。[代码]tip[代码]: name 属性大小写不敏感。[代码]tip[代码]: 如果使用了不受信任的 HTML 节点,该节点及其所有子节点将会被移除。[代码]tip[代码]: img 标签仅支持网络图片。[代码]tip[代码]: 如果在自定义组件中使用 [代码]rich-text[代码] 组件,那么仅自定义组件的 wxss 样式对 [代码]rich-text[代码] 中的 class 生效。[图片]
2022-09-19 - 小程序新手引导实现思路整理
从个人开发和使用角度来说,新手引导属于一个比较鸡肋的功能。 [图片] [图片] 反正就是有几个提示,就弹几个框。这里采用的方式是,用三倍图片,然后用2倍的尺寸,然后加上一个黑科技属性,可以做到弹框图片高清不模糊的效果。 话不多说直接上代码:使用点击 +1,的逻辑动态显示对应的图片 <Mask v-if="showGuide"> <view class="maskGuide"> <view v-if="guideNumber == 1" @tap="clickToNext"> <image src="XXX.png" class="tl-img-1"/> </view> <view v-if="guideNumber == 2" @tap="clickToNext"> <image src="XXX.png" class="tl-img-2"/> </view> <view v-if="guideNumber == 3" @tap="clickToNext"> <image src="XXX.png" class="tl-img-3"/> </view> <view v-if="guideNumber == 4" @tap="clickToNext"> <image src="XXX.png" class="tl-img-4"/> </view> <view v-if="guideNumber == 5" @tap="clickToNext"> <image v-if="gender==1" src="XXX.png" class="tl-img-5"/> <image v-else src="XXX.png" class="tl-img-5"/> </view> </view> </Mask> js 逻辑: uni.setStorageSync('isGuide', true) //存储本地,只弹出一次的逻辑 // 新手引导初步下一步逻辑 clickToNext(){ this.guideNumber +=1 if(this.guideNumber == 6){ uni.setStorageSync('isGuide', true) this.showGuide = false; } }, less .maskGuide{ position: relative; width: 100vw; height: 100vh; .tl-img-1{ position: absolute; top: 48rpx; left: 20rpx; width: 584rpx; height: 230rpx; image-rendering: -webkit-optimize-contrast; } .tl-img-2{ position: absolute; top: 174rpx; left: 20rpx; width: 646rpx; height: 230rpx; image-rendering: -webkit-optimize-contrast; } .tl-img-3{ position: absolute; top: 40rpx; right: 0; width: 630rpx; height: 308rpx; image-rendering: -webkit-optimize-contrast; } .tl-img-4{ position: absolute; top: 240rpx; right: 10rpx; width: 604rpx; height: 112rpx; image-rendering: -webkit-optimize-contrast; } .tl-img-5{ position: absolute; top: 164rpx; right: 35rpx; width: 684rpx; height: 1048rpx; image-rendering: -webkit-optimize-contrast; } } 以上是目前想到的一个实现方案,如果有其他实现方案,欢迎大家补充。共勉! [图片]
2022-09-02 - 小程序 跳转传参 技能升级
方案一: //A页面 实现跳转 jump: function () { let a = 1; let b = 2; wx.navigateTo({ url: '/page/b/b?data1=' + a + '&data2=' + b }) } //B页面 接收参数 onLoad: function (options) { //此时A页面传递的参数由options接收,A页面传递参数时的参数名分别叫data1和data2,所以B页面想拿到A页面传递的参数拿对应的参数名即可 let data1 = options.data1; let data2 = options.data2; console.log(data1);//输出的结果是 1 console.log(data2);//输出的结果是 2 } 方案二: a页面 godeposit(e) { // console.log(e.currentTarget.dataset) let aa =JSON.stringify(e.currentTarget.dataset); app.navigateTo("pages/my/deposit/deposit?data1="+ aa) } b页面 onLoad: function (options) { let a =JSON.parse(options.data1); console.log(a) } 方案三:最新方案,解决大数据传输问题: 参考文档: EventChannel | 微信开放文档 传参模式: let obj = { disease_ids: strIds, disease_name: strNames, } uni.navigateTo({ // url:`/packageA/pages/estimateQuestion/estimateQuestion?disease_ids=${strIds}&disease_name=${strNames}`, url:`/packageA/pages/estimateQuestion/estimateQuestion`, success: function (res) { res.eventChannel.emit('getData', obj) //触发事件 } }) 接收模式: onLoad: function(options) { //接收页面 const eventChannel = this.getOpenerEventChannel(); eventChannel.on("getData",data => { console.log(data) }); } [图片] [图片] [图片] [图片]
2022-08-26 - 开发者如何进行有效的提问
在你的问题被解决前,你需要做的最最重要的一件事,就是让回答者知道你在问什么,否则别人很难给你答复。 你遇到了什么问题? 你的预期是什么? 你的环境是什么(运行的软件版本、操作系统)? 你尝试了哪些解决方案,对解决当前问题是否有帮助? 出现问题的代码直接贴出来 ,或者提供代码文件. 按照上面的规则,描述你的问题,让回答者能更加清晰的知道你遇到的是什么问题,除此之外,你可能还需要按照提供一些额外信息,来帮助回答者更快的帮你解决问题,毕竟帮回答者节约时间,也是给你自己节约时间。 另外就是需要自己挖的坑自己填: 1、及时回应/补充你的问题,有些人可能是第一次提问,遗漏了一些东西,有时候就会有人提醒你,需要补充哪些信息,请及时处理。 2、与回答者沟通,如果你的问题描述的不是那么完美无瑕,或者回答者理解偏差导致给出了错误的回答,你应该及时的跟回答者进行沟通,而不是无视别人。 自己提出的问题,如果有了解决方案,一定要去把这个坑填上,共勉! [图片] [图片]
2022-07-25 - 微信小程序头像昵称实战篇
2022-08-25 api文档地址: https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/userProfile.html 目前的api变更后,得到的地址为 临时地址, 这个是文档没有说明的, 最佳实践,是需要把得到的地址上传到自己的服务器,然后用服务器返回的地址作为 真实头像的永久地址. 核心点说明: //获取到api返回的新地址路径 onChooseAvatar(e) { this.avatarUrl = e.detail.avatarUrl console.log('e.detail', e.detail) // this.updateUserInfo(); this.uploadFile(); }, /* 上传 头像 转 话格式*/ uploadFile(){ uni.uploadFile({ url: config.webUrl + '/upload/uploadImages',//后台接口 filePath: this.avatarUrl,// 上传图片 url name:'image', // formData: this.formData, header: { 'content-type': 'multipart/form-data', 'token': uni.getStorageSync('token') }, // header 值 success: res => { let obj = JSON.parse(res.data) console.log('obj', obj) if (obj.code == 1) { let imgUrl = obj.data.full_path; this.userImg = imgUrl; this.updateUserInfo(); } else { uni.showToast({ icon: 'none', title: '图片太大,请重新选择!' }); } }, fail: e => { this.$toast('上传失败') } }); }, 这里需要注意, wx.uploadFile 返回的是字符串类型,需要前端自己处理一下数据结构: [图片] 完整代码如下: import config from "@/common/config.js"; import {debounce} from '@/utils/debounce.js' export default { data() { return { defaultAvatarUrl: 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0', avatarUrl: '', nick_name: '', userImg: '', } }, onLoad() { let userInfo = uni.getStorageSync('userInfo') || {}; let { nick_name,img_url } = {...userInfo}; this.userImg = img_url; this.nick_name = nick_name; }, methods: { onChooseAvatar(e) { this.avatarUrl = e.detail.avatarUrl console.log('e.detail', e.detail) // this.updateUserInfo(); this.uploadFile(); }, inputWord: debounce(function(e){ this.nick_name = e.detail.value console.log('this.nick_name.length',this.nick_name.length) let str = this.nick_name.trim(); if(str.length==0){ this.$toast('请输入合法的昵称') return } if((/[^/a-zA-Z0-9\u4E00-\u9FA5]/g).test(str)){ this.$toast('请输入中英文和数字') return } this.updateUserInfo() }, 1500), /* 上传 头像 转 话格式*/ uploadFile(){ uni.uploadFile({ url: config.webUrl + '/upload/uploadImages',//后台接口 filePath: this.avatarUrl,// 上传图片 url name:'image', // formData: this.formData, header: { 'content-type': 'multipart/form-data', 'token': uni.getStorageSync('token') }, // header 值 success: res => { let obj = JSON.parse(res.data) console.log('obj', obj) if (obj.code == 1) { let imgUrl = obj.data.full_path; this.userImg = imgUrl; this.updateUserInfo(); } else { uni.showToast({ icon: 'none', title: '图片太大,请重新选择!' }); } }, fail: e => { this.$toast('上传失败') } }); }, updateUserInfo(){ let self = this; uni.showLoading({}); let params = { img_url: this.userImg, nick_name: this.nick_name.trim(), } self.$http.post('updateUserInfo', params).then(res => { uni.hideLoading() if (res.data.code == 1) { self.$toast('修改成功!') }else { self.$toast(res.data.msg) } }) }, } } 请一键三连,争取升个级,谢谢各位道友! 补充一下,如果api不生效注意切换一下版本库: 我本地用的2.26.1 [图片] 实际效果图: [图片] [图片]
2022-11-24 - 医疗 > 其他医学健康服务的服务类目,已提供完整正确的医疗机构合作协议为什么依旧不给通过?
我们这次提交的是和拥有合法执照的医疗检测机构签署的基因检测相关的合作协议,合法合规,请问官方为什么还是给出一个含糊不清的理由予以不通过,请给出一个合理的解释,不要影响我们正常的业务进展。 [图片]
2019-11-15 - 请问,如何在h5移动端凋起微信分享?
需求:点击按钮凋起微信(对话/朋友圈)分享
2022-07-08 - 小程序性能优化实践
小程序性能优化课程基于实际开发场景,由资深开发者分享小程序性能优化的各项能力及应用实践,提升小程序性能表现,满足用户体验。
10-09 - 小程序支持跳转到应用宝或者第三方网页吗?
目前没有这个功能。小程序web-view只支持跳转到开发者自己的页面。
2020-01-14 - 【已解决、uniapp】app微信授权登录和小程序微信授权登录返回的unionid不一样?
都是同一个用户登录 app和小程序都已经绑定到同一个主体的微信开放平台 移动应用 appid:wx3cc022dc55e3a437 返回unionid:oU5Yytx43g8DvJl9Uvb-nc7RUHn4 小程序 appid:wxbf4e6a077dadb4ac 返回unionid:orFT1vyJQTjl87o4Y1ETDAzia08Q [图片] [图片] [图片] [图片] 解决问题: 原因在于Hbuilder不会动态使用我们配置的appid等参数,需要先生成自定义基座才能使用。 [图片] https://ask.dcloud.net.cn/article/35115 参考资料: https://ask.dcloud.net.cn/question/78075 https://blog.csdn.net/E86huang/article/details/100535603
2020-07-22 - 小程序显示H5网页教程
一、介绍 小程序里显示Html代码,目前插件(wxParse 解析html)解析支持不太好,有时候格式还是达不到预想的效果。 小程序里的HTML语法有点不好用, 最好的解决方案是直接在里面显示HTML页面,使用小程序的web-view组件。 之前由于需要验证原因,很多人把Bmob的素材页面填写到微信的业务域名,提示: 小程序设置web-view业务域名,解决“不支持打开非业务域名,请重新配置” 这个提示的根本原因,是没有成功设置业务域名。次次开放业务域名设置。大家可以上传静态HTML文件到自己应用。 二、设置业务域名前要做的准备 1.备案的域名 (如果有自己备案的域名,可以提交工单绑定自己域名,没有Bmob后端可以开通一个应用私人域名) 2.FTP 上传工具(这里大家通过Bmob后台素材生成HTML,或自己编写的代码工具都可以)当然FTP会更方便管理这些文件。 三、设置 1.登陆微信小程序控制台,进入设置-》开发设置-》业务域名(这里注意,目前只有企业资质也有这个设置) [图片] 2.点击设置,注意这里需要管理员扫描 下载这个效验文件到电脑,目前格式是txt。传入到自己私人域名 [图片] 3.上传到自己应用文件域名下 [图片] 4.检查是否正确 上传成功后,点击保存。完成设置 四、显示HTML 示例代码 [图片] https://mp.weixin.qq.com/ 是你的HTML地址。 数据库查询标题,列表也点击开这个URL。 例如Bmob的图文素材表,都会有个HTML,大家可以把这个地址填入web-view 这样就完美的解决了小程序显示HTML代码错乱问题。 欢迎加Bmob官方Q群探讨:586487943
2019-04-23 - 那些被忽略的盒子模型小知识
那些被忽略的盒子模型小知识 本文是笔者在学习CSS时的一些小白总结 我们知道的盒子模型主要由4个区域组成,分别是内容区域(content),内边距区域(padding),边框区域(border)和外边距区域(margin)。 对于不了解盒子模型的朋友可以移步到这里了解一下。 [图片] Content(内容) 1. 替换元素 替换元素(replaced element),顾名思义就是内容可以被替换的元素。 我们通常会把一些特殊意义的文本替换成图片,比如一个网站的logo。 [图片] 我们会在页面上看到的不是h1标签显示的”Google“文字,而是谷歌logo的图片。使用了content的元素的内容在html标签中是不存在的。这样做就有个好处,当爬虫来访问我们的网站,爬虫可以知道我们这个主站的h1标题是”Google“而不是一个img标签,且在视觉上给用户更好的体验。 2. 伪元素::before和::after [图片] 为了实现上面显示价格,之前写react代码时候会经常这么写,感觉在逻辑上写了好多关联性不大的文本。其实可以利用[代码]::before[代码]和[代码]::after[代码]两个伪元素,把这些与逻辑不相关的写在css里,react dom则专注于数据的表现。 [代码]<div className="price-panel"> <span className="price-panel__price"> ¥ {(totalPrice / 100).toFixed(1)} </span> <span className="price-panel__discount-price"> 已省¥ {(totalDiscountPrice / 100).toFixed(1)} </span> <span className="price-panel__discount"> ( {(discount / 10).toFixed(1)} 折) </span> </div> [代码] 使用了伪元素后的react代码显然更加清晰表示数据。 HTML: [代码]<div className="price-panel"> <span className="price-panel__price"> {(totalPrice / 100).toFixed(1)} </span> <span className="price-panel__discount-price"> {(totalDiscountPrice / 100).toFixed(1)} </span> <span className="price-panel__discount"> {(discount / 10).toFixed(1)} </span> </div> [代码] SCSS: [代码].price-panel { &__price { &::before { content: '¥'; } } &__discount-price { &::before { content: '已省¥'; } } &__discount { &::before { content: '('; } &::after { content: '折)'; } } } [代码] 我们还能使用伪元素帮助实现一些本来需要多个div实现的样式,比如下面这个对话框。 [图片] HTML: [代码]<div class="dialog">Hi,I’m a bubble dialog. Can you see me?</div> [代码] CSS: [代码].dialog { background: #f0f; padding: 10px; border-radius: 10px; color: white; max-width: 250px; position: relative; overflow: visible; } .dialog::after { position: absolute; content: ''; display: inline-block; border-width: 5px 10px; border-style: solid; border-color: transparent transparent #f0f #f0f; width: 0; height: 0; right: -20px; } [代码] Padding(内边距) padding的百分比值是非常有用的。需要注意的padding的百分比值,无论是水平方向还是垂直方向都是相对于父级元素的宽度进行计算的。 如果需要弄一张16:9的等比缩放图片,可以利用padding的这个特性,设置一个[代码]padding-top[代码]或者[代码]padding-bottom[代码]为56.25%即可(100\16*9) [图片] [图片] Margin(外边距) 1. margin合并 块级元素的[代码]margin-top[代码]和[代码]margin-bottom[代码]有时候会合并为单个margin,这种现象叫margin合并。 margin合并发生两个重要元素 必须是块级元素 只发生在垂直方向。 margin合并的场景 1.1 相邻兄弟元素 [图片] 1.2 父级和第一个/最后一个子元素 在实际开发中,父子margin合并很有可能会带给我们麻烦。 如下图所示,div表现出和我们预想不一致的结果。 [图片] 那么怎么才能防止这种父子margin合并导致的和预想不一致问题呢? 解决方法如下(这里直接复制了张鑫旭老师书籍《CSS世界》的原话。): (1)对于margin-top合并(满足一个即可): 父元素设置为BFC 设置[代码]border-top[代码]的值(亲测transparent也可以的) 设置[代码]padding-top[代码]的值 父元素和第一个子元素之间添加内联元素 (2)对于margin-bottom合并(满足一个即可): 父元素设置为BFC 设置[代码]border-bottom[代码](transparent也可以的) 设置[代码]padding-bottom[代码] 父元素和最后一个子元素之间添加一个内联元素 父元素设置[代码]height[代码]、[代码]min-height[代码]或者[代码]max-height[代码] 1.3 空块级元素的margin合并 [图片] 2. margin auto 每当说到[代码]margin:auto[代码],我的第一反应是居中。但这个只是一个浅层应用的表象。 接下来我们去一起看看这个[代码]margin:auto[代码]究竟是‘何方神圣’。 [代码]margin:auto[代码]的填充规则如下: 如果一侧定值,一侧auto,则auto为剩余空间大小。注意auto并不是0的意思。 如果两侧都是auto,则平分剩余的空间 我会疑惑为什么我设置了[代码]margin: auto[代码],却在垂直方向上没有居中。 [图片] 这里《css世界》中给出的答案让人非常容易理解。假如把.son元素的height去掉,.son的高度会自动变成父元素的200px,显然不会,所以无法触发margin: auto。同理,如果把width为200px去掉,确实是会和父元素一样宽。 那么如何让垂直居中呢? 子元素使用绝对定位后设置[代码]margin: auto[代码]即可 [图片] Border(边框) 用border绘制三角形 我们可以利用border color为透明来绘制一些图形,比如三角形 [图片] 注意[代码]border-color[代码]这个属性。 [代码]/* border-color: color; 单值语法 */ border-color: red; /* border-color: vertical horizontal; 双值语法*/ border-color: red #f015ca; /* border-color: top horizontal bottom; 三值语法 */ border-color: red yellow green; /* border-color: top right bottom left; 四值语法 */ border-color: red yellow green blue; [代码] 当然,我们绘制三角形不限于这种等腰三角。 [图片] 这里绘制了一个底边分别是60px和160px的直角三角形。 参考 文章主要参考了张鑫旭老师的《css世界》并根据自己的业务做出的一些实践总结。
2019-04-28 - 分享一个自适应的自定义导航栏组件
自定义导航栏的一些常见问题: 1、怎么适配手机状态栏高度,使导航栏与胶囊按钮对齐? 2、写了导航栏组件但是用了fixed定位,脱离普通文档流,要麻烦的去每个页面加padding-top。 3、怎么让导航栏跟page融合到一起,跟随page滚动,这样就不用在这个页面单独适配状态栏了。 4、怎么自动识别非首页启动的小程序,在自定义导航栏加个返回首页的引导? 使用案例可导入代码片段【现在导入】查看。 子页面 [图片][图片] [图片] 非首页启动的小程序 [图片] 跟随页面,不置顶。 [图片] 监测滚动切换导航栏的显示或隐藏。 [图片] -----------------------------------------------------------------------------------------------------------------------------------------
2019-05-26 - 云开发实战分享|诗和远方:旅行小账本云开发
原创:豪豪 前言 最近沉迷小程序开发,发现了一款功能、界面、体验俱佳的小程序“旅行小账本”。着手做了个简约版——“旅行小账本”。效果比较满意,毕竟前后台一人单干。 [图片] IDE 微信开发者工具 VSCode 小程序开发必然少不了微信开发者工具,再加上其对云开发的全面支持,再好不过的开发利器。但熟悉微信开发者工具的朋友们应该知道,它不支持Emmet缩写语法,并且wxml的属性值默认用单引号表示(强迫症表示很难受)。 而VSCode很好的补足了微信开发者工具的不足之处,并且支持多元化插件开发,轻量好用。 所以这里推荐采用微信开发者工具+VSCode配合开发。微信开发者工具负责调试、模拟小程序运行情况,VSCode负责代码编辑工作。二者各司其职,会使开发更加的高效、便捷。 总体架构 该项目基于小程序云开发,使用的模板是云开发快速启动模板。 由于是个全栈项目,前端使用小程序所支持的wxml + wxss + js开发模式,命名采用BEM命名规范。后台则是借助云数据库+云储存进行数据管理。 项目总体结构 [代码]|-travelbook 项目名 |-cloudfunctions 云函数模块 |-deleteItems 级联删除--云函数 |-getTime 获取时间--云函数 |-miniprogram 项目模块 |-components 自定义组件 |-accountCover 账本封面组件 |-spendDetail 支出细节组件 |-pages 页面 |-accountBooks 总账本页 |-accountCalendar 账本日历页 |-accountDetail 支出细节页 |-accountList 支出明细页 |-accountPage 选定账本页 |-editAccount 账本编辑页 |-index 首页 |-vant-weapp 有赞vant框架组件库 |-··· 系列组件... app.js 全局js app.json 全局json配置 app.wxss 全局wxss [代码] 逆向工程 在做该小程序之前,有必要进行项目的逆向工程,进一步解构每一个页面,从而深入了解这款小程序的交互细节。那么现在我假设自己为腾讯旅游的产品设计师,在绘制完界面原型后,撰写了相应的交互文档。当然解构过程中可能有些细节处理并没有那么仔细到位… 以下是我绘制的界面原型 [图片] [图片] [图片] 接下来对每个页面的细节进行解构,并完成简单的wxml结构 [图片] [代码]<!--switchList使用定位布局--> <view bindtap="switchList" class="list"></view> <!--newAccount使用flex布局--> <view class="newAccount" bindtap="createNewAccount"> <view class="desc">旅行中的每一笔开支都有独特的意义!</view> <image src="{{}}"></image> <view class="title">创建一个新账本</view> </view> [代码] [图片] [代码]<!--整体用flex + 百分比布局--> <input type="text" class="accuntName" placeholder="旅行账本名称" bindinput="getInput" /> <van-panel title="选择封面" class="panel"> <van-row class="imageBox"> <!--使用wx:for遍历数据库账本图片信息--> <van-col span="8" class="imgCol" bindtap="selectThis"> <image class="select" src="{{}}"></image> </van-col> <van-col span="8"> <view class="addBox" bindtap="useMore">更多封面</view> </van-col> </van-row> </van-panel> <button type="primary" bindtap="save">保存</button> <button type="warn" bindtap="delete">删除</button> [代码] [图片] [代码]<view class="accountDesc" bindtap="viewDetail"> <!--使用wx:for遍历数据库账本信息--> <view class="accountName"> <view>{{}}</view> <view class="accountTime">{{}}</view> </view> <!--绝对定位--> <image class="updateImg" catchtap="editAccount" src="{{}}"></image> </view> [代码] [图片] [代码]<!--switchList使用定位布局--> <view bindtap="switchList" class="list"></view> <view class="account__list-year">{{}}</view> <view class="account__list-new account__list-public" bindtap="createNewAccount"> <!--日期小圆点--> <view class="account__list-point"></view> <view class="account__list-time">{{}}</view> <image src="{{}}"></image> <view class="account__list-title">创建一个新账本</view> </view> <!--使用wx:for遍历数据库账本信息--> <view class="account__list-item account__list-public" bindtap="viewDetail"> <!--日期小圆点--> <view class="account__list-point"></view> <image src="{{}}" mode="aspectFill"></image> <view class="account__list-name">{{}}</view> <view class="account__list-time">{{}}</view> <image class="account__list-update" catchtap="editAccount" src="{{}}"></image> </view> [代码] [图片] [代码]<view class="account__spend"> <image bindtap="getCalendar" class="account__spend-calendar" src="{{}}"></image> <view class="account__spend-text"> <view class="account__spend-total">总花费(元)</view> <view class="account__spend-num">{{}}</view> </view> <image bindtap="accountAnalyze" class="account__spend-detail" src="{{}}"></image> </view> <view class="account__show-time">今天</view> <view class="account__show-detail"> <view class="account__show-income account__show-public"> <view class="account__show-title">收入(元)</view> <text class="account__show-in">+{{}}</text> </view> <view class="account__show-spend account__show-public"> <view class="account__show-title">支出(元)</view> <text class="account__show-out">-{{}}</text> </view> </view> <!--使用wx:for遍历数据库账本信息--> <view class="account__show-items-spend"> <view> <image src="{{}}"></image> </view> <text>{{}}</text> <text class="account__show-items-money">{{}}</text> </view> [代码] [图片] [代码]<!--日历使用极点日历的插件--> <!--json中做配置--> "usingComponents": { "calendar": "plugin://calendar/calendar" } <!--js改变样式--> days_style.push({ month: 'current', day: new Date().getDate(), color: 'white', background: '#e0a58e' }) <!--wxml中引用--> <calendar weeks-type="cn" cell-size="50" next="{{true}}" prev="{{true}}" show-more-days="{{true}}" calendar-style="demo6-calendar" header-style="calendar-header"board-style="calendar-board" active-type="rounded" lunar="true" header-style="header"calendar-style="calendar"days-color="{{days_style}}"> </calendar> [代码] [图片] [代码]<!--顶栏日期及收支结构--> <view class="account__title"> <text class="account__title-time">{{}}</text> <text class="account__title-spend">支出{{}}元 收入{{}}元</text> </view> <!--收支细节结构 使用flex弹性布局--> <view class="account__detail"> <image src="{{}}"></image> <view class="account__detail-name">{{}}</view> <view class="account__detail-money">{{}}</view> </view> [代码] [图片] [代码]<!--使用vant框架的van-tabs组件--> <!--并封装自定义组件复用收支页,自定义组件后面会详细说明--> <van-tabs active="{{ active }}" bind:change="onChange"> <van-tab title="支出"> <spendDetail detail="{{detail}}" accountKey="{{accountKey}}"></spendDetail> </van-tab> <van-tab title="收入"> <spendDetail detail="{{income}}" accountKey="{{accountKey}}"></spendDetail> </van-tab> </van-tabs> [代码] 云开发 在做完逆向工程的解构,页面基础结构基本搭建完成。但页面依旧是静态的,需要数据来填充。所以第二步就是数据库的设计。而小程序的云控制台恰好提供了数据的操作功能,为数据驱动提供基石。 [图片] 云数据库设计 云数据库是一种NoSQL数据库。每一张表是一个集合。值得注意的是在设计数据库时,[代码]_id[代码] 和[代码]_openid[代码]这两个字段需要带上。[代码]_id[代码]是表的主键,而[代码]_openid[代码]是用户标识,每个用户都有不同的[代码]_openid[代码],可区分不同用户。 以下是项目中的数据表设计 [代码]cover_photos 账本封面表 用于存储创建账本时需要的封面信息 - _id - _openid - cover_index 封面索引 - cover_url 封面url - isSelected 封面是否选中 [代码] [代码]accounts 账本表 用于存储用户创建的账本 - _id - _openid - accountKey 账本唯一标识 - coverUrl 账本封面 - i 账本索引 - inputValue 账本名字 - now 账本创建时间 - spend 账本总花费 [代码] [代码]account_detail 支出类型表 用于存储消费类型 - _id - _openid - detail 类型细节 - pic_index 消费类型索引 - pic_url 未点击时的图片 - pic_url_act 点击后的图片 - type 消费类型 [代码] [代码]account_income 收入类型表 用于存储收入类型 - _id - _openid - pic_index 收入类型索引 - pic_url 未点击时的图片 - pic_url_act 点击后的图片 - type 收入类型 [代码] [代码]spend_items 消费明细表 - _id - _openid - accountKey 账本唯一标识 - address 消费地点 - desc 消费描述 - fullDate 消费时间 - money 消费金额 - pic_type 消费类型 - pic_url 消费类型图片 [代码] 云储存管理 这是个非常实用的板块。类似于<a href=“https://pan.baidu.com/”>百度云盘</a>,它提供了文件存储、上传与下载功能。 [图片] 除此之外,它还会将你所上传的资源自动进行压缩操作,并生成一个地址供你引用。该项目中的一些图片资源就是存在于此,然后在云数据库的字段中引用这些资源地址即可,十分方便,不必在本地存储,占用小程序内存。 [图片] 云函数设计 云函数简单来说就是在云后端(Node.js)运行的代码,本地看不到这些代码的执行过程,全封闭式只暴露接口供本地调用执行,本地只需等待云端代码执行完毕后返回结果。这也是面向接口编程的思想体现。 项目中的云函数设计 [图片] [代码]// getTime 获取当前时间并格式化为 yyyy-mm-dd // 云函数入口文件 const cloud = require('wx-server-sdk') // 初始化云函数 cloud.init() // 云函数入口函数 exports.main = async (event, context) => { var date = new Date() var seperator1 = "-" var year = date.getFullYear() var month = date.getMonth() + 1 var strDate = date.getDate() if (month >= 1 && month <= 9) { month = "0" + month } if (strDate >= 0 && strDate <= 9) { strDate = "0" + strDate } // 格式化当前时间 var currentdate = year + seperator1 + month + seperator1 + strDate return currentdate } [代码] [代码]// deleteItems 批量删除,云数据库的批量删除只允许在云函数中执行 // 云函数入口文件 const cloud = require('wx-server-sdk') // 初始化云函数 cloud.init() // 连接云数据库 const db = cloud.database() const _ = db.command // 云函数入口函数 exports.main = async (event, context) => { try { return await db.collection('spend_items') .where({ accountKey: event.accountKey }) .remove() } catch (e) { console.error(e) } } [代码] MVVM 界面有了,数据有了。万事俱备,只欠东风!所以下一步就是MVVM的设计。小程序本质就是基于MVVM所设计的,在MVVM的世界里,数据是灵魂,一切都由数据来驱动。 账本页显示 [图片] 账本页有两种显示的风格,左上角的按钮可以来回切换风格,下拉可刷新页面,显示accounts数据表中存储的账本信息。显示时有个小细节,需要根据创建的时间先后来显示,越晚创建的越先显示。 [代码]// 页面数据设计, 在wxml中使用{{}}符号引用数据,数据就动态显示到了页面上 data: { isList: false, // 转换页面风格的标识 true为竖向风格 false为横向风格 accounts: [], // 存储查询的账本数据 now: null, // 存储当日时间 year: null // 存储年份 } // 转换显示风格 switchList() { // 设置页面风格样式 let isList = !this.data.isList this.setData({ isList }) wx.setStorage({ key: "isList", data: isList }) } // 获取页面风格转换标识 var isList = wx.getStorageSync('isList') // 查询账本 db.collection('accounts') .get({ success: res => { this.setData({ accounts: res.data.reverse(), // 反转数组,优先显示创建早的账本 isList }) wx.hideLoading() } }) // 调用云函数接口 获取当前日期 wx.cloud.callFunction({ // 云函数接口名就是创建的云函数名字,这里是'getTime' name: 'getTime', success: (res) => { let year = res.result.split('-')[0] this.setData({ now: res.result, year }) }, fail: console.error }) [代码] 账本页增删改 [图片] 账本页通过调用相应的云数据库API,可进行一系列的增删改操作。值得一提的是,修改时需要表单回显,删除时需要级联删除。因为一个账本中有许多收支情况,spend_items表就是进行收支记录,所以删除账本时需要级联删除对应的spend_items表中的收支信息。 一些重要的逻辑 封面单选逻辑[代码]data: { images: [], // 封面数组 selectImg: null, // 选择其它封面 isSelected: {}, // 选中的图片 inputValue: '', // 账本名字 now: null, // 当前时间 account: {} // 传入账本信息 } // 单选逻辑 通过构造{'0': isSelected}来实现 selectThis(e) { let index = e.currentTarget.dataset.index let coverUrl = e.currentTarget.dataset.coverurl let is = this.data.isSelected[index] let obj = { coverUrl } // obj[index] 属性动态改变 obj[index] = !is obj.i = index this.setData({ isSelected: obj }) } [代码] 表单回显逻辑[代码]// 页面加载时先通过对应的accountKey, 得到回显信息 let { i, id, value, url, accountKey } = options photos.get({ success: res => { this.setData({ images: res.data, account: { id, value, url, i, accountKey }, isSelected: obj }) wx.hideLoading() } }) // 修改 save() { let { id } = this.data.account let { i, coverUrl, value } = this.data.isSelected // 若没修改 则为之前的value let inputValue = this.data.inputValue || value [代码] db.collection(‘accounts’) .doc(id) .update({ data: { inputValue, coverUrl, i } }) } ``` 级联删除逻辑[代码]db.collection('accounts') .doc(this.data.account.id) .remove() .then(() => { wx.hideLoading() wx.showToast({ title: '删除成功' }) setTimeout(() => { wx.reLaunch({ url: '../accountBooks/accountBooks' }) }, 400) }) // 调用deleteItems云函数, 传入对应accountKey主键, 通过云函数批量删除 wx.cloud.callFunction({ name: 'deleteItems', data: { accountKey } }) [代码] 账本页收支 [图片] 因为收入与支出页面基本类似,所以使用自定义组件封装,可以复用。 [代码]// 封装spendDetail组件 // 注册组件 properties: { detail: { type: Object }, accountKey: { type: Number }, isSpend: { type: Boolean } } // 引用组件 <van-tab title="支出"> <spendDetail detail="{{detail}}" accountKey="{{accountKey}}" isSpend="{{isSpend}}"></spendDetail> </van-tab> <van-tab title="收入"> <spendDetail detail="{{income}}" accountKey="{{accountKey}}" isSpend="{{isSpend}}"></spendDetail> </van-tab> [代码] 收入与支出类型icon选择使用两个view来存放,通过选择不同类型,跳转不同的icon [代码]// js data: { address: '', money: 0, desc: '', selectPicIndex: 0, selectIndex: 0 } // 选择消费类别 selectSpend(e) { let { index } = e.currentTarget.dataset let { selectPicIndex } = this.data selectPicIndex = index this.setData({ selectPicIndex }) }, // 选择消费类别中的细节 selectSpendDetail(e) { let { index } = e.currentTarget.dataset let { selectIndex } = this.data selectIndex = index this.setData({ selectIndex }) } // wxml // 消费类型 <view class="expense"> <block wx:for="{{detail}}" wx:key="index"> <view class="expense__type" bindtap="selectSpend" data-index="{{index}}"> <block wx:if="{{selectPicIndex == item.pic_index}}"> <view class="expense__type-icon" style="background-color: #e64343"> <image src="{{item.pic_url_act}}"></image> </view> </block> <block wx:else> <view class="expense__type-icon"> <image src="{{item.pic_url}}"></image> </view> </block> <view class="expense__type-name">{{item.type}}</view> </view> </block> </view> // 消费子类型 <view class="detail"> <block wx:for="{{detail[selectPicIndex].detail}}" wx:key="index"> <view class="detail__type" bindtap="selectSpendDetail" data-index="{{index}}"> <image class="detail__type-icon" src="{{item.detail_url}}"></image> <block wx:if="{{selectIndex == item.detail_index}}"> <view class="detail__type-name" style="color: #f86319; border-bottom: 1rpx solid #f86319;"> {{item.detail_type}} </view> </block> <block wx:else> <view class="detail__type-name" style="border-bottom: 1rpx solid #e4e2e2;"> {{item.detail_type}} </view> </block> </view> </block> </view> [代码] 账本页明细 [图片] 因为收支明细中需要显示每一天的消费信息,所以需要将数据表中的数据通过时间来分类,分成若干个数组,页面从而使用wx:for来遍历这些数组。在显示之前,首先需要判断有无收支信息。 [代码]// 通过时间分类算法 {} => [ [{时间1}], [{时间2}], [{时间3}] ] arr.forEach(item => { if (!_this.isExist(item.fullDate, dateArr)) { dateArr.push([item]) } else { dateArr.forEach(res => { if (res[0].fullDate == item.fullDate) { res.push(item) } }) } }) // 使用map 方法构造 [{}, {}, {}, ...] 类型数组 dateArr = dateArr.map((item) => { let spend = 0 let income = 0 item.forEach(res => { if (res.money > 0) { spend += res.money } else { income += (-res.money) } }) return { item, spend, income } }) // 判断自身是否存在数组中 isExist(item, arr) { for (let i = 0; i < arr.length; i++) { if (item == arr[i][0].fullDate) return true } return false } [代码] 以上是小程序中比较复杂的逻辑实现。 运用云开发,开发一份专属自己的旅行小账本吧~
2019-03-15